Write

import type { ApiResponse } from "@/types/api";
import type {
  DictType,
  DictData,
  DictQueryParams,
  DictTypeQueryParams,
} from "@/types/dict";

import { request } from "@/utils/request";

const isMock = import.meta.env.VITE_USE_MOCK === "true";

// backend status mapping: '0' = enabled, '1' = disabled
const mapTypeFromBackend = (t: any): DictType => ({
  ...t,
  status: t.status === "1" ? "disabled" : "enabled",
});
const mapDataFromBackend = (d: any): DictData => ({
  ...d,
  status: d.status === "1" ? "disabled" : "enabled",
});
const mapStatusToBackend = (status?: string) =>
  status === "disabled" ? "1" : status === "enabled" ? "0" : status;

// ---------------- Mock helpers ----------------

const ok = <T>(data: T, message = "success"): ApiResponse<T> => ({
  code: 200,
  message,
  data,
  success: true,
});

const error = <T = null>(code: number, message: string): ApiResponse<T> => ({
  code,
  message,
  data: null as T,
  success: false,
});

// ---------------- Dict Type APIs ----------------

/**
 * 获取所有字典类型
 */
export async function getDictTypes(): Promise<ApiResponse<DictType[]>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    return ok(dictTypes.filter((t) => t.status === "enabled"));
  }

  const res: any = await request.get("/sys/dict/type/list");
  return {
    code: 200,
    message: res.message || "success",
    data: (res.data || res).map(mapTypeFromBackend),
    success: true,
  };
}

/**
 * 获取字典类型列表（分页）
 */
export async function getDictTypeList(
  params: DictTypeQueryParams,
): Promise<
  ApiResponse<{
    list: DictType[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const { page = 1, pageSize = 10, code, name, status } = params;
    let filtered = [...dictTypes];
    if (code) filtered = filtered.filter((item) => item.code.includes(code));
    if (name) filtered = filtered.filter((item) => item.name.includes(name));
    if (status) filtered = filtered.filter((item) => item.status === status);
    const start = (page - 1) * pageSize;
    const list = filtered.slice(start, start + pageSize);
    return ok({ list, total: filtered.length, page, pageSize });
  }

  const res: any = await request.post("/sys/dict/type/page", {
    page: params.page || 1,
    pageSize: params.pageSize || 10,
    name: params.name,
    code: params.code,
    status: mapStatusToBackend(params.status),
  });
  return {
    code: 200,
    message: res.message || "success",
    data: {
      list: (res.list || []).map(mapTypeFromBackend),
      total: res.total || 0,
      page: res.page || params.page || 1,
      pageSize: res.pageSize || params.pageSize || 10,
    },
    success: true,
  };
}

/**
 * 创建字典类型
 */
export async function createDictType(
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const newType: DictType = {
      id: String(Date.now()),
      name: data.name || "",
      code: data.code || "",
      description: data.description,
      status: data.status || "enabled",
      createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    dictTypes.push(newType);
    return ok(newType, "创建成功");
  }

  const res: any = await request.post("/sys/dict/type/save", {
    name: data.name,
    code: data.code,
    description: data.description,
    status: mapStatusToBackend(data.status),
  });
  return {
    code: 200,
    message: res.message || "创建成功",
    data: res.data ? mapTypeFromBackend({ ...data, id: res.data }) : null,
    success: true,
  };
}

/**
 * 更新字典类型
 */
export async function updateDictType(
  id: string,
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const index = dictTypes.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典类型不存在");
    dictTypes[index] = {
      ...dictTypes[index],
      ...data,
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    return ok(dictTypes[index], "更新成功");
  }

  const res: any = await request.post("/sys/dict/type/save", {
    id,
    name: data.name,
    code: data.code,
    description: data.description,
    status: mapStatusToBackend(data.status),
  });
  return {
    code: 200,
    message: res.message || "更新成功",
    data: null,
    success: true,
  };
}

/**
 * 删除字典类型
 */
export async function deleteDictType(id: string): Promise<ApiResponse<void>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const index = dictTypes.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典类型不存在");
    dictTypes.splice(index, 1);
    return ok(undefined as unknown as void, "删除成功");
  }

  await request.post("/sys/dict/type/delete", { id });
  return { code: 200, message: "删除成功", data: null, success: true };
}

// ---------------- Dict Data APIs ----------------

/**
 * 获取所有字典数据
 */
export async function getAllDictData(): Promise<ApiResponse<DictData[]>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    return ok(dictData);
  }

  const res: any = await request.get("/sys/dict/data/all");
  const list = (res.data || res).map(mapDataFromBackend);
  return { code: 200, message: res.message || "success", data: list, success: true };
}

/**
 * 根据类型获取字典数据
 */
export async function getDictDataByType(
  typeCode: string,
): Promise<ApiResponse<DictData[]>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const filtered = dictData.filter(
      (d) => d.typeCode === typeCode && d.status === "enabled",
    );
    filtered.sort((a, b) => a.sort - b.sort);
    return ok(filtered);
  }

  const res: any = await request.post("/sys/dict/data/page", {
    typeCode,
    page: 1,
    pageSize: 9999,
  });
  const list = (res.list || []).map(mapDataFromBackend).filter((d: DictData) => d.status === "enabled");
  return { code: 200, message: "success", data: list, success: true };
}

/**
 * 获取字典数据列表（分页）
 */
export async function getDictDataList(
  params: DictQueryParams,
): Promise<
  ApiResponse<{
    list: DictData[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const { page = 1, pageSize = 10, typeCode, label, value, status } = params;
    let filtered = [...dictData];
    if (typeCode) filtered = filtered.filter((item) => item.typeCode === typeCode);
    if (label) filtered = filtered.filter((item) => item.label.includes(label));
    if (value) filtered = filtered.filter((item) => item.value.includes(value));
    if (status) filtered = filtered.filter((item) => item.status === status);
    filtered.sort((a, b) => a.sort - b.sort);
    const start = (page - 1) * pageSize;
    const list = filtered.slice(start, start + pageSize);
    return ok({ list, total: filtered.length, page, pageSize });
  }

  const res: any = await request.post("/sys/dict/data/page", {
    page: params.page || 1,
    pageSize: params.pageSize || 10,
    typeCode: params.typeCode,
    label: params.label,
    value: params.value,
    status: mapStatusToBackend(params.status),
  });
  return {
    code: 200,
    message: res.message || "success",
    data: {
      list: (res.list || []).map(mapDataFromBackend),
      total: res.total || 0,
      page: res.page || params.page || 1,
      pageSize: res.pageSize || params.pageSize || 10,
    },
    success: true,
  };
}

/**
 * 创建字典数据
 */
export async function createDictData(
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const newData: DictData = {
      id: String(Date.now()),
      typeCode: data.typeCode || "",
      label: data.label || "",
      value: data.value || "",
      sort: data.sort || 0,
      status: data.status || "enabled",
      remark: data.remark,
      createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    dictData.push(newData);
    return ok(newData, "创建成功");
  }

  const res: any = await request.post("/sys/dict/data/save", {
    typeCode: data.typeCode,
    label: data.label,
    value: data.value,
    sort: data.sort,
    status: mapStatusToBackend(data.status),
    remark: data.remark,
  });
  return {
    code: 200,
    message: res.message || "创建成功",
    data: res.data ? { ...data, id: res.data } as DictData : null,
    success: true,
  };
}

/**
 * 更新字典数据
 */
export async function updateDictData(
  id: string,
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const index = dictData.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典数据不存在");
    dictData[index] = {
      ...dictData[index],
      ...data,
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    return ok(dictData[index], "更新成功");
  }

  const res: any = await request.post("/sys/dict/data/save", {
    id,
    typeCode: data.typeCode,
    label: data.label,
    value: data.value,
    sort: data.sort,
    status: mapStatusToBackend(data.status),
    remark: data.remark,
  });
  return {
    code: 200,
    message: res.message || "更新成功",
    data: null,
    success: true,
  };
}

/**
 * 删除字典数据
 */
export async function deleteDictData(id: string): Promise<ApiResponse<void>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const index = dictData.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典数据不存在");
    dictData.splice(index, 1);
    return ok(undefined as unknown as void, "删除成功");
  }

  await request.post("/sys/dict/data/delete", { id });
  return { code: 200, message: "删除成功", data: null, success: true };
}


The file c:/Workspace/Project/tansci/antdv-next-admin/src/api/dict.ts has been updated successfully. (file state is current in your context — no need to Read it back)

import type { ApiResponse } from "@/types/api";
import type {
  DictType,
  DictData,
  DictQueryParams,
  DictTypeQueryParams,
} from "@/types/dict";

import { request } from "@/utils/request";

const isMock = import.meta.env.VITE_USE_MOCK === "true";

// backend status mapping: '0' = enabled, '1' = disabled
const mapTypeFromBackend = (t: any): DictType => ({
  ...t,
  status: t.status === "1" ? "disabled" : "enabled",
});
const mapDataFromBackend = (d: any): DictData => ({
  ...d,
  status: d.status === "1" ? "disabled" : "enabled",
});
const mapStatusToBackend = (status?: string) =>
  status === "disabled" ? "1" : status === "enabled" ? "0" : status;

// ---------------- Mock helpers ----------------

const ok = <T>(data: T, message = "success"): ApiResponse<T> => ({
  code: 200,
  message,
  data,
  success: true,
});

const error = <T = null>(code: number, message: string): ApiResponse<T> => ({
  code,
  message,
  data: null as T,
  success: false,
});

// ---------------- Dict Type APIs ----------------

/**
 * 获取所有字典类型
 */
export async function getDictTypes(): Promise<ApiResponse<DictType[]>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    return ok(dictTypes.filter((t) => t.status === "enabled"));
  }

  const res: any = await request.get("/sys/dict/type/list");
  return {
    code: 200,
    message: res.message || "success",
    data: (res.data || res).map(mapTypeFromBackend),
    success: true,
  };
}

/**
 * 获取字典类型列表（分页）
 */
export async function getDictTypeList(
  params: DictTypeQueryParams,
): Promise<
  ApiResponse<{
    list: DictType[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const { page = 1, pageSize = 10, code, name, status } = params;
    let filtered = [...dictTypes];
    if (code) filtered = filtered.filter((item) => item.code.includes(code));
    if (name) filtered = filtered.filter((item) => item.name.includes(name));
    if (status) filtered = filtered.filter((item) => item.status === status);
    const start = (page - 1) * pageSize;
    const list = filtered.slice(start, start + pageSize);
    return ok({ list, total: filtered.length, page, pageSize });
  }

  const res: any = await request.post("/sys/dict/type/page", {
    page: params.page || 1,
    pageSize: params.pageSize || 10,
    name: params.name,
    code: params.code,
    status: mapStatusToBackend(params.status),
  });
  return {
    code: 200,
    message: res.message || "success",
    data: {
      list: (res.list || []).map(mapTypeFromBackend),
      total: res.total || 0,
      page: res.page || params.page || 1,
      pageSize: res.pageSize || params.pageSize || 10,
    },
    success: true,
  };
}

/**
 * 创建字典类型
 */
export async function createDictType(
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const newType: DictType = {
      id: String(Date.now()),
      name: data.name || "",
      code: data.code || "",
      description: data.description,
      status: data.status || "enabled",
      createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    dictTypes.push(newType);
    return ok(newType, "创建成功");
  }

  const res: any = await request.post("/sys/dict/type/save", {
    name: data.name,
    code: data.code,
    description: data.description,
    status: mapStatusToBackend(data.status),
  });
  return {
    code: 200,
    message: res.message || "创建成功",
    data: res.data ? mapTypeFromBackend({ ...data, id: res.data }) : null,
    success: true,
  };
}

/**
 * 更新字典类型
 */
export async function updateDictType(
  id: string,
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const index = dictTypes.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典类型不存在");
    dictTypes[index] = {
      ...dictTypes[index],
      ...data,
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    return ok(dictTypes[index], "更新成功");
  }

  const res: any = await request.post("/sys/dict/type/save", {
    id,
    name: data.name,
    code: data.code,
    description: data.description,
    status: mapStatusToBackend(data.status),
  });
  return {
    code: 200,
    message: res.message || "更新成功",
    data: null,
    success: true,
  };
}

/**
 * 删除字典类型
 */
export async function deleteDictType(id: string): Promise<ApiResponse<void>> {
  if (isMock) {
    const { dictTypes } = await import("../../mock/data/dict.data");
    const index = dictTypes.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典类型不存在");
    dictTypes.splice(index, 1);
    return ok(undefined as unknown as void, "删除成功");
  }

  await request.post("/sys/dict/type/delete", { id });
  return { code: 200, message: "删除成功", data: null, success: true };
}

// ---------------- Dict Data APIs ----------------

/**
 * 获取所有字典数据
 */
export async function getAllDictData(): Promise<ApiResponse<DictData[]>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    return ok(dictData);
  }

  const res: any = await request.get("/sys/dict/data/all");
  const list = (res.data || res).map(mapDataFromBackend);
  return { code: 200, message: res.message || "success", data: list, success: true };
}

/**
 * 根据类型获取字典数据
 */
export async function getDictDataByType(
  typeCode: string,
): Promise<ApiResponse<DictData[]>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const filtered = dictData.filter(
      (d) => d.typeCode === typeCode && d.status === "enabled",
    );
    filtered.sort((a, b) => a.sort - b.sort);
    return ok(filtered);
  }

  const res: any = await request.post("/sys/dict/data/page", {
    typeCode,
    page: 1,
    pageSize: 9999,
  });
  const list = (res.list || []).map(mapDataFromBackend).filter((d: DictData) => d.status === "enabled");
  return { code: 200, message: "success", data: list, success: true };
}

/**
 * 获取字典数据列表（分页）
 */
export async function getDictDataList(
  params: DictQueryParams,
): Promise<
  ApiResponse<{
    list: DictData[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const { page = 1, pageSize = 10, typeCode, label, value, status } = params;
    let filtered = [...dictData];
    if (typeCode) filtered = filtered.filter((item) => item.typeCode === typeCode);
    if (label) filtered = filtered.filter((item) => item.label.includes(label));
    if (value) filtered = filtered.filter((item) => item.value.includes(value));
    if (status) filtered = filtered.filter((item) => item.status === status);
    filtered.sort((a, b) => a.sort - b.sort);
    const start = (page - 1) * pageSize;
    const list = filtered.slice(start, start + pageSize);
    return ok({ list, total: filtered.length, page, pageSize });
  }

  const res: any = await request.post("/sys/dict/data/page", {
    page: params.page || 1,
    pageSize: params.pageSize || 10,
    typeCode: params.typeCode,
    label: params.label,
    value: params.value,
    status: mapStatusToBackend(params.status),
  });
  return {
    code: 200,
    message: res.message || "success",
    data: {
      list: (res.list || []).map(mapDataFromBackend),
      total: res.total || 0,
      page: res.page || params.page || 1,
      pageSize: res.pageSize || params.pageSize || 10,
    },
    success: true,
  };
}

/**
 * 创建字典数据
 */
export async function createDictData(
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const newData: DictData = {
      id: String(Date.now()),
      typeCode: data.typeCode || "",
      label: data.label || "",
      value: data.value || "",
      sort: data.sort || 0,
      status: data.status || "enabled",
      remark: data.remark,
      createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    dictData.push(newData);
    return ok(newData, "创建成功");
  }

  const res: any = await request.post("/sys/dict/data/save", {
    typeCode: data.typeCode,
    label: data.label,
    value: data.value,
    sort: data.sort,
    status: mapStatusToBackend(data.status),
    remark: data.remark,
  });
  return {
    code: 200,
    message: res.message || "创建成功",
    data: res.data ? { ...data, id: res.data } as DictData : null,
    success: true,
  };
}

/**
 * 更新字典数据
 */
export async function updateDictData(
  id: string,
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const index = dictData.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典数据不存在");
    dictData[index] = {
      ...dictData[index],
      ...data,
      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    };
    return ok(dictData[index], "更新成功");
  }

  const res: any = await request.post("/sys/dict/data/save", {
    id,
    typeCode: data.typeCode,
    label: data.label,
    value: data.value,
    sort: data.sort,
    status: mapStatusToBackend(data.status),
    remark: data.remark,
  });
  return {
    code: 200,
    message: res.message || "更新成功",
    data: null,
    success: true,
  };
}

/**
 * 删除字典数据
 */
export async function deleteDictData(id: string): Promise<ApiResponse<void>> {
  if (isMock) {
    const { dictData } = await import("../../mock/data/dict.data");
    const index = dictData.findIndex((item) => item.id === id);
    if (index === -1) return error(404, "字典数据不存在");
    dictData.splice(index, 1);
    return ok(undefined as unknown as void, "删除成功");
  }

  await request.post("/sys/dict/data/delete", { id });
  return { code: 200, message: "删除成功", data: null, success: true };
}

const isMock = import.meta.env.VITE_USE_MOCK === "true";

+// backend status mapping: '0' = enabled, '1' = disabled

+const mapTypeFromBackend = (t: any): DictType => ({

+  status: t.status === "1" ? "disabled" : "enabled",

+const mapDataFromBackend = (d: any): DictData => ({

+  status: d.status === "1" ? "disabled" : "enabled",

+const mapStatusToBackend = (status?: string) =>

+  status === "disabled" ? "1" : status === "enabled" ? "0" : status;

-  if (!isMock) return request.get("/dict/types");

+    const { dictTypes } = await import("../../mock/data/dict.data");

+    return ok(dictTypes.filter((t) => t.status === "enabled"));

-  const { dictTypes } = await import("../../mock/data/dict.data");

-  return ok(dictTypes.filter((t) => t.status === "enabled"));

+  const res: any = await request.get("/sys/dict/type/list");

+    data: (res.data || res).map(mapTypeFromBackend),

* 获取字典类型列表（分页）

-export async function getDictTypeList(params: DictTypeQueryParams): Promise<

-  if (!isMock) return request.get("/dict/type/list", { params });

+    const { page = 1, pageSize = 10, code, name, status } = params;

+    if (code) filtered = filtered.filter((item) => item.code.includes(code));

+    if (name) filtered = filtered.filter((item) => item.name.includes(name));

+    if (status) filtered = filtered.filter((item) => item.status === status);

+    const start = (page - 1) * pageSize;

+    const list = filtered.slice(start, start + pageSize);

+    return ok({ list, total: filtered.length, page, pageSize });

-  const { page = 1, pageSize = 10, code, name, status } = params;

-  if (code) filtered = filtered.filter((item) => item.code.includes(code));

-  if (name) filtered = filtered.filter((item) => item.name.includes(name));

-  if (status) filtered = filtered.filter((item) => item.status === status);

-  const list = filtered.slice(start, start + pageSize);

-  return ok({ list, total: filtered.length, page, pageSize });

+  const res: any = await request.post("/sys/dict/type/page", {

+    status: mapStatusToBackend(params.status),

+      list: (res.list || []).map(mapTypeFromBackend),

+      page: res.page || params.page || 1,

+      pageSize: res.pageSize || params.pageSize || 10,

-  if (!isMock) return request.post("/dict/type", data);

+      status: data.status || "enabled",

+      createTime: new Date().toISOString().replace("T", " ").slice(0, 19),

+      updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),

+  const res: any = await request.post("/sys/dict/type/save", {

-    createTime: new Date().toISOString().replace("T", " ").slice(0, 19),

-    updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),

+    status: mapStatusToBackend(data.status),

+    data: res.data ? mapTypeFromBackend({ ...data, id: res.data }) : null,

-  if (!isMock) return request.put(`/dict/type/${id}`, data);

+    const index = dictTypes.findIndex((item) => item.id === id);

+    if (index === -1) return error(404, "字典类型不存在");

+    return ok(dictTypes[index], "更新成功");

-  const index = dictTypes.findIndex((item) => item.id === id);

-  if (index === -1) return error(404, "字典类型不存在");

-  if (!isMock) return request.delete(`/dict/type/${id}`);

+    return ok(undefined as unknown as void, "删除成功");

-  return ok(undefined as unknown as void, "删除成功");

+  await request.post("/sys/dict/type/delete", { id });

+  return { code: 200, message: "删除成功", data: null, success: true };

-  if (!isMock) return request.get("/dict/data/all");

+    const { dictData } = await import("../../mock/data/dict.data");

-  const { dictData } = await import("../../mock/data/dict.data");

+  const res: any = await request.get("/sys/dict/data/all");

+  const list = (res.data || res).map(mapDataFromBackend);

+  return { code: 200, message: res.message || "success", data: list, success: true };

export async function getDictDataByType(

-  if (!isMock) return request.get(`/dict/data/${typeCode}`);

+      (d) => d.typeCode === typeCode && d.status === "enabled",

+    filtered.sort((a, b) => a.sort - b.sort);

-    (d) => d.typeCode === typeCode && d.status === "enabled",

-  filtered.sort((a, b) => a.sort - b.sort);

+  const res: any = await request.post("/sys/dict/data/page", {

+  const list = (res.list || []).map(mapDataFromBackend).filter((d: DictData) => d.status === "enabled");

+  return { code: 200, message: "success", data: list, success: true };

* 获取字典数据列表（分页）

-export async function getDictDataList(params: DictQueryParams): Promise<

-  if (!isMock) return request.get("/dict/data/list", { params });

+    const { page = 1, pageSize = 10, typeCode, label, value, status } = params;

+    if (typeCode) filtered = filtered.filter((item) => item.typeCode === typeCode);

+    if (label) filtered = filtered.filter((item) => item.label.includes(label));

+    if (value) filtered = filtered.filter((item) => item.value.includes(value));

+    const start = (page - 1) * pageSize;

+    const list = filtered.slice(start, start + pageSize);

+    return ok({ list, total: filtered.length, page, pageSize });

-  const { page = 1, pageSize = 10, typeCode, label, value, status } = params;

-    filtered = filtered.filter((item) => item.typeCode === typeCode);

-  if (label) filtered = filtered.filter((item) => item.label.includes(label));

-  if (value) filtered = filtered.filter((item) => item.value.includes(value));

-  filtered.sort((a, b) => a.sort - b.sort);

-  const list = filtered.slice(start, start + pageSize);

-  return ok({ list, total: filtered.length, page, pageSize });

+  const res: any = await request.post("/sys/dict/data/page", {

+    status: mapStatusToBackend(params.status),

+      list: (res.list || []).map(mapDataFromBackend),

+      page: res.page || params.page || 1,

+      pageSize: res.pageSize || params.pageSize || 10,

-  if (!isMock) return request.post("/dict/data", data);

+      status: data.status || "enabled",

+  const res: any = await request.post("/sys/dict/data/save", {

+    status: mapStatusToBackend(data.status),

+    data: res.data ? { ...data, id: res.data } as DictData : null,

-  if (!isMock) return request.put(`/dict/data/${id}`, data);

+    const index = dictData.findIndex((item) => item.id === id);

+    if (index === -1) return error(404, "字典数据不存在");

+    return ok(dictData[index], "更新成功");

-  const index = dictData.findIndex((item) => item.id === id);

-  if (index === -1) return error(404, "字典数据不存在");

-  if (!isMock) return request.delete(`/dict/data/${id}`);

+    return ok(undefined as unknown as void, "删除成功");

-  return ok(undefined as unknown as void, "删除成功");

+  await request.post("/sys/dict/data/delete", { id });

import type { ApiResponse } from "@/types/api";
import type {
  DictType,
  DictData,
  DictQueryParams,
  DictTypeQueryParams,
} from "@/types/dict";

import { request } from "@/utils/request";

const isMock = import.meta.env.VITE_USE_MOCK === "true";

const ok = <T>(data: T, message = "success"): ApiResponse<T> => ({
  code: 200,
  message,
  data,
  success: true,
});

const error = <T = null>(code: number, message: string): ApiResponse<T> => ({
  code,
  message,
  data: null as T,
  success: false,
});

/**
 * 获取所有字典类型
 */
export async function getDictTypes(): Promise<ApiResponse<DictType[]>> {
  if (!isMock) return request.get("/dict/types");

  const { dictTypes } = await import("../../mock/data/dict.data");
  return ok(dictTypes.filter((t) => t.status === "enabled"));
}

/**
 * 获取字典类型列表（分页）
 */
export async function getDictTypeList(params: DictTypeQueryParams): Promise<
  ApiResponse<{
    list: DictType[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (!isMock) return request.get("/dict/type/list", { params });

  const { dictTypes } = await import("../../mock/data/dict.data");
  const { page = 1, pageSize = 10, code, name, status } = params;
  let filtered = [...dictTypes];

  if (code) filtered = filtered.filter((item) => item.code.includes(code));
  if (name) filtered = filtered.filter((item) => item.name.includes(name));
  if (status) filtered = filtered.filter((item) => item.status === status);

  const start = (page - 1) * pageSize;
  const list = filtered.slice(start, start + pageSize);

  return ok({ list, total: filtered.length, page, pageSize });
}

/**
 * 创建字典类型
 */
export async function createDictType(
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (!isMock) return request.post("/dict/type", data);

  const { dictTypes } = await import("../../mock/data/dict.data");
  const newType: DictType = {
    id: String(Date.now()),
    name: data.name || "",
    code: data.code || "",
    description: data.description,
    status: data.status || "enabled",
    createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
  };
  dictTypes.push(newType);
  return ok(newType, "创建成功");
}

/**
 * 更新字典类型
 */
export async function updateDictType(
  id: string,
  data: Partial<DictType>,
): Promise<ApiResponse<DictType>> {
  if (!isMock) return request.put(`/dict/type/${id}`, data);

  const { dictTypes } = await import("../../mock/data/dict.data");
  const index = dictTypes.findIndex((item) => item.id === id);
  if (index === -1) return error(404, "字典类型不存在");

  dictTypes[index] = {
    ...dictTypes[index],
    ...data,
    updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
  };
  return ok(dictTypes[index], "更新成功");
}

/**
 * 删除字典类型
 */
export async function deleteDictType(id: string): Promise<ApiResponse<void>> {
  if (!isMock) return request.delete(`/dict/type/${id}`);

  const { dictTypes } = await import("../../mock/data/dict.data");
  const index = dictTypes.findIndex((item) => item.id === id);
  if (index === -1) return error(404, "字典类型不存在");

  dictTypes.splice(index, 1);
  return ok(undefined as unknown as void, "删除成功");
}

/**
 * 获取所有字典数据
 */
export async function getAllDictData(): Promise<ApiResponse<DictData[]>> {
  if (!isMock) return request.get("/dict/data/all");

  const { dictData } = await import("../../mock/data/dict.data");
  return ok(dictData);
}

/**
 * 根据类型获取字典数据
 */
export async function getDictDataByType(
  typeCode: string,
): Promise<ApiResponse<DictData[]>> {
  if (!isMock) return request.get(`/dict/data/${typeCode}`);

  const { dictData } = await import("../../mock/data/dict.data");
  const filtered = dictData.filter(
    (d) => d.typeCode === typeCode && d.status === "enabled",
  );
  filtered.sort((a, b) => a.sort - b.sort);
  return ok(filtered);
}

/**
 * 获取字典数据列表（分页）
 */
export async function getDictDataList(params: DictQueryParams): Promise<
  ApiResponse<{
    list: DictData[];
    total: number;
    page: number;
    pageSize: number;
  }>
> {
  if (!isMock) return request.get("/dict/data/list", { params });

  const { dictData } = await import("../../mock/data/dict.data");
  const { page = 1, pageSize = 10, typeCode, label, value, status } = params;
  let filtered = [...dictData];

  if (typeCode)
    filtered = filtered.filter((item) => item.typeCode === typeCode);
  if (label) filtered = filtered.filter((item) => item.label.includes(label));
  if (value) filtered = filtered.filter((item) => item.value.includes(value));
  if (status) filtered = filtered.filter((item) => item.status === status);

  filtered.sort((a, b) => a.sort - b.sort);

  const start = (page - 1) * pageSize;
  const list = filtered.slice(start, start + pageSize);

  return ok({ list, total: filtered.length, page, pageSize });
}

/**
 * 创建字典数据
 */
export async function createDictData(
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (!isMock) return request.post("/dict/data", data);

  const { dictData } = await import("../../mock/data/dict.data");
  const newData: DictData = {
    id: String(Date.now()),
    typeCode: data.typeCode || "",
    label: data.label || "",
    value: data.value || "",
    sort: data.sort || 0,
    status: data.status || "enabled",
    remark: data.remark,
    createTime: new Date().toISOString().replace("T", " ").slice(0, 19),
    updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
  };
  dictData.push(newData);
  return ok(newData, "创建成功");
}

/**
 * 更新字典数据
 */
export async function updateDictData(
  id: string,
  data: Partial<DictData>,
): Promise<ApiResponse<DictData>> {
  if (!isMock) return request.put(`/dict/data/${id}`, data);

  const { dictData } = await import("../../mock/data/dict.data");
  const index = dictData.findIndex((item) => item.id === id);
  if (index === -1) return error(404, "字典数据不存在");

  dictData[index] = {
    ...dictData[index],
    ...data,
    updateTime: new Date().toISOString().replace("T", " ").slice(0, 19),
  };
  return ok(dictData[index], "更新成功");
}

/**
 * 删除字典数据
 */
export async function deleteDictData(id: string): Promise<ApiResponse<void>> {
  if (!isMock) return request.delete(`/dict/data/${id}`);

  const { dictData } = await import("../../mock/data/dict.data");
  const index = dictData.findIndex((item) => item.id === id);
  if (index === -1) return error(404, "字典数据不存在");

  dictData.splice(index, 1);
  return ok(undefined as unknown as void, "删除成功");
}


添加字典表以及字典表页面，另外，现在前端页面资产管理确实  但是在菜单列表是可以看到的


Now I need to:
1. Add the dict route to asyncRoutes
2. Add menu item to sys_menu database table
3. Fix the dict page's response handling (it checks `response.code === 200` but the response wrapper already handles this)

Also, looking at the dict store, it checks `response.code === 1` (line 51). After the response interceptor passes through the response, the code will be `1` from the backend. But wait — in my rewritten API, I'm wrapping the response with `code: 200`. So the store will get `code: 200`. But the store checks for `code === 1`. I need to fix the store too.

Actually, wait. Looking at the response interceptor again:
```js
if (typeof res === "object" && res.code !== undefined && res.code !== 1) {
  // ERROR
}
return res;
```

So on success, it returns the original response (which has `code: 1`). But in my rewritten API, I'M NOT directly calling the API — I'm doing extra processing. Let me look at how the API functions work...

In `getAllDictData`:
```js
const res: any = await request.get("/sys/dict/data/all");
```

This `res` will be the response data from the interceptor. Since magic-api returns `{code: 1, message: "success", data: [...]}`, `res` will be that object.

But in my code, I'm doing:
```js
const list = (res.data || res).map(mapDataFromBackend);
return { code: 200, message: res.message || "success", data: list, success: true };
```

I'm re-wrapping with `code: 200`. This is consistent with the mock format.

Then the dict store checks:
```js
if (response.code === 1) { ... }
```

This will fail! It should be `response.code === 200` since I'm re-wrapping with code 200.

Let me fix the dict store too.


现在添加路由、修正 store 的响应码检查，并添加数据库菜单记录。


Edit

if (response.code === 200 || response.code === 1) {


The file c:/Workspace/Project/tansci/antdv-next-admin/src/stores/dict.ts has been updated successfully. (file state is current in your context — no need to Read it back)

import type { DictData, DictGroup } from "@/types/dict";

import { defineStore } from "pinia";
import { ref, computed } from "vue";

import { getAllDictData } from "@/api/dict";

export const useDictStore = defineStore("dict", () => {
  // 所有字典数据
  const dictData = ref<DictData[]>([]);

  // 是否已加载
  const loaded = ref(false);

  // 是否正在加载
  const loading = ref(false);

  // 按类型分组的字典数据
  const dictGroup = computed<DictGroup>(() => {
    const group: DictGroup = {};
    dictData.value.forEach((item) => {
      if (!group[item.typeCode]) {
        group[item.typeCode] = [];
      }
      group[item.typeCode].push(item);
    });
    // 按 sort 排序
    Object.keys(group).forEach((key) => {
      group[key].sort((a, b) => a.sort - b.sort);
    });
    return group;
  });

  /**
   * 加载所有字典数据
   */
  const loadDictData = async (force = false) => {
    // 如果已加载且不强制刷新，直接返回
    if (loaded.value && !force) {
      return;
    }

    // 如果正在加载，避免重复请求
    if (loading.value) {
      return;
    }

    try {
      loading.value = true;
      const response = await getAllDictData();
      if (response.code === 1) {
        dictData.value = response.data.filter(
          (item: DictData) => item.status === "enabled",
        );
        loaded.value = true;
      }
    } catch {
      // Dict API not available on backend yet, fail silently
    } finally {
      loading.value = false;
    }
  };

  /**
   * 根据类型获取字典数据
   */
  const getDictByType = (typeCode: string): DictData[] => {
    return dictGroup.value[typeCode] || [];
  };

  /**
   * 根据类型和值获取标签
   */
  const getDictLabel = (typeCode: string, value: string): string => {
    const dict = dictGroup.value[typeCode]?.find(
      (item) => item.value === value,
    );
    return dict?.label || value;
  };

  /**
   * 根据类型和标签获取值
   */
  const getDictValue = (typeCode: string, label: string): string => {
    const dict = dictGroup.value[typeCode]?.find(
      (item) => item.label === label,
    );
    return dict?.value || label;
  };

  /**
   * 获取字典选项（用于下拉框）
   */
  const getDictOptions = (typeCode: string) => {
    return getDictByType(typeCode).map((item) => ({
      label: item.label,
      value: item.value,
    }));
  };

  /**
   * 刷新字典数据
   */
  const refreshDictData = () => {
    return loadDictData(true);
  };

  /**
   * 清空字典数据
   */
  const clearDictData = () => {
    dictData.value = [];
    loaded.value = false;
  };

  return {
    dictData,
    dictGroup,
    loaded,
    loading,
    loadDictData,
    getDictByType,
    getDictLabel,
    getDictValue,
    getDictOptions,
    refreshDictData,
    clearDictData,
  };
});

+      if (response.code === 200 || response.code === 1) {


Now let me add the dict route to asyncRoutes in routes.ts and insert the menu item into the database.


现在添加字典路由到 `asyncRoutes`。


      {
        path: "permission",
        name: "SystemPermission",
        component: () => import("@/views/system/permission/index.vue"),
        meta: {
          title: "menu.permission",
          icon: "SafetyCertificateOutlined",
          requiresAuth: true,
        },
      },
    ],
  },

      {
        path: "permission",
        name: "SystemPermission",
        component: () => import("@/views/system/permission/index.vue"),
        meta: {
          title: "menu.permission",
          icon: "SafetyCertificateOutlined",
          requiresAuth: true,
        },
      },
      {
        path: "dict",
        name: "SystemDict",
        component: () => import("@/views/system/dict/index.vue"),
        meta: {
          title: "menu.dict",
          icon: "BookOutlined",
          requiresAuth: true,
        },
      },
    ],
  },


The file c:/Workspace/Project/tansci/antdv-next-admin/src/router/routes.ts has been updated successfully. (file state is current in your context — no need to Read it back)

      {
        path: "permission",
        name: "SystemPermission",
        component: () => import("@/views/system/permission/index.vue"),
        meta: {
          title: "menu.permission",
          icon: "SafetyCertificateOutlined",
          requiresAuth: true,
        },
      },
    ],
  },

      {
        path: "permission",
        name: "SystemPermission",
        component: () => import("@/views/system/permission/index.vue"),
        meta: {
          title: "menu.permission",
          icon: "SafetyCertificateOutlined",
          requiresAuth: true,
        },
      },
      {
        path: "dict",
        name: "SystemDict",
        component: () => import("@/views/system/dict/index.vue"),
        meta: {
          title: "menu.dict",
          icon: "BookOutlined",
          requiresAuth: true,
        },
      },
    ],
  },

+        component: () => import("@/views/system/dict/index.vue"),


Now I need to:
1. Add i18n strings for the dict menu
2. Add menu item to sys_menu database
3. Check if there are i18n keys for dict already

Let me check the i18n files for existing dict translations.


现在添加 i18n 翻译和数据库菜单项。


94:    dict: "数据字典",
409:  dict: {
411:    dictType: "字典类型",
412:    dictData: "字典数据",
416:    dictLabel: "字典标签",
417:    dictValue: "字典值",
445:    dictDataTitle: "字典数据 - {name}",
1122:      dictionary: "数据字典",


There are already dict-related i18n strings in zh-CN.ts. Let me check the "menu" section to see what key I should add for the menu. I see `menu.dict` should map to "数据字典".

Let me check what keys are in the menu section of zh-CN.ts.


86	  menu: {
87	    dashboard: "数据看板",
88	    organization: "组织权限",
89	    system: "系统管理",
90	    user: "用户管理",
91	    role: "角色管理",
92	    permission: "权限管理",
93	    menu: "菜单管理",
94	    dict: "数据字典",
95	    log: "系统日志",
96	    dept: "部门管理",
97	    config: "系统配置",
98	    file: "文件管理",
99	    examples: "示例中心",
100	    examplesQuickStart: "快速开始",
101	    examplesFormInput: "表单与输入",
102	    examplesContent: "内容编辑",
103	    examplesInteraction: "基础交互",
104	    examplesBusinessScaffold: "业务脚手架",
105	    examplesSecurityEngineering: "安全与工程",

  menu: {
    dashboard: "数据看板",
    organization: "组织权限",
    system: "系统管理",
    user: "用户管理",
    role: "角色管理",
    permission: "权限管理",
    menu: "菜单管理",
    dict: "数据字典",
    log: "系统日志",
    dept: "部门管理",
    config: "系统配置",
    file: "文件管理",
    examples: "示例中心",
    examplesQuickStart: "快速开始",
    examplesFormInput: "表单与输入",
    examplesContent: "内容编辑",
    examplesInteraction: "基础交互",
    examplesBusinessScaffold: "业务脚手架",
    examplesSecurityEngineering: "安全与工程",


创建 sys_dict_type 和 sys_dict_data 表

创建 magic-api 字典 CRUD 接口 (sys/dict/)

修正前端 dict.ts API 路径匹配后端

修正前端字典 API 路径

添加字典路由到 asyncRoutes + 数据库 sys_menu

为 asset/base/biz 模块创建前端页面和路由

重启后端、验证字典功能和资产管理页面


Great, `menu.dict` = "数据字典" already exists in zh-CN.ts! So the route `meta.title: "menu.dict"` will work with i18n.

Now I need to add the menu item to sys_menu in the database. Let me also check the English translations file.

Now let me:
1. Add menu item to sys_menu table
2. Then move on to the asset management issue


`menu.dict` 已存在。现在添加数据库菜单记录。


Bash

"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -h 192.168.2.1 -P 3307 -u root -pKoteladt@123 our_itam -e "
SET @sys_parent = '019e3091-0001-7000-8000-000000000001';
SET @new_id = REPLACE(UUID(), '-', '');

INSERT INTO sys_menu (id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, remark, create_time)
VALUES (@new_id, '字典管理', @sys_parent, 6, '/system/dict', '', 'C', '0', '0', 'system:dict:list', 'BookOutlined', '', NOW());

SELECT id, menu_name, path, perms, icon FROM sys_menu WHERE menu_name = '字典管理';
" 2>/dev/null

Insert dict menu item


id	menu_name	path	perms	icon
3dbeb75b51a811f1bf487eb6b3ae683b	�ֵ����	/system/dict	system:dict:list	BookOutlined


Dict menu item added. Now let me also:
1. Check the English i18n file for `menu.dict`
2. Move on to the asset management issue

Actually, the English check can wait. Let me focus on the asset management issue now.

## Asset Management Issue

The problem:
- Database `sys_menu` has entries for 资产管理 (asset), 基础数据 (base), 业务管理 (biz)
- Backend magic-api has endpoints for all these modules
- But the frontend has NO routes in asyncRoutes and NO view components

I need to:
1. Create view components for asset modules (device, license, accessory, consumable, depreciation, offering)
2. Create view components for base modules (category, location, manufacturer, model, supplier)
3. Create view components for biz modules (failure, stocktake, transaction, allocation, request)
4. Add routes to asyncRoutes

That's 16 view pages! This is a lot of work. Let me create a generic ProTable-based view component that can be reused, since all these pages follow the same pattern (list page with search, pagination, CRUD).

Actually, looking at the magic-api endpoints, each module has:
- page.ms — paginated list
- detail.ms — get single record
- save.ms — create/update
- delete.ms — soft delete

The typical page pattern is:
- ProTable with search toolbar
- Add/Edit modal
- Delete confirmation

Let me create a generic component approach. I'll create a `GenericCrudPage.vue` or create individual simple pages for each module.

Actually, since they all share the same structure but with different API endpoints and columns, let me create the most important ones first. The user specifically mentioned "资产管理" (asset management).

Let me create the 6 asset module pages:
1. Device (设备管理) — /asset/device
2. License (许可证管理) — /asset/license
3. Accessory (配件管理) — /asset/accessory
4. Consumable (耗材管理) — /asset/consumable
5. Depreciation (折旧管理) — /asset/depreciation
6. Offering (提供服务) — /asset/offering

Each page needs:
- ProTable with columns based on the database table
- Add/Edit modal
- API module

But I need to know the database table schemas for each. Let me check the database.

Actually, this is a LOT of work. Let me take a pragmatic approach:

1. Create API modules for each
2. Create a generic CRUD page that takes config
3. Create the routes

OR, better yet, since the backend uses a magic-api pattern with page/save/delete/detail, let me just create the pages one by one but focus on the most commonly used ones first.

Actually, let me check what the database table schemas look like to understand the columns I need.

Let me take a step back. The user's main ask is:
1. Dict table + page ← DONE
2. Asset management pages are missing ← need to fix

For the asset management, I'll create the individual pages. But it will be simpler if I create a reusable API pattern and create the views. This is a significant amount of work.

Let me start by creating the asset module pages. I'll do one as a pattern and then replicate.

But wait, let me check if I should create a separate API module for asset or put it all in one file. Looking at the existing pattern, each module has its own API file (user.ts, role.ts, dept.ts, etc.).

Let me create:
1. `src/api/asset.ts` — all asset API calls
2. `src/views/asset/device/index.vue`
3. `src/views/asset/license/index.vue`
4. etc.

Actually, to save time, let me create a simpler approach. I'll make a single CRUD page component and configure it for each asset type. Each page file will be thin, just importing the generic one with specific config.

Actually, that's overengineering it. Let me just create straightforward pages with ProTable, similar to how the user list page works.

Let me look at the existing user page for pattern reference.


TodoWrite

创建 sys_dict_type 和 sys_dict_data 表

创建 magic-api 字典 CRUD 接口 (sys/dict/)

修正前端字典 API 路径

修正前端 dict.ts API 路径匹配后端

添加字典路由到 asyncRoutes + 数据库 sys_menu

为 asset/base/biz 模块创建前端页面和路由

重启后端、验证字典功能和资产管理页面


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

创建 sys_dict_type 和 sys_dict_data 表

创建 magic-api 字典 CRUD 接口 (sys/dict/)

修正前端 dict.ts API 路径匹配后端

修正前端字典 API 路径

添加字典路由到 asyncRoutes + 数据库 sys_menu

为 asset/base/biz 模块创建前端页面和路由

重启后端、验证字典功能和资产管理页面

创建 sys_dict_type 和 sys_dict_data 表

创建 magic-api 字典 CRUD 接口 (sys/dict/)

修正前端 dict.ts API 路径匹配后端

修正前端字典 API 路径

添加字典路由到 asyncRoutes + 数据库 sys_menu

为 asset/base/biz 模块创建前端页面和路由

重启后端、验证字典功能和资产管理页面


Let me check the user list page for the pattern, then check the database table schemas for the asset tables, and then create the pages. Let me do this in parallel.


先看看现有页面的模式，以及资产相关数据库表结构。


Bash

"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -h 192.168.2.1 -P 3307 -u root -pKoteladt@123 our_itam -e "DESCRIBE itam_device; DESCRIBE itam_licenses; DESCRIBE itam_accessories; DESCRIBE itam_consumables; DESCRIBE itam_depreciations; DESCRIBE itam_offering;" 2>/dev/null

Check asset table schemas


1	<template>
2	  <div class="page-container">
3	    <ProTable
4	      ref="tableRef"
5	      :columns="columns"
6	      :request="fetchTableData"
7	      :toolbar="toolbarConfig"
8	      :search="{
9	        labelWidth: 6,
10	        defaultCollapsed: true,
11	      }"
12	      row-key="id"
13	    >
14	      <template #toolbar-actions>
15	        <a-space>
16	          <a-upload
17	            :show-upload-list="false"
18	            accept=".csv"
19	            :before-upload="handleImport"
20	          >
21	            <a-button> <UploadOutlined /> {{ $t("common.import") }} </a-button>
22	          </a-upload>
23	          <a-button @click="handleExport">
24	            <DownloadOutlined /> {{ $t("common.export") }}
25	          </a-button>
26	          <a-button
27	            type="primary"
28	            class="create-user-btn"
29	            @click="handleCreate"
30	          >
31	            <PlusOutlined /> {{ $t("user.createUser") }}
32	          </a-button>
33	        </a-space>
34	      </template>
35	    </ProTable>
36
37	    <a-modal
38	      v-model:open="modalVisible"
39	      :title="editingUserId ? $t('user.editUser') : $t('user.createUser')"
40	      :confirm-loading="submitting"
41	      width="760px"
42	      @ok="handleSubmit"
43	      @cancel="handleCancel"
44	    >
45	      <ProForm
46	        ref="formRef"
47	        :form-items="formItems"
48	        :initial-values="formData"
49	        :grid="{ cols: 2, gutter: 16 }"
50	        :layout="{ layout: 'vertical' }"
51	      />
52	    </a-modal>
53	  </div>
54	</template>
55
56	<script setup lang="ts">
57	import type { Role, User } from "@/types/auth";
58	import type { ProFormItem, ProTableColumn } from "@/types/pro";
59
60	import {
61	  PlusOutlined,
62	  EditOutlined,
63	  DeleteOutlined,
64	  UploadOutlined,
65	  DownloadOutlined,
66	} from "@antdv-next/icons";
67	import { message, Modal } from "antdv-next";
68	import { computed, markRaw, onMounted, ref } from "vue";
69
70	import { getRoleList } from "@/api/role";
71	import { createUser, deleteUser, getUserList, updateUser } from "@/api/user";
72	import ProForm from "@/components/Pro/ProForm/index.vue";
73	import ProTable from "@/components/Pro/ProTable/index.vue";
74	import { $t } from "@/locales";
75	import { exportToCSV, parseCSV } from "@/utils/export";
76
77	type UserFormValues = {
78	  username: string;
79	  realName: string;
80	  email: string;
81	  phone: string;
82	  gender: "male" | "female";
83	  status: "active" | "inactive";
84	  roleIds: string[];
85	  bio: string;
86	};
87
88	const tableRef = ref<{
89	  refresh: () => void;
90	  reload: () => void;
91	} | null>(null);
92	const formRef = ref<{
93	  validate: () => Promise<boolean>;
94	  getFieldsValue: () => UserFormValues;
95	} | null>(null);
96
97	const modalVisible = ref(false);
98	const submitting = ref(false);
99	const editingUserId = ref<string | null>(null);
100	const roleOptions = ref<Role[]>([]);

<template>
  <div class="page-container">
    <ProTable
      ref="tableRef"
      :columns="columns"
      :request="fetchTableData"
      :toolbar="toolbarConfig"
      :search="{
        labelWidth: 6,
        defaultCollapsed: true,
      }"
      row-key="id"
    >
      <template #toolbar-actions>
        <a-space>
          <a-upload
            :show-upload-list="false"
            accept=".csv"
            :before-upload="handleImport"
          >
            <a-button> <UploadOutlined /> {{ $t("common.import") }} </a-button>
          </a-upload>
          <a-button @click="handleExport">
            <DownloadOutlined /> {{ $t("common.export") }}
          </a-button>
          <a-button
            type="primary"
            class="create-user-btn"
            @click="handleCreate"
          >
            <PlusOutlined /> {{ $t("user.createUser") }}
          </a-button>
        </a-space>
      </template>
    </ProTable>

    <a-modal
      v-model:open="modalVisible"
      :title="editingUserId ? $t('user.editUser') : $t('user.createUser')"
      :confirm-loading="submitting"
      width="760px"
      @ok="handleSubmit"
      @cancel="handleCancel"
    >
      <ProForm
        ref="formRef"
        :form-items="formItems"
        :initial-values="formData"
        :grid="{ cols: 2, gutter: 16 }"
        :layout="{ layout: 'vertical' }"
      />
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import type { Role, User } from "@/types/auth";
import type { ProFormItem, ProTableColumn } from "@/types/pro";

import {
  PlusOutlined,
  EditOutlined,
  DeleteOutlined,
  UploadOutlined,
  DownloadOutlined,
} from "@antdv-next/icons";
import { message, Modal } from "antdv-next";
import { computed, markRaw, onMounted, ref } from "vue";

import { getRoleList } from "@/api/role";
import { createUser, deleteUser, getUserList, updateUser } from "@/api/user";
import ProForm from "@/components/Pro/ProForm/index.vue";
import ProTable from "@/components/Pro/ProTable/index.vue";
import { $t } from "@/locales";
import { exportToCSV, parseCSV } from "@/utils/export";

type UserFormValues = {
  username: string;
  realName: string;
  email: string;
  phone: string;
  gender: "male" | "female";
  status: "active" | "inactive";
  roleIds: string[];
  bio: string;
};

const tableRef = ref<{
  refresh: () => void;
  reload: () => void;
} | null>(null);
const formRef = ref<{
  validate: () => Promise<boolean>;
  getFieldsValue: () => UserFormValues;
} | null>(null);

const modalVisible = ref(false);
const submitting = ref(false);
const editingUserId = ref<string | null>(null);
const roleOptions = ref<Role[]>([]);


Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
model_id	char(36)	NO	MUL	NULL
category_id	char(36)	YES	MUL	NULL
assets_status	varchar(50)	NO	MUL	1
serial	varchar(255)	YES	MUL	NULL
device_no	varchar(100)	YES		NULL
name	varchar(255)	YES		NULL
image_url	varchar(500)	YES		NULL
purchase_date	date	YES		NULL
purchase_cost	decimal(18,2)	YES		NULL
warranty_date	date	YES		NULL
assigned_to	char(36)	YES	MUL	NULL
location_id	char(36)	YES	MUL	NULL
supplier_id	char(36)	YES	MUL	NULL
depreciation_id	char(36)	YES	MUL	NULL
description	text	YES		NULL
deleted	tinyint(1)	NO		0
create_time	datetime	NO		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL
Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
name	varchar(255)	NO	MUL	NULL
license_key	varchar(500)	NO		NULL
total_seats	bigint	NO		NULL
manufacturer_id	char(36)	YES	MUL	NULL
category_id	char(36)	YES	MUL	NULL
min_qty	bigint	YES		NULL
licensed_to_name	varchar(255)	YES		NULL
licensed_to_email	varchar(255)	YES		NULL
supplier_id	char(36)	YES		NULL
order_number	varchar(255)	YES		NULL
purchase_cost	decimal(18,2)	YES		NULL
purchase_date	datetime	YES		NULL
expiration_date	datetime	YES	MUL	NULL
termination_date	datetime	YES		NULL
notes	text	YES		NULL
deleted	tinyint(1)	NO		0
create_time	datetime	NO		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL
Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
name	varchar(255)	NO	MUL	NULL
asset_number	varchar(255)	YES		NULL
category_id	char(36)	NO	MUL	NULL
supplier_id	char(36)	YES	MUL	NULL
manufacturer_id	char(36)	YES	MUL	NULL
specifications	varchar(255)	YES		NULL
location_id	char(36)	YES	MUL	NULL
depreciation_id	char(36)	YES		NULL
quantity	int	NO		NULL
min_quantity	int	YES		NULL
purchase_date	date	YES		NULL
warranty_expiration_date	date	YES		NULL
purchase_cost	decimal(18,2)	YES		NULL
order_number	varchar(255)	YES		NULL
serial_number	varchar(255)	YES		NULL
description	text	YES		NULL
deleted	tinyint(1)	NO		0
create_time	datetime	NO		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL
Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
name	varchar(255)	NO	MUL	NULL
category_id	char(36)	NO	MUL	NULL
manufacturer_id	char(36)	YES	MUL	NULL
item_no	varchar(255)	YES		NULL
order_number	varchar(255)	YES		NULL
purchase_date	date	YES		NULL
purchase_cost	decimal(18,2)	YES		NULL
quantity	int	NO		NULL
min_quantity	int	YES		NULL
location_id	char(36)	YES	MUL	NULL
deleted	tinyint(1)	NO		0
create_time	datetime	NO		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL
Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
name	varchar(255)	NO		NULL
status	tinyint	YES		1
months	int	NO		NULL
floor_type	varchar(20)	NO		amount
floor_val	decimal(18,2)	NO		NULL
stages	json	YES		NULL
remark	varchar(500)	YES		NULL
deleted	tinyint(1)	NO		0
create_time	datetime	YES		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL
Field	Type	Null	Key	Default	Extra
id	char(36)	NO	PRI	NULL
name	varchar(255)	NO		NULL
supplier_id	char(36)	YES	MUL	NULL
service_number	varchar(255)	YES		NULL
start_date	datetime	YES		NULL
end_date	datetime	YES		NULL
cost	decimal(18,2)	YES		NULL
notes	text	YES		NULL
offering_status	varchar(50)	YES	MUL	active
deleted	tinyint(1)	NO		0
create_time	datetime	NO		CURRENT_TIMESTAMP	DEFAULT_GENERATED
update_time	datetime	YES		NULL
create_by	char(36)	YES		NULL
update_by	char(36)	YES		NULL


Now I understand the database schemas. This is a LOT of pages to create. Let me take a pragmatic approach and create a reusable page pattern. I'll create:

1. A generic asset API module (`src/api/asset.ts`) that handles all asset CRUD
2. Individual page files that are lightweight configurations

Actually, let me think about the most efficient approach. Each page needs:
- ProTable with columns
- Search functionality
- Add/Edit modal with form
- Delete confirmation
- API calls

The pattern is quite standard. Let me create the pages one by one, but make each one fairly simple. Since they all follow exactly the same pattern, I can write them quickly.

Let me first create the API module, then create the 6 asset pages, then the routes.

Actually, to be faster, let me create a single shared module for all asset types and just create the view pages.

Let me start by creating the asset API module and the first page (device), then I'll replicate the pattern for others.


资产模块较多，让我高效地创建它们。先创建 API 模块。


Write

import { request } from "@/utils/request";

// Generic CRUD helper for asset/base/biz modules
function makeAssetApi(prefix: string) {
  return {
    page(params: Record<string, unknown>) {
      return request.post(`${prefix}/page`, params);
    },
    detail(id: string) {
      return request.get(`${prefix}/detail`, { params: { id } });
    },
    save(data: Record<string, unknown>) {
      return request.post(`${prefix}/save`, data);
    },
    del(id: string) {
      return request.post(`${prefix}/delete`, { id });
    },
  };
}

export const deviceApi = makeAssetApi("/asset/device");
export const licenseApi = makeAssetApi("/asset/license");
export const accessoryApi = makeAssetApi("/asset/accessory");
export const consumableApi = makeAssetApi("/asset/consumable");
export const depreciationApi = makeAssetApi("/asset/depreciation");
export const offeringApi = makeAssetApi("/asset/offering");

// Base module
export const categoryApi = makeAssetApi("/base/category");
export const locationApi = makeAssetApi("/base/location");
export const manufacturerApi = makeAssetApi("/base/manufacturer");
export const modelApi = makeAssetApi("/base/model");
export const supplierApi = makeAssetApi("/base/supplier");

// Biz module
export const failureApi = makeAssetApi("/biz/failure");
export const stocktakeApi = makeAssetApi("/biz/stocktake");
export const allocationApi = makeAssetApi("/biz/allocation");
export const transactionApi = makeAssetApi("/biz/transaction");
export const requestApi = makeAssetApi("/biz/request");


File created successfully at: c:/Workspace/Project/tansci/antdv-next-admin/src/api/asset.ts (file state is current in your context — no need to Read it back)

import { request } from "@/utils/request";

// Generic CRUD helper for asset/base/biz modules
function makeAssetApi(prefix: string) {
  return {
    page(params: Record<string, unknown>) {
      return request.post(`${prefix}/page`, params);
    },
    detail(id: string) {
      return request.get(`${prefix}/detail`, { params: { id } });
    },
    save(data: Record<string, unknown>) {
      return request.post(`${prefix}/save`, data);
    },
    del(id: string) {
      return request.post(`${prefix}/delete`, { id });
    },
  };
}

export const deviceApi = makeAssetApi("/asset/device");
export const licenseApi = makeAssetApi("/asset/license");
export const accessoryApi = makeAssetApi("/asset/accessory");
export const consumableApi = makeAssetApi("/asset/consumable");
export const depreciationApi = makeAssetApi("/asset/depreciation");
export const offeringApi = makeAssetApi("/asset/offering");

// Base module
export const categoryApi = makeAssetApi("/base/category");
export const locationApi = makeAssetApi("/base/location");
export const manufacturerApi = makeAssetApi("/base/manufacturer");
export const modelApi = makeAssetApi("/base/model");
export const supplierApi = makeAssetApi("/base/supplier");

// Biz module
export const failureApi = makeAssetApi("/biz/failure");
export const stocktakeApi = makeAssetApi("/biz/stocktake");
export const allocationApi = makeAssetApi("/biz/allocation");
export const transactionApi = makeAssetApi("/biz/transaction");
export const requestApi = makeAssetApi("/biz/request");


Great, now let me create the view pages. Given the volume, I'll create a reasonable set of asset pages first. The 6 asset sub-modules are the priority since the user mentioned "资产管理".

For asset pages, I'll create a generic ProTable-based pattern for each. The pages will be similar - a ProTable component with search, add/edit modal, and delete functionality.

Let me create the directory structure and the first few pages. I'll create each as a compact ProTable-based page.

For efficiency, let me create the most important pages:
1. device/index.vue
2. license/index.vue
3. accessory/index.vue
4. consumable/index.vue
5. depreciation/index.vue
6. offering/index.vue

And add all routes to asyncRoutes.

Let me start creating the directories and pages.


现在创建资产管理页面。先创建设备管理页面作为模板，其他模块参照。


Bash

mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/device && mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/license && mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/accessory && mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/consumable && mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/depreciation && mkdir -p c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/offering && echo "OK"

Create asset view directories


OK


Now let me create the asset management pages. I'll use a compact but functional pattern with ProTable for each. Each page will have:
- ProTable with columns matching the database schema
- Search functionality
- Add/Edit modal with basic form
- Delete confirmation

Let me create them all at once.


Write

<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '设备管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增设备</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑设备' : '新增设备'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="设备名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="设备编号"><a-input v-model:value="form.device_no" /></a-form-item>
        <a-form-item label="序列号"><a-input v-model:value="form.serial" /></a-form-item>
        <a-form-item label="资产状态"><a-input v-model:value="form.assets_status" placeholder="1=使用中" /></a-form-item>
        <a-form-item label="采购日期"><a-input v-model:value="form.purchase_date" type="date" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
        <a-form-item label="描述"><a-textarea v-model:value="form.description" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { deviceApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false);
const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "设备名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "设备编号", dataIndex: "device_no", key: "device_no" },
  { title: "序列号", dataIndex: "serial", key: "serial" },
  { title: "资产状态", dataIndex: "assets_status", key: "assets_status" },
  { title: "采购日期", dataIndex: "purchase_date", key: "purchase_date" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "创建时间", dataIndex: "create_time", key: "create_time" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  try {
    const res: any = await deviceApi.page({ page: params.current, pageSize: params.pageSize, keyword: params.keyword });
    return { data: res.list || [], total: res.total || 0, success: true };
  } catch { return { data: [], total: 0, success: false }; }
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (record: Record<string, unknown>) => { isEdit.value = true; form.value = { ...record }; visible.value = true; };
const handleDelete = async (id: string) => { await deviceApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => {
  if (isEdit.value) await deviceApi.save({ ...form.value });
  else await deviceApi.save(form.value);
  message.success(isEdit.value ? "更新成功" : "创建成功");
  visible.value = false;
};
</script>


<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '许可证管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑许可证' : '新增许可证'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="许可证密钥" required><a-input v-model:value="form.license_key" /></a-form-item>
        <a-form-item label="席位总数"><a-input-number v-model:value="form.total_seats" style="width:100%" /></a-form-item>
        <a-form-item label="到期日期"><a-input v-model:value="form.expiration_date" type="date" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
        <a-form-item label="备注"><a-textarea v-model:value="form.notes" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { licenseApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "许可证密钥", dataIndex: "license_key", key: "license_key" },
  { title: "席位总数", dataIndex: "total_seats", key: "total_seats" },
  { title: "到期日期", dataIndex: "expiration_date", key: "expiration_date" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await licenseApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await licenseApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await licenseApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '配件管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑配件' : '新增配件'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="资产编号"><a-input v-model:value="form.asset_number" /></a-form-item>
        <a-form-item label="数量"><a-input-number v-model:value="form.quantity" style="width:100%" /></a-form-item>
        <a-form-item label="规格"><a-input v-model:value="form.specifications" /></a-form-item>
        <a-form-item label="序列号"><a-input v-model:value="form.serial_number" /></a-form-item>
        <a-form-item label="描述"><a-textarea v-model:value="form.description" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { accessoryApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "资产编号", dataIndex: "asset_number", key: "asset_number" },
  { title: "数量", dataIndex: "quantity", key: "quantity" },
  { title: "规格", dataIndex: "specifications", key: "specifications" },
  { title: "序列号", dataIndex: "serial_number", key: "serial_number" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await accessoryApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await accessoryApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await accessoryApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '耗材管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑耗材' : '新增耗材'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="物料编号"><a-input v-model:value="form.item_no" /></a-form-item>
        <a-form-item label="数量"><a-input-number v-model:value="form.quantity" style="width:100%" /></a-form-item>
        <a-form-item label="最小库存"><a-input-number v-model:value="form.min_quantity" style="width:100%" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { consumableApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "物料编号", dataIndex: "item_no", key: "item_no" },
  { title: "数量", dataIndex: "quantity", key: "quantity" },
  { title: "最小库存", dataIndex: "min_quantity", key: "min_quantity" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await consumableApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await consumableApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await consumableApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '折旧管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑折旧规则' : '新增折旧规则'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="折旧月数" required><a-input-number v-model:value="form.months" style="width:100%" /></a-form-item>
        <a-form-item label="折旧算法"><a-select v-model:value="form.floor_type" :options="[{label:'按金额',value:'amount'},{label:'按百分比',value:'percent'}]" style="width:100%" /></a-form-item>
        <a-form-item label="折旧值"><a-input-number v-model:value="form.floor_val" style="width:100%" /></a-form-item>
        <a-form-item label="备注"><a-textarea v-model:value="form.remark" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { depreciationApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "折旧月数", dataIndex: "months", key: "months" },
  { title: "折旧算法", dataIndex: "floor_type", key: "floor_type" },
  { title: "折旧值", dataIndex: "floor_val", key: "floor_val" },
  { title: "备注", dataIndex: "remark", key: "remark" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await depreciationApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await depreciationApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await depreciationApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '提供服务' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑服务' : '新增服务'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="服务编号"><a-input v-model:value="form.service_number" /></a-form-item>
        <a-form-item label="开始日期"><a-input v-model:value="form.start_date" type="date" /></a-form-item>
        <a-form-item label="结束日期"><a-input v-model:value="form.end_date" type="date" /></a-form-item>
        <a-form-item label="费用"><a-input-number v-model:value="form.cost" style="width:100%" /></a-form-item>
        <a-form-item label="备注"><a-textarea v-model:value="form.notes" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { offeringApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "服务编号", dataIndex: "service_number", key: "service_number" },
  { title: "开始日期", dataIndex: "start_date", key: "start_date" },
  { title: "结束日期", dataIndex: "end_date", key: "end_date" },
  { title: "费用", dataIndex: "cost", key: "cost" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await offeringApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await offeringApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await offeringApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


File created successfully at: c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/device/index.vue (file state is current in your context — no need to Read it back)

<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '设备管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增设备</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑设备' : '新增设备'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="设备名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="设备编号"><a-input v-model:value="form.device_no" /></a-form-item>
        <a-form-item label="序列号"><a-input v-model:value="form.serial" /></a-form-item>
        <a-form-item label="资产状态"><a-input v-model:value="form.assets_status" placeholder="1=使用中" /></a-form-item>
        <a-form-item label="采购日期"><a-input v-model:value="form.purchase_date" type="date" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
        <a-form-item label="描述"><a-textarea v-model:value="form.description" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { deviceApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false);
const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "设备名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "设备编号", dataIndex: "device_no", key: "device_no" },
  { title: "序列号", dataIndex: "serial", key: "serial" },
  { title: "资产状态", dataIndex: "assets_status", key: "assets_status" },
  { title: "采购日期", dataIndex: "purchase_date", key: "purchase_date" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "创建时间", dataIndex: "create_time", key: "create_time" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  try {
    const res: any = await deviceApi.page({ page: params.current, pageSize: params.pageSize, keyword: params.keyword });
    return { data: res.list || [], total: res.total || 0, success: true };
  } catch { return { data: [], total: 0, success: false }; }
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (record: Record<string, unknown>) => { isEdit.value = true; form.value = { ...record }; visible.value = true; };
const handleDelete = async (id: string) => { await deviceApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => {
  if (isEdit.value) await deviceApi.save({ ...form.value });
  else await deviceApi.save(form.value);
  message.success(isEdit.value ? "更新成功" : "创建成功");
  visible.value = false;
};
</script>


File created successfully at: c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/license/index.vue (file state is current in your context — no need to Read it back)

<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '许可证管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑许可证' : '新增许可证'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="许可证密钥" required><a-input v-model:value="form.license_key" /></a-form-item>
        <a-form-item label="席位总数"><a-input-number v-model:value="form.total_seats" style="width:100%" /></a-form-item>
        <a-form-item label="到期日期"><a-input v-model:value="form.expiration_date" type="date" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
        <a-form-item label="备注"><a-textarea v-model:value="form.notes" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { licenseApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "许可证密钥", dataIndex: "license_key", key: "license_key" },
  { title: "席位总数", dataIndex: "total_seats", key: "total_seats" },
  { title: "到期日期", dataIndex: "expiration_date", key: "expiration_date" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await licenseApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await licenseApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await licenseApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


File created successfully at: c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/accessory/index.vue (file state is current in your context — no need to Read it back)

<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '配件管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑配件' : '新增配件'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="资产编号"><a-input v-model:value="form.asset_number" /></a-form-item>
        <a-form-item label="数量"><a-input-number v-model:value="form.quantity" style="width:100%" /></a-form-item>
        <a-form-item label="规格"><a-input v-model:value="form.specifications" /></a-form-item>
        <a-form-item label="序列号"><a-input v-model:value="form.serial_number" /></a-form-item>
        <a-form-item label="描述"><a-textarea v-model:value="form.description" :rows="3" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { accessoryApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "资产编号", dataIndex: "asset_number", key: "asset_number" },
  { title: "数量", dataIndex: "quantity", key: "quantity" },
  { title: "规格", dataIndex: "specifications", key: "specifications" },
  { title: "序列号", dataIndex: "serial_number", key: "serial_number" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await accessoryApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await accessoryApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await accessoryApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>


File created successfully at: c:/Workspace/Project/tansci/antdv-next-admin/src/views/asset/consumable/index.vue (file state is current in your context — no need to Read it back)

<template>
  <div class="page-container">
    <ProTable :columns="columns" :request="loadData" :toolbar="{ title: '耗材管理' }">
      <template #toolbar-actions>
        <a-button type="primary" @click="handleAdd"><PlusOutlined /> 新增</a-button>
      </template>
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'action'">
          <a-space :size="4">
            <a-button type="link" size="small" @click="handleEdit(record)"><EditOutlined /> 编辑</a-button>
            <a-popconfirm title="确认删除？" @confirm="handleDelete(record.id)">
              <a-button type="link" size="small" danger><DeleteOutlined /> 删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </template>
    </ProTable>

    <a-modal v-model:open="visible" :title="isEdit ? '编辑耗材' : '新增耗材'" @ok="handleSubmit" width="640px">
      <a-form :model="form" :label-col="{ span: 6 }">
        <a-form-item label="名称" required><a-input v-model:value="form.name" /></a-form-item>
        <a-form-item label="物料编号"><a-input v-model:value="form.item_no" /></a-form-item>
        <a-form-item label="数量"><a-input-number v-model:value="form.quantity" style="width:100%" /></a-form-item>
        <a-form-item label="最小库存"><a-input-number v-model:value="form.min_quantity" style="width:100%" /></a-form-item>
        <a-form-item label="采购价格"><a-input-number v-model:value="form.purchase_cost" style="width:100%" /></a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@antdv-next/icons";
import { message } from "antdv-next";
import { ref } from "vue";
import { consumableApi } from "@/api/asset";
import ProTable from "@/components/Pro/ProTable/index.vue";
import type { ProTableColumn } from "@/types/pro";

const visible = ref(false); const isEdit = ref(false);
const form = ref<Record<string, unknown>>({});

const columns: ProTableColumn[] = [
  { title: "名称", dataIndex: "name", key: "name", search: true, searchType: "input" },
  { title: "物料编号", dataIndex: "item_no", key: "item_no" },
  { title: "数量", dataIndex: "quantity", key: "quantity" },
  { title: "最小库存", dataIndex: "min_quantity", key: "min_quantity" },
  { title: "采购价格", dataIndex: "purchase_cost", key: "purchase_cost" },
  { title: "操作", dataIndex: "action", key: "action", width: 150, fixed: "right" as const },
];

const loadData = async (params: Record<string, unknown>) => {
  const res: any = await consumableApi.page({ page: params.current, pageSize: params.pageSize });
  return { data: res.list || [], total: res.total || 0, success: true };
};

const handleAdd = () => { isEdit.value = false; form.value = {}; visible.value = true; };
const handleEdit = (r: Record<string, unknown>) => { isEdit.value = true; form.value = { ...r }; visible.value = true; };
const handleDelete = async (id: string) => { await consumableApi.del(id); message.success("删除成功"); };
const handleSubmit = async () => { await consumableApi.save(form.value); message.success(isEdit.value ? "更新成功" : "创建成功"); visible.value = false; };
</script>