admin.ts 7.8 KB
import request, { type ApiEnvelope } from "@/utils/request";

export function login(username: string, password: string) {
  return request.post<unknown, ApiEnvelope<{ token: string; admin: AdminProfile }>>(
    "/admin/v1/login",
    { username, password }
  );
}

export interface AdminProfile {
  id: number;
  username: string;
  nickname: string;
  role_id: number;
  role_name: string;
}

export function fetchProfile() {
  return request.get<unknown, ApiEnvelope<AdminProfile>>("/admin/v1/profile");
}

export interface MenuNode {
  id: number;
  parent_id: number;
  name: string;
  type: number;
  path: string;
  component: string;
  perms: string;
  icon: string;
  sort: number;
  status?: number;
  children?: MenuNode[];
}

export function fetchMyMenus() {
  return request.get<unknown, ApiEnvelope<{ list: MenuNode[] }>>("/admin/v1/menus/my");
}

export interface DashboardStats {
  pending: number;
  approved: number;
  rejected?: number;
  mp_users: number;
  wx_users: number;
}

export function fetchDashboardStats() {
  return request.get<unknown, ApiEnvelope<DashboardStats>>("/admin/v1/dashboard/stats");
}

export interface PendingWork {
  id: number;
  userId: number;
  imageUrl: string;
  title: string;
  author: string;
  authorOpenid: string;
  uploadDate: string;
  category: string;
}

export interface RejectedWork extends PendingWork {
  rejectReason: string;
  rejectDate: string;
}

export function fetchPendingWorks() {
  return request.get<unknown, ApiEnvelope<{ list: PendingWork[] }>>("/admin/v1/works/pending");
}

export function fetchRejectedWorks() {
  return request.get<unknown, ApiEnvelope<{ list: RejectedWork[] }>>("/admin/v1/works/rejected");
}

export interface ApprovedWork extends PendingWork {
  order: number;
  /** 上传时间(完整) */
  createTime?: string;
  /** 审核通过/上架日 */
  publishDate?: string;
  /** 通过展示用日期(有 publish 用 publish,否则用 update) */
  approvedDate?: string;
  updateTime?: string;
}

export function fetchApprovedWorks(params?: { keyword?: string }) {
  const keyword = params?.keyword?.trim();
  return request.get<unknown, ApiEnvelope<{ list: ApprovedWork[] }>>(
    "/admin/v1/works/approved",
    { params: keyword ? { keyword } : undefined }
  );
}

export function approveWork(id: number) {
  return request.post(`/admin/v1/works/${id}/approve`, {});
}

export function rejectWork(id: number, payload: { reason: string }) {
  return request.post(`/admin/v1/works/${id}/reject`, payload);
}

export function deleteWork(id: number) {
  return request.post(`/admin/v1/works/${id}/delete`, {});
}

export function sortApprovedWorks(ids: number[]) {
  return request.post("/admin/v1/works/sort", { ids });
}

export interface WorkAuditDetailImage {
  url: string;
  sort: number;
}

export interface WorkAuditDetailWork {
  id: number;
  userId: number;
  title: string;
  description: string;
  shootDate: string;
  status: string;
  coverImage: string;
  likesCount: number;
  createTime: string;
  categories: string[];
  images: WorkAuditDetailImage[];
  author: string;
  authorOpenid: string;
  authorPhone: string;
  rejectReason?: string;
}

export function fetchWorkAuditDetail(id: number) {
  return request.get<unknown, ApiEnvelope<{ work: WorkAuditDetailWork }>>(
    `/admin/v1/works/${id}/detail`
  );
}

export interface SysAdminRow {
  id: number;
  username: string;
  nickname: string;
  role_id: number;
  status: number;
  create_time: string;
}

export function fetchAdminUsers() {
  return request.get<unknown, ApiEnvelope<{ list: SysAdminRow[] }>>("/admin/v1/users");
}

export function saveAdminUser(payload: Record<string, unknown>) {
  return request.post("/admin/v1/users/save", payload);
}

export function deleteAdminUser(id: number) {
  return request.post(`/admin/v1/users/${id}/delete`, {});
}

export interface RoleRow {
  id: number;
  name: string;
  code: string;
  remark: string;
  status: number;
}

export function fetchRoles() {
  return request.get<unknown, ApiEnvelope<{ list: RoleRow[] }>>("/admin/v1/roles");
}

export function saveRole(payload: Record<string, unknown>) {
  return request.post("/admin/v1/roles/save", payload);
}

export function deleteRole(id: number) {
  return request.post(`/admin/v1/roles/${id}/delete`, {});
}

export function fetchRoleMenuIds(roleId: number) {
  return request.get<unknown, ApiEnvelope<{ menu_ids: number[] }>>(`/admin/v1/roles/${roleId}/menus`);
}

export function saveRoleMenus(roleId: number, menu_ids: number[]) {
  return request.post(`/admin/v1/roles/${roleId}/menus`, { menu_ids });
}

export function fetchMenuTree() {
  return request.get<unknown, ApiEnvelope<{ list: MenuNode[] }>>("/admin/v1/menus/tree");
}

export function saveMenu(payload: Record<string, unknown>) {
  return request.post("/admin/v1/menus/save", payload);
}

export function deleteMenu(id: number) {
  return request.post(`/admin/v1/menus/${id}/delete`, {});
}

export interface MpUserRow {
  id: number;
  phone: string;
  openid: string;
  unionid: string;
  nickname: string;
  avatar: string;
  bio: string;
  gender: number;
  status: number;
  work_count: number;
  create_time: string;
}

export function fetchMpUsers(params: { keyword?: string; page?: number; page_size?: number }) {
  return request.get<unknown, ApiEnvelope<{ list: MpUserRow[]; total: number; page: number; page_size: number }>>(
    "/admin/v1/mp-users",
    { params }
  );
}

export function saveMpUser(payload: {
  id: number;
  nickname: string;
  avatar: string;
  bio: string;
  gender: number;
  status: number;
}) {
  return request.post("/admin/v1/mp-users/save", payload);
}

export function deleteMpUser(id: number) {
  return request.post(`/admin/v1/mp-users/${id}/delete`, {});
}

export interface BannerRow {
  id: number;
  image_url: string;
  title: string;
  subtitle: string;
  sort: number;
  status: number;
  create_time?: string;
  update_time?: string;
}

/** 管理后台上传图片,返回相对路径如 /uploads/20260402/xxx.png */
export function uploadAdminImage(file: File | Blob) {
  const fd = new FormData();
  const name =
    file instanceof File && file.name
      ? file.name
      : file.type === "image/png"
        ? "upload.png"
        : file.type === "image/webp"
          ? "upload.webp"
          : file.type === "image/gif"
            ? "upload.gif"
            : "upload.jpg";
  fd.append("file", file, name);
  return request.post<unknown, ApiEnvelope<{ url: string }>>("/admin/v1/upload", fd);
}

export function fetchBanners() {
  return request.get<unknown, ApiEnvelope<{ list: BannerRow[] }>>("/admin/v1/banners");
}

export function saveBanner(payload: Record<string, unknown>) {
  return request.post("/admin/v1/banners/save", payload);
}

export function deleteBanner(id: number) {
  return request.post(`/admin/v1/banners/${id}/delete`, {});
}

export interface MapSpotRow {
  id: number;
  name: string;
  lat: number;
  lng: number;
  description: string;
  thumbnail: string;
  photos_count: number;
  pin_left_pct: number;
  pin_top_pct: number;
  sort: number;
  status: number;
  create_time?: string;
  update_time?: string;
}

export function fetchMapSpots() {
  return request.get<unknown, ApiEnvelope<{ list: MapSpotRow[] }>>("/admin/v1/map-spots");
}

export function saveMapSpot(payload: Record<string, unknown>) {
  return request.post("/admin/v1/map-spots/save", payload);
}

export function deleteMapSpot(id: number) {
  return request.post(`/admin/v1/map-spots/${id}/delete`, {});
}

export interface DownloadAssetsConfig {
  certificate: string;
  guide: string;
  updatedAt?: {
    certificate?: string;
    guide?: string;
  };
}

export function fetchDownloadAssets() {
  return request.get<unknown, ApiEnvelope<DownloadAssetsConfig>>("/admin/v1/download-assets");
}

export function saveDownloadAsset(payload: { type: "certificate" | "guide"; image_url: string }) {
  return request.post("/admin/v1/download-assets/save", payload);
}