zhangwei
2025-06-26 f397a6cfd118ae36022576374ed4a1fd9e15448d
'登录注册完善'
11个文件已修改
6个文件已添加
1474 ■■■■■ 已修改文件
src/api/item/index.ts 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/util.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/home.ts 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/item.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/remaining.ts 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/utils.ts 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/auth.ts 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/commonFunction.ts 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/formatTime.ts 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/http/index.ts 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/index.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/item/component/editDialog.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/item/index.vue 506 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
types/index.d.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/item/index.ts
New file
@@ -0,0 +1,148 @@
/**
 * (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
 *
 * 注册api接口集合
 * @method login 登录
 */
import { http } from "@/utils/http";
import { baseUrlApi } from "../util";
type Result = {
  success: boolean;
  data: Array<any>;
};
// 获取行政区域列表
export const getRegionList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getRegionList")
  );
};
// 获取开标方式集合
export const getKaibiaofangshiList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getKaibiaofangshiList")
  );
};
// 获取是否枚举集合
export const getYesNoEnumList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getYesNoEnumList")
  );
};
// 获取复合体投标集合
export const getFuhetitoubiaoList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getFuhetitoubiaoList")
  );
};
// 获取定标规则集合
export const getDingbiaoguizeList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getDingbiaoguizeList")
  );
};
// 获取代码类型集合
export const getDaimaleixingList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getDaimaleixingList")
  );
};
// 获取采购方式集合
export const getCaigoufangshiList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getCaigoufangshiList")
  );
};
// 获取行业品目集合
export const getHangyepingmuList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getHangyepingmuList")
  );
};
// 获取项目进度集合
export const getOrderStatusList = () => {
  return http.request<Result>(
    "get",
    baseUrlApi("/api/tenderOrder/getOrderStatusList")
  );
};
// 获取非政府订单处理详情
export const getTenderOrderDetail = () => {
  return http.request<Result>("get", baseUrlApi("/api/tenderOrder/detail"));
};
// 下载非政府订单处理数据导入模板
export const importFZF = () => {
  return http.request<Result>("get", baseUrlApi("/api/tenderOrder/import"));
};
// 首页查询非政府订单处理
export const shouyeOrder = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/shouyeOrder"), {
    data
  });
};
// 招标代理分页查询非政府订单处理
export const zhaobiaoPageOrder = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/page"), { data });
};
// 采购代理人增加非政府订单处理
export const caigourenAdd = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/add"), { data });
};
// 采购代理人更新非政府订单处理
export const caigourenUpdate = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/update"), { data });
};
// 采购代理人更新非政府订单质疑
export const caigourenUpdateZhiyi = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/updateZhiyi"), {
    data
  });
};
// 采购代理人更新非政府订单投诉
export const caigourenUpdateTousu = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/updateTousu"), {
    data
  });
};
// 采购代理人删除非政府订单处理
export const caigourenDelete = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/delete"), { data });
};
// 采购代理人批量删除非政府订单处理
export const caigourenBatchDelete = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/batchDelete"), {
    data
  });
};
// 导出非政府订单处理记录
export const exportFZF = (data?: object) => {
  return http.request("post", baseUrlApi("/api/tenderOrder/export"), { data });
};
src/api/util.ts
@@ -1,4 +1,4 @@
export const baseUrlApi = (url: string) =>
  process.env.NODE_ENV === "development"
    ? `/api${url}`
    : `http://192.168.0.36:5005${url}`;
    : `http://192.168.0.31:5005${url}`;
src/router/index.ts
@@ -106,8 +106,9 @@
}
/** 路由白名单 */
// const whiteList = ["/login"];
const whiteList = [];
const whiteList = ["/login"];
const noLoginList = ["/index", "/register", "/registersucess"];
// const whiteList = [];
const { VITE_HIDE_HOME } = import.meta.env;
@@ -141,7 +142,7 @@
    }
    // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
    if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
      next({ path: "/error/404" });
      next({ path: "/index" });
    }
    if (_from?.name) {
      // name为超链接
@@ -194,10 +195,15 @@
  } else {
    if (to.path !== "/login") {
      if (whiteList.indexOf(to.path) !== -1) {
        console.log(to.path, "====1", noLoginList.indexOf(to.path));
        next();
      } else {
        removeToken();
        next({ path: "/login" });
        // if (noLoginList.indexOf(to.path) == -1) {
        next();
        // } else {
        //   removeToken();
        //   next({ path: "/login" });
        // }
      }
    } else {
      next();
src/router/modules/home.ts
@@ -2,13 +2,13 @@
const Layout = () => import("@/layout/index.vue");
export default {
  path: "/welcome",
  name: "Welcome",
  path: "/",
  name: "Home",
  component: Layout,
  // redirect: "/welcome",
  redirect: "/welcome",
  meta: {
    icon: "ep/home-filled",
    title: "主页",
    title: "首页",
    rank: 0
  },
  children: [
@@ -17,7 +17,7 @@
      name: "Welcome",
      component: () => import("@/views/welcome/index.vue"),
      meta: {
        title: "主页",
        title: "首页",
        showLink: VITE_HIDE_HOME === "true" ? false : true
      }
    }
src/router/modules/item.ts
New file
@@ -0,0 +1,12 @@
export default {
  path: "/item",
  component: () => import("@/views/item/index.vue"),
  name: "item",
  // redirect: "/error/403",
  meta: {
    icon: "ri/information-line",
    // showLink: false,
    title: "项目管理",
    rank: 9
  }
} satisfies RouteConfigsTable;
src/router/modules/remaining.ts
@@ -1,19 +1,19 @@
const Layout = () => import("@/layout/index.vue");
export default [
  // {
  //   path: "/",
  //   name: "Main",
  //   redirect: "/home",
  //   meta: {
  //     title: "首页",
  //     showLink: false,
  //     rank: 101
  //   }
  // },
  {
    path: "/",
    path: "/index",
    name: "Main",
    redirect: "/home",
    meta: {
      title: "首页",
      showLink: false,
      rank: 101
    }
  },
  {
    path: "/home",
    name: "Home",
    component: () => import("@/views/home/index.vue"),
    meta: {
      title: "首页",
src/router/utils.ts
@@ -33,7 +33,7 @@
  const { name, path, parentId, meta } = routeInfo;
  return isAllEmpty(parentId)
    ? isAllEmpty(meta?.rank) ||
      (meta?.rank === 0 && name !== "Welcome" && path !== "/welcome")
      (meta?.rank === 0 && name !== "Home" && path !== "/")
      ? true
      : false
    : false;
@@ -171,7 +171,7 @@
          if (!router.hasRoute(v?.name)) router.addRoute(v);
          const flattenRouters: any = router
            .getRoutes()
            .find(n => n.path === "/welcome");
            .find(n => n.path === "/");
          // 保持router.options.routes[0].children与path为"/"的children一致,防止数据不一致导致异常
          flattenRouters.children = router.options.routes[0].children;
          router.addRoute(flattenRouters);
@@ -205,8 +205,8 @@
    } else {
      return new Promise(resolve => {
        getAsyncRoutes().then(({ data }) => {
          // handleAsyncRoutes(cloneDeep(data));
          // storageLocal().setItem(key, data);
          handleAsyncRoutes(cloneDeep(data));
          storageLocal().setItem(key, data);
          resolve(router);
        });
      });
@@ -214,7 +214,7 @@
  } else {
    return new Promise(resolve => {
      getAsyncRoutes().then(({ data }) => {
        // handleAsyncRoutes(cloneDeep(data));
        handleAsyncRoutes(cloneDeep(data));
        resolve(router);
      });
    });
@@ -249,7 +249,7 @@
  if (routesList?.length === 0) return routesList;
  const newRoutesList: RouteRecordRaw[] = [];
  routesList.forEach((v: RouteRecordRaw) => {
    if (v.path === "/welcome") {
    if (v.path === "/") {
      newRoutesList.push({
        component: v.component,
        name: v.name,
@@ -388,6 +388,7 @@
  );
  tag && useMultiTagsStoreHook().handleTags("push", topMenu);
  console.log(topMenu, "topMenu");
  return topMenu;
}
src/store/modules/user.ts
@@ -68,7 +68,9 @@
      return new Promise<UserResult>((resolve, reject) => {
        getLogin(data)
          .then(data => {
            if (data?.success) setToken(data.data);
            console.log(data, "这是什么?");
            if (data?.code == 200) setToken(data.result);
            resolve(data);
          })
          .catch(error => {
src/utils/auth.ts
@@ -16,7 +16,7 @@
  /** 昵称 */
  nickname?: string;
  /** 当前登录用户的角色 */
  roles?: Array<string>;
  exRoles?: Array<string>;
  /** 当前登录用户的按钮级别权限 */
  permissions?: Array<string>;
}
@@ -68,11 +68,11 @@
      : {}
  );
  function setUserKey({ avatar, username, nickname, roles, permissions }) {
  function setUserKey({ avatar, username, nickname, exRoles, permissions }) {
    useUserStoreHook().SET_AVATAR(avatar);
    useUserStoreHook().SET_USERNAME(username);
    useUserStoreHook().SET_NICKNAME(nickname);
    useUserStoreHook().SET_ROLES(roles);
    useUserStoreHook().SET_ROLES(exRoles);
    useUserStoreHook().SET_PERMS(permissions);
    storageLocal().setItem(userKey, {
      refreshToken,
@@ -80,18 +80,18 @@
      avatar,
      username,
      nickname,
      roles,
      exRoles,
      permissions
    });
  }
  if (data.username && data.roles) {
    const { username, roles } = data;
  if (data.exRoles) {
    const { username, exRoles } = data;
    setUserKey({
      avatar: data?.avatar ?? "",
      username,
      nickname: data?.nickname ?? "",
      roles,
      exRoles,
      permissions: data?.permissions ?? []
    });
  } else {
@@ -101,15 +101,15 @@
      storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "";
    const nickname =
      storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
    const roles =
      storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
    const exRoles =
      storageLocal().getItem<DataInfo<number>>(userKey)?.exRoles ?? [];
    const permissions =
      storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
    setUserKey({
      avatar,
      username,
      nickname,
      roles,
      exRoles,
      permissions
    });
  }
src/utils/commonFunction.ts
New file
@@ -0,0 +1,138 @@
// 通用函数
// import useClipboard from "vue-clipboard3";
// import { ElMessage } from "element-plus";
import { formatDate } from "@/utils/formatTime";
export default function () {
  // const { toClipboard } = useClipboard();
  // 百分比格式化
  const percentFormat = (
    row: EmptyArrayType,
    column: number,
    cellValue: string
  ) => {
    return cellValue ? `${cellValue}%` : "-";
  };
  // 列表日期时间格式化
  const dateFormatYMD = (
    row: EmptyArrayType,
    column: number,
    cellValue: string
  ) => {
    if (!cellValue) return "-";
    return formatDate(new Date(cellValue), "YYYY-mm-dd");
  };
  // 列表日期时间格式化
  const dateFormatYMDHMS = (
    row: EmptyArrayType,
    column: number,
    cellValue: string
  ) => {
    if (!cellValue) return "-";
    return formatDate(new Date(cellValue), "YYYY-mm-dd HH:MM:SS");
  };
  // 列表日期时间格式化
  const dateFormatHMS = (
    row: EmptyArrayType,
    column: number,
    cellValue: string
  ) => {
    if (!cellValue) return "-";
    let time = 0;
    if (typeof row === "number") time = row;
    if (typeof cellValue === "number") time = cellValue;
    return formatDate(new Date(time * 1000), "HH:MM:SS");
  };
  // 小数格式化
  const scaleFormat = (value: string = "0", scale: number = 4) => {
    return Number.parseFloat(value).toFixed(scale);
  };
  // 小数格式化
  const scale2Format = (value: string = "0") => {
    return Number.parseFloat(value).toFixed(2);
  };
  // 千分符,默认保留两位小数
  const groupSeparator = (value: number, minimumFractionDigits: number = 2) => {
    return value.toLocaleString("en-US", {
      minimumFractionDigits: minimumFractionDigits,
      maximumFractionDigits: 2
    });
  };
  /**
   * 删除字符串首尾指定字符
   * @param Str 源字符
   * @param char 去除的指定字符
   * @param type 类型,右边或左边,为空是替换首尾
   */
  const trimChar = (Str: string, char: string, type: string) => {
    if (char) {
      if (type == "left") {
        return Str.replace(new RegExp("^\\" + char + "+", "g"), "");
      } else if (type == "right") {
        return Str.replace(new RegExp("\\" + char + "+$", "g"), "");
      }
      return Str.replace(
        new RegExp("^\\" + char + "+|\\" + char + "+$", "g"),
        ""
      );
    }
    return Str.replace(/^\s+|\s+$/g, "");
  };
  // 点击复制文本
  // const copyText = (text: string) => {
  //   return new Promise((resolve, reject) => {
  //     try {
  //       //复制
  //       toClipboard(text);
  //       //下面可以设置复制成功的提示框等操作
  //       ElMessage.success("复制成功");
  //       resolve(text);
  //     } catch (e) {
  //       //复制失败
  //       ElMessage.error("复制失败");
  //       reject(e);
  //     }
  //   });
  // };
  // 去掉Html标签(取前面5个字符)
  const removeHtmlSub = (value: string) => {
    const str = value.replace(/<[^>]+>/g, "");
    if (str.length > 50) return str.substring(0, 50) + "......";
    else return str;
  };
  // 去掉Html标签
  const removeHtml = (value: string) => {
    return value.replace(/<[^>]+>/g, "");
  };
  // 获取枚举描述
  const getEnumDesc = (key: any, lstEnum: any) => {
    return lstEnum.find((x: any) => x.value == key)?.describe;
  };
  // 追加query参数到url
  const appendQueryParams = (url: string, params: { [key: string]: any }) => {
    if (!params || Object.keys(params).length == 0) return url;
    const queryString = Object.keys(params)
      .map(
        key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`
      )
      .join("&");
    return `${url}${url.includes("?") ? "&" : "?"}${queryString}`;
  };
  return {
    percentFormat,
    dateFormatYMD,
    dateFormatYMDHMS,
    dateFormatHMS,
    scaleFormat,
    scale2Format,
    groupSeparator,
    copyText,
    removeHtmlSub,
    removeHtml,
    getEnumDesc,
    appendQueryParams,
    trimChar
  };
}
src/utils/formatTime.ts
New file
@@ -0,0 +1,207 @@
/**
 * 时间日期转换
 * @param date 当前时间,new Date() 格式
 * @param format 需要转换的时间格式字符串
 * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
 * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
 * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
 * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
 * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
 * @returns 返回拼接后的时间字符串
 */
export function formatDate(date: Date, format: string): string {
  const we = date.getDay(); // 星期
  const z = getWeek(date); // 周
  const qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
  const opt: { [key: string]: string } = {
    "Y+": date.getFullYear().toString(), // 年
    "m+": (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
    "d+": date.getDate().toString(), // 日
    "H+": date.getHours().toString(), // 时
    "M+": date.getMinutes().toString(), // 分
    "S+": date.getSeconds().toString(), // 秒
    "q+": qut // 季度
  };
  // 中文数字 (星期)
  const week: { [key: string]: string } = {
    "0": "日",
    "1": "一",
    "2": "二",
    "3": "三",
    "4": "四",
    "5": "五",
    "6": "六"
  };
  // 中文数字(季度)
  const quarter: { [key: string]: string } = {
    "1": "一",
    "2": "二",
    "3": "三",
    "4": "四"
  };
  if (/(W+)/.test(format))
    format = format.replace(
      RegExp.$1,
      RegExp.$1.length > 1
        ? RegExp.$1.length > 2
          ? "星期" + week[we]
          : "周" + week[we]
        : week[we]
    );
  if (/(Q+)/.test(format))
    format = format.replace(
      RegExp.$1,
      RegExp.$1.length == 4 ? "第" + quarter[qut] + "季度" : quarter[qut]
    );
  if (/(Z+)/.test(format))
    format = format.replace(
      RegExp.$1,
      RegExp.$1.length == 3 ? "第" + z + "周" : z + ""
    );
  for (const k in opt) {
    const r = new RegExp("(" + k + ")").exec(format);
    // 若输入的长度不为1,则前面补零
    if (r)
      format = format.replace(
        r[1],
        RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, "0")
      );
  }
  return format;
}
/**
 * 获取当前日期是第几周
 * @param dateTime 当前传入的日期值
 * @returns 返回第几周数字值
 */
export function getWeek(dateTime: Date): number {
  const temptTime = new Date(dateTime.getTime());
  // 周几
  const weekday = temptTime.getDay() || 7;
  // 周1+5天=周六
  temptTime.setDate(temptTime.getDate() - weekday + 1 + 5);
  let firstDay = new Date(temptTime.getFullYear(), 0, 1);
  const dayOfWeek = firstDay.getDay();
  let spendDay = 1;
  if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1;
  firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay);
  const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000);
  const result = Math.ceil(d / 7);
  return result;
}
/**
 * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
 * @param param 当前时间,new Date() 格式或者字符串时间格式
 * @param format 需要转换的时间格式字符串
 * @description param 10秒:  10 * 1000
 * @description param 1分:   60 * 1000
 * @description param 1小时: 60 * 60 * 1000
 * @description param 24小时:60 * 60 * 24 * 1000
 * @description param 3天:   60 * 60* 24 * 1000 * 3
 * @returns 返回拼接后的时间字符串
 */
export function formatPast(
  param: string | Date,
  format: string = "YYYY-mm-dd"
): string {
  // 传入格式处理、存储转换值
  let t: any, s: number;
  // 获取js 时间戳
  let time: number = new Date().getTime();
  // 是否是对象
  typeof param === "string" || "object"
    ? (t = new Date(param).getTime())
    : (t = param);
  // 当前时间戳 - 传入时间戳
  time = Number.parseInt(`${time - t}`);
  if (time < 10000) {
    // 10秒内
    return "刚刚";
  } else if (time < 60000 && time >= 10000) {
    // 超过10秒少于1分钟内
    s = Math.floor(time / 1000);
    return `${s}秒前`;
  } else if (time < 3600000 && time >= 60000) {
    // 超过1分钟少于1小时
    s = Math.floor(time / 60000);
    return `${s}分钟前`;
  } else if (time < 86400000 && time >= 3600000) {
    // 超过1小时少于24小时
    s = Math.floor(time / 3600000);
    return `${s}小时前`;
  } else if (time < 259200000 && time >= 86400000) {
    // 超过1天少于3天内
    s = Math.floor(time / 86400000);
    return `${s}天前`;
  } else {
    // 超过3天
    const date =
      typeof param === "string" || "object" ? new Date(param) : param;
    return formatDate(date, format);
  }
}
/**
 * 时间问候语
 * @param param 当前时间,new Date() 格式
 * @description param 调用 `formatAxis(new Date())` 输出 `上午好`
 * @returns 返回拼接后的时间字符串
 */
export function formatAxis(param: Date): string {
  const hour: number = new Date(param).getHours();
  if (hour < 6) return "凌晨好";
  else if (hour < 9) return "早上好";
  else if (hour < 12) return "上午好";
  else if (hour < 14) return "中午好";
  else if (hour < 17) return "下午好";
  else if (hour < 19) return "傍晚好";
  else if (hour < 22) return "晚上好";
  else return "夜里好";
}
/**
 * 获取两个时间相差的秒数
 * @dateBegin 开始时间,new Date() 格式
 * @dateEnd 结束时间,new Date() 格式
 * @returns 返回秒数
 */
export function getTimeDiff(dateBegin: Date, dateEnd: Date) {
  const dateDiff = dateEnd.getTime() - dateBegin.getTime();
  return dateDiff / 1000;
}
/**
 * 格式化两个时间差
 * @dateBegin 开始时间,new Date() 格式
 * @dateEnd 结束时间,new Date() 格式
 * @description dateBegin 2025-1-1,dateEnd 2025-1-2 10:10:10 :   1天10时10分10秒
 * @returns 返回拼接后的时间字符串
 */
export function formatTimeDiff(dateBegin: Date, dateEnd: Date) {
  const dateDiff = dateEnd.getTime() - dateBegin.getTime(); //时间差的毫秒数
  const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); //计算出相差天数
  const leave1 = dateDiff % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
  const hours = Math.floor(leave1 / (3600 * 1000)); //计算出小时数
  //计算相差分钟数
  const leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
  const minutes = Math.floor(leave2 / (60 * 1000)); //计算相差分钟数
  //计算相差秒数
  const leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数
  const seconds = Math.round(leave3 / 1000);
  let result = "";
  if (dayDiff > 0) {
    result += dayDiff + "天";
  }
  if (hours > 0) {
    result += hours + "时";
  }
  if (minutes > 0) {
    result += minutes + "分";
  }
  if (seconds >= 0) {
    result += seconds + "秒";
  }
  return result;
}
src/utils/http/index.ts
@@ -73,7 +73,14 @@
          return config;
        }
        /** 请求白名单,放置一些不需要`token`的接口(通过设置请求白名单,防止`token`过期后再请求造成的死循环问题) */
        const whiteList = ["/refresh-token", "/login"];
        const whiteList = [
          "/refresh-token",
          "/loginPhone",
          "/register",
          "customerRegistration",
          "captcha",
          "exRole"
        ];
        return whiteList.some(url => config.url.endsWith(url))
          ? config
          : new Promise(resolve => {
src/views/home/index.vue
@@ -1,3 +1,4 @@
el
<template>
  <div class="header">
    <img width="227px" height="74px" src="@/assets/home/logo.png" alt="" />
@@ -41,10 +42,10 @@
      </div>
    </div>
    <div class="right">
      <el-tabs v-model="activeName" class="demo-tabs">
      <!-- <el-tabs v-model="activeName" class="demo-tabs">
        <el-tab-pane label="意向公开" name="first">
          意向公开
          <!-- <div class="item">
          意向公开 -->
      <div class="item">
            <span
              ><span style="color: #145ccd; font-weight: 600">·</span
              >【四川省成都市】2024年石盘街道付家祠村粮油产业园区及配套基础设施建设项目磋商公告</span
@@ -92,13 +93,20 @@
              >【四川省成都市】金牛区抢险救灾工程项目工程队伍储备库(房建、市政类)招标公告</span
            >
            <span>2024-04-15 18:10</span>
          </div> -->
        </el-tab-pane>
      </div>
      <div class="item">
        <span
          ><span style="color: #145ccd; font-weight: 600">·</span
          >【四川省成都市】金牛区抢险救灾工程项目工程队伍储备库(房建、市政类)招标公告</span
        >
        <span>2024-04-15 18:10</span>
      </div>
      <!-- </el-tab-pane>
        <el-tab-pane label="工程招标" name="second">工程招标</el-tab-pane>
        <el-tab-pane label="货物招标" name="third">货物招标</el-tab-pane>
        <el-tab-pane label="服务招标" name="fourth">服务招标</el-tab-pane>
        <el-tab-pane label="网上竞价" name="fourth">网上竞价</el-tab-pane>
      </el-tabs>
        <el-tab-pane label="网上竞价" name="fourth1">网上竞价</el-tab-pane>
      </el-tabs> -->
    </div>
  </div>
  <div class="daixie">
@@ -140,7 +148,7 @@
  <div class="user">
    <div class="all">
      <div class="left">
        <div class="item item1">
        <div v-for="item in state.roleList" :key="item.id" class="item item1">
          <div class="box">
            <img
              width="18px"
@@ -148,65 +156,44 @@
              src="@/assets/home/car1.png"
              alt=""
            />
            采购人
            {{ item.name }}
          </div>
          <div>
            <span class="hover:cursor-pointer" @click="toRegister">注册</span
            ><span class="m-2">|</span
            ><span class="hover:cursor-pointer" @click="toLogin">登录</span>
          </div>
        </div>
        <div class="item">
          <div class="box">
            <img
              width="18px"
              height="18px"
              src="@/assets/home/car.png"
              alt=""
            />
            代理机构
          </div>
          <div>
            <span class="hover:cursor-pointer" @click="toRegister">注册</span
            ><span class="m-2">|</span
            ><span class="hover:cursor-pointer" @click="toLogin">登录</span>
          </div>
        </div>
        <div class="item">
          <div class="box">
            <img
              width="18px"
              height="18px"
              src="@/assets/home/car.png"
              alt=""
            />供应商
          </div>
          <div>
            <span class="hover:cursor-pointer" @click="toRegister">注册</span
            ><span class="m-2">|</span
            ><span class="hover:cursor-pointer" @click="toLogin">登录</span>
          </div>
        </div>
        <div class="item">
          <div class="box">
            <img
              width="18px"
              height="18px"
              src="@/assets/home/car.png"
              alt=""
            />评审专家
          </div>
          <div>
            <span class="hover:cursor-pointer" @click="toRegister">注册</span
            ><span class="m-2">|</span
            ><span class="hover:cursor-pointer" @click="toLogin">登录</span>
            <el-link
              v-if="state.rolesCode.indexOf(item.code) !== -1"
              :underline="false"
              class="hover:cursor-pointer"
              @click="toRegister"
              >退出</el-link
            >
            <el-link
              v-else
              :underline="false"
              class="hover:cursor-pointer"
              @click="toRegister"
              >申请</el-link
            ><span class="m-2">|</span>
            <el-link
              v-if="state.rolesCode.indexOf(item.code) !== -1"
              :underline="false"
              class="hover:cursor-pointer"
              @click="toWelcome"
              >进入</el-link
            >
            <el-link
              v-else
              :underline="false"
              class="hover:cursor-pointer"
              :disabled="state.accessToken"
              @click="toLogin"
              >登录</el-link
            >
          </div>
        </div>
      </div>
      <div class="right" />
    </div>
  </div>
  <div class="more">
    <div class="content">
      <div class="item">
@@ -399,16 +386,45 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { onMounted, ref, computed, reactive } from "vue";
import myFooter from "./component/myFooter.vue";
let activeName = ref("first");
import { useUserStoreHook } from "@/store/modules/user";
import { exRole } from "@/api/register/index.ts";
import { storageLocal, isString, isIncludeAllChildren } from "@pureadmin/utils";
import { getToken } from "@/utils/auth";
import { useRoute, useRouter } from "vue-router";
defineOptions({
  name: "Home"
  name: "Main"
});
let activeName = ref("first");
/** 角色(如果昵称为空则显示用户名) */
const getUseRoles = () => {
  state.userInfo = storageLocal().getItem("user-info");
  state.userInfo.exRoles.forEach(element => {
    state.rolesCode.push(element.code);
  });
};
const state = reactive({
  roleList: [],
  rolesCode: [],
  userInfo: {},
  accessToken: ""
});
onMounted(() => {
  exRole().then(res => {
    state.roleList = res.result;
  });
  getUseRoles();
  state.accessToken = getToken().accessToken;
});
const router = useRouter();
const toRegister = () => {
  router.push({ name: "Register" });
};
const toWelcome = () => {
  router.push({ name: "Welcome" });
};
const toLogin = () => {
  router.push({ name: "Login" });
@@ -552,9 +568,12 @@
        justify-content: space-between;
        align-items: center;
        padding: 0 30px;
        height: 25%;
        height: 23%;
        text-align: left;
        color: #5f5f5f;
        // box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
        border-radius: 6px;
        margin-bottom: 10px;
        .box {
          display: flex;
          justify-content: center;
@@ -569,13 +588,13 @@
        color: #ffffff;
      }
    }
    .left :first-child {
      border-top-left-radius: 8px;
      border-top-right-radius: 8px;
    }
    .left *:not(:first-child):not(:last-child) {
      border-bottom: #c6c6c6 1px solid;
    }
    // .left :first-child {
    //   border-top-left-radius: 8px;
    //   border-top-right-radius: 8px;
    // }
    // .left *:not(:first-child):not(:last-child) {
    //   border-bottom: #c6c6c6 1px solid;
    // }
    .right {
      background: #a9b3c4;
      width: 75%;
@@ -721,4 +740,14 @@
    border-radius: 4px;
  }
}
a {
  font-size: 16px;
  // font-weight: 600;
  color: #fff;
}
a:hover,
a:active {
  color: #fff;
}
</style>
src/views/item/component/editDialog.vue
New file
@@ -0,0 +1,193 @@
<script lang="ts" name="procurementComplaint" setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormRules } from 'element-plus';
import { formatDate } from '/@/utils/formatTime';
import { useProcurementComplaintApi } from '/@/api/fb_p_complaints/procurementComplaint';
import { log } from 'console';
//父级传递来的函数,用于回调
const emit = defineEmits(['reloadTable']);
const procurementComplaintApi = useProcurementComplaintApi();
const ruleFormRef = ref();
const state = reactive({
    title: '',
    loading: false,
    showDialog: false,
    ruleForm: {
        complaints: [{ itemDescription: '' }],
    } as any,
    stores: {},
    dropdownData: {} as any,
});
const handlingStatus = [
    { value: 0, label: '其它' },
    { value: 1, label: '成立' },
    { value: 2, label: '驳回' },
    { value: 3, label: '部分成立' },
];
// 自行添加其他规则
const rules = ref<FormRules>({
    projectCode: [{ required: true, message: '请选择项目编号!', trigger: 'blur' }],
    projectName: [{ required: true, message: '请选择项目名称!', trigger: 'blur' }],
    decisionDate: [{ required: true, message: '请选择决定日期!', trigger: 'change' }],
    purchaser: [{ required: true, message: '请选择采购人!', trigger: 'blur' }],
    procurementAgency: [{ required: true, message: '请选择采购代理机构!', trigger: 'blur' }],
});
// 页面加载时
onMounted(async () => {});
// 打开弹窗
const openDialog = async (row: any, title: string) => {
    state.title = title;
    row = row ?? { complaints: [{ itemDescription: '' }] };
    state.ruleForm = row.id ? await procurementComplaintApi.detail(row.id).then((res) => res.data.result) : JSON.parse(JSON.stringify(row));
    state.showDialog = true;
    console.log(state.ruleForm.complaints, state.ruleForm.complaints.length);
};
// 关闭弹窗
const closeDialog = () => {
    emit('reloadTable');
    state.showDialog = false;
};
// 提交
const submit = async () => {
    ruleFormRef.value.validate(async (isValid: boolean, fields?: any) => {
        if (isValid) {
            let values = state.ruleForm;
            await procurementComplaintApi[state.ruleForm.id ? 'update' : 'add'](values);
            closeDialog();
        } else {
            ElMessage({
                message: `表单有${Object.keys(fields).length}处验证失败,请修改后再提交`,
                type: 'error',
            });
        }
    });
};
const changeComplaints = (index:any,txt: String) => {
    if (txt == 'add') {
        state.ruleForm.complaints.splice(index+1, 0, { itemDescription: '' })
    } else {
        state.ruleForm.complaints.splice(index, 1)
    }
};
//将属性或者函数暴露给父组件
defineExpose({ openDialog });
</script>
<template>
  <div class="procurementComplaint-container">
    <el-dialog v-model="state.showDialog" :width="900" draggable :close-on-click-modal="false">
      <template #header>
        <div style="color: #fff">
          <span>{{ state.title }}</span>
        </div>
      </template>
      <el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto" :rules="rules">
        <el-row :gutter="35">
          <el-form-item v-show="false">
            <el-input v-model="state.ruleForm.id" />
          </el-form-item>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="决定日期" prop="decisionDate">
              <el-date-picker v-model="state.ruleForm.decisionDate" type="date" placeholder="请选择决定日期" />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="项目名称" prop="projectName">
              <el-input v-model="state.ruleForm.projectName" placeholder="请输入项目名称" maxlength="200" show-word-limit
                clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="项目编号" prop="projectCode">
              <el-input v-model="state.ruleForm.projectCode" placeholder="请输入项目编号" maxlength="50" show-word-limit
                clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="采购人" prop="purchaser">
              <el-input v-model="state.ruleForm.purchaser" placeholder="请输入采购人" maxlength="100" show-word-limit
                clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="采购代理机构" prop="procurementAgency">
              <el-input v-model="state.ruleForm.procurementAgency" placeholder="请输入采购代理机构" maxlength="100"
                show-word-limit clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="采购监督部门" prop="procurementSupervisionDepartment">
              <el-input v-model="state.ruleForm.procurementSupervisionDepartment" placeholder="请输入采购监督部门"
                maxlength="100" show-word-limit clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="投诉人" prop="complainant">
              <el-input v-model="state.ruleForm.complainant" placeholder="请输入投诉人" maxlength="100" show-word-limit
                clearable />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
            <el-form-item label="线上地址" prop="url">
              <el-input v-model="state.ruleForm.url" placeholder="请输入线上地址" maxlength="255" show-word-limit clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <template v-for="(item,index) in state.ruleForm.complaints" :key="index">
            <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
              <el-form-item :label="`投诉事项${index==0?'':index}`" prop="complaints">
                <el-input v-model="item.itemDescription" type="textarea" :placeholder="`请输入投诉事项${index==0?'':index}`"
                  maxlength="1000" show-word-limit clearable />
              </el-form-item>
            </el-col>
            <el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12" class="">
              <el-form-item :label="`处理描述${index==0?'':index}`" prop="complaints">
                <el-input v-model="item.handlingResult" type="textarea" :placeholder="`请输入处理描述${index==0?'':index}`"
                  maxlength="1000" show-word-limit clearable />
              </el-form-item>
            </el-col>
            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6" class="mb20">
              <el-form-item :label="`投诉状态${index==0?'':index}`" prop="complaints">
                <el-select v-model="item.handlingStatus">
                  <el-option v-for="item in handlingStatus" :key="item.value" :label="item.label" :value="item.value" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :xs="1" :sm="1" :md="1" :lg="1" :xl="1" class="mb20">
              <el-button type="primary" @click="changeComplaints(index,'add')">+</el-button>
            </el-col>
            <el-col :xs="1" :sm="1" :md="1" :lg="1" :xl="1" class="mb20"
              v-if="state.ruleForm.complaints&&state.ruleForm.complaints.length>1">
              <el-button type="primary" @click="changeComplaints(index)">-</el-button>
            </el-col>
            <el-divider v-if="state.ruleForm.complaints&&state.ruleForm.complaints.length>1" />
          </template>
        </el-row>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="() => state.showDialog = false">取 消</el-button>
          <el-button @click="submit" type="primary" v-reclick="1000">确 定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<style lang="scss" scoped>
:deep(.el-select),
:deep(.el-input-number) {
    width: 100%;
}
</style>
src/views/item/index.vue
New file
@@ -0,0 +1,506 @@
<script lang="ts" setup name="procurementComplaint">
import { ref, reactive, onMounted } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
// import { downloadStreamFile } from "@/utils/download";
import { useProcurementComplaintApi } from "/@/api/fb_p_complaints/procurementComplaint";
import editDialog from "/@/views/fb_p_complaints/procurementComplaint/component/editDialog.vue";
import printDialog from "/@/views/system/print/component/hiprint/preview.vue";
import ModifyRecord from "/@/components/table/modifyRecord.vue";
import ImportData from "/@/components/table/importData.vue";
import commonFunction from "@/utils/commonFunction";
const { dateFormatYMD } = commonFunction();
const procurementComplaintApi = useProcurementComplaintApi();
const printDialogRef = ref();
const editDialogRef = ref();
const importDataRef = ref();
const state = reactive({
  exportLoading: false,
  tableLoading: false,
  stores: {},
  showAdvanceQueryUI: false,
  dropdownData: {} as any,
  selectData: [] as any[],
  tableQueryParams: {} as any,
  tableParams: {
    page: 1,
    pageSize: 20,
    total: 0,
    field: "decisionDate", // 默认的排序字段
    order: "descending", // 排序方向
    descStr: "descending" // 降序排序的关键字符
  },
  tableData: []
});
// 页面加载时
onMounted(async () => {});
// 查询操作
const handleQuery = async (params: any = {}) => {
  state.tableLoading = true;
  state.tableParams = Object.assign(state.tableParams, params);
  const result = await procurementComplaintApi
    .page(Object.assign(state.tableQueryParams, state.tableParams))
    .then(res => res.data.result);
  state.tableParams.total = result?.total;
  state.tableData = result?.items ?? [];
  state.tableLoading = false;
};
// 列排序
const sortChange = async (column: any) => {
  state.tableParams.field = column.prop;
  state.tableParams.order = column.order;
  await handleQuery();
};
// 删除
const delProcurementComplaint = (row: any) => {
  ElMessageBox.confirm(`确定要删除吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
  })
    .then(async () => {
      await procurementComplaintApi.delete({ id: row.id });
      handleQuery();
      ElMessage.success("删除成功");
    })
    .catch(() => {});
};
// 批量删除
const batchDelProcurementComplaint = () => {
  ElMessageBox.confirm(
    `确定要删除${state.selectData.length}条记录吗?`,
    "提示",
    {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning"
    }
  )
    .then(async () => {
      await procurementComplaintApi
        .batchDelete(state.selectData.map(u => ({ id: u.id })))
        .then(res => {
          ElMessage.success(`成功批量删除${res.data.result}条记录`);
          handleQuery();
        });
    })
    .catch(() => {});
};
// 导出数据
const exportProcurementComplaintCommand = async (command: string) => {
  try {
    state.exportLoading = true;
    if (command === "select") {
      const params = Object.assign(
        {},
        state.tableQueryParams,
        state.tableParams,
        { selectKeyList: state.selectData.map(u => u.id) }
      );
      await procurementComplaintApi
        .exportData(params)
        .then(res => downloadStreamFile(res));
    } else if (command === "current") {
      const params = Object.assign(
        {},
        state.tableQueryParams,
        state.tableParams
      );
      await procurementComplaintApi
        .exportData(params)
        .then(res => downloadStreamFile(res));
    } else if (command === "all") {
      const params = Object.assign(
        {},
        state.tableQueryParams,
        state.tableParams,
        { page: 1, pageSize: 99999999 }
      );
      await procurementComplaintApi
        .exportData(params)
        .then(res => downloadStreamFile(res));
    }
  } finally {
    state.exportLoading = false;
  }
};
handleQuery();
</script>
<template>
  <div v-loading="state.exportLoading" class="procurementComplaint-container">
    <el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
      <el-form ref="queryForm" :model="state.tableQueryParams" labelWidth="90">
        <el-row>
          <el-col :xs="24" :sm="12" :md="12" :lg="5" :xl="4" class="mb10">
            <el-form-item label="决定日期">
              <el-date-picker
                v-model="state.tableQueryParams.decisionDateRange"
                type="daterange"
                value-format="YYYY-MM-DD HH:mm:ss"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="项目名称">
              <el-input
                v-model="state.tableQueryParams.projectName"
                clearable
                placeholder="请输入项目名称"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="项目编号">
              <el-input
                v-model="state.tableQueryParams.projectCode"
                clearable
                placeholder="请输入项目编号"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="采购人">
              <el-input
                v-model="state.tableQueryParams.purchaser"
                clearable
                placeholder="请输入采购人"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="采购代理机构">
              <el-input
                v-model="state.tableQueryParams.procurementAgency"
                clearable
                placeholder="请输入采购代理机构"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="5"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="采购监督部门">
              <el-input
                v-model="
                  state.tableQueryParams.procurementSupervisionDepartment
                "
                clearable
                placeholder="请输入采购监督部门"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="投诉人">
              <el-input
                v-model="state.tableQueryParams.complainant"
                clearable
                placeholder="请输入投诉人"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="投诉事项">
              <el-input
                v-model="state.tableQueryParams.keyword"
                clearable
                placeholder="请输入投诉事项"
              />
            </el-form-item>
          </el-col>
          <el-col
            v-if="state.showAdvanceQueryUI"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="4"
            :xl="4"
            class="mb10"
          >
            <el-form-item label="是否成立">
              <el-select
                v-model="state.tableQueryParams.status"
                placeholder="请选择"
                clearable
              >
                <el-option label="成立" value="Valid" />
                <el-option label="驳回" value="Rejected" />
                <el-option label="其它" value="Other" />
              </el-select>
            </el-form-item>
          </el-col>
          <!-- <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
            <el-form-item label="关键字">
              <el-input v-model="state.tableQueryParams.keyword" clearable placeholder="请输入模糊查询关键字"/>
            </el-form-item>
          </el-col> -->
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
            <el-form-item>
              <el-button-group style="display: flex; align-items: center">
                <el-button
                  v-reclick="1000"
                  type="primary"
                  icon="ele-Search"
                  @click="handleQuery"
                >
                  查询
                </el-button>
                <el-button
                  icon="ele-Refresh"
                  @click="() => (state.tableQueryParams = {})"
                >
                  重置
                </el-button>
                <el-button
                  v-if="!state.showAdvanceQueryUI"
                  icon="ele-ZoomIn"
                  style="margin-left: 5px"
                  @click="() => (state.showAdvanceQueryUI = true)"
                >
                  高级查询
                </el-button>
                <el-button
                  v-if="state.showAdvanceQueryUI"
                  icon="ele-ZoomOut"
                  style="margin-left: 5px"
                  @click="() => (state.showAdvanceQueryUI = false)"
                >
                  隐藏
                </el-button>
                <el-button
                  type="danger"
                  style="margin-left: 5px"
                  icon="ele-Delete"
                  :disabled="state.selectData.length == 0"
                  @click="batchDelProcurementComplaint"
                >
                  删除
                </el-button>
                <el-button
                  type="primary"
                  style="margin-left: 5px"
                  icon="ele-Plus"
                  @click="
                    editDialogRef.openDialog(null, '新增政府采购投诉数据处理')
                  "
                >
                  新增
                </el-button>
                <!-- <el-dropdown :show-timeout="70" :hide-timeout="50" @command="exportProcurementComplaintCommand">
                  <el-button type="primary" style="margin-left:5px;" icon="ele-FolderOpened" v-reclick="20000"
                    v-auth="'procurementComplaint:export'"> 导出 </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item command="select"
                        :disabled="state.selectData.length == 0">导出选中</el-dropdown-item>
                      <el-dropdown-item command="current">导出本页</el-dropdown-item>
                      <el-dropdown-item command="all">导出全部</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
                <el-button type="warning" style="margin-left:5px;" icon="ele-MostlyCloudy"
                  @click="importDataRef.openDialog()" v-auth="'procurementComplaint:import'"> 导入 </el-button> -->
              </el-button-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    <el-card class="full-table" shadow="hover" style="margin-top: 5px">
      <el-table
        v-loading="state.tableLoading"
        :data="state.tableData"
        :default-sort="{ prop: 'decisionDate', order: 'descending' }"
        style="width: 100%"
        tooltip-effect="light"
        row-key="id"
        border
        @selection-change="
          (val: any[]) => {
            state.selectData = val;
          }
        "
        @sort-change="sortChange"
      >
        <el-table-column type="selection" width="40" align="center" />
        <el-table-column type="index" label="序号" width="55" align="center" />
        <el-table-column
          prop="decisionDate"
          label="决定日期"
          width="90"
          show-overflow-tooltip
          sortable
        >
          <template #default="{ row }">
            <span>{{ dateFormatYMD(null, null, row.decisionDate) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="projectName"
          label="项目名称"
          show-overflow-tooltip
        />
        <el-table-column
          prop="projectCode"
          label="项目编号"
          width="135"
          show-overflow-tooltip
        />
        <el-table-column
          prop="purchaser"
          label="采购人"
          show-overflow-tooltip
        />
        <el-table-column
          prop="procurementAgency"
          label="采购代理机构"
          show-overflow-tooltip
        />
        <el-table-column
          prop="complainant"
          label="投诉人"
          show-overflow-tooltip
        />
        <el-table-column
          prop="procurementSupervisionDepartment"
          label="采购监督部门"
          width="140"
          show-overflow-tooltip
        />
        <!-- <el-table-column prop='complaints' label='是否成立' show-overflow-tooltip /> -->
        <el-table-column prop="url" label="线上地址" show-overflow-tooltip>
          <template #default="{ row }">
            <el-link underline :href="row.url" target="_blank">{{
              row.url
            }}</el-link>
          </template>
        </el-table-column>
        <!-- <el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
          <template #default="scope">
            <ModifyRecord :data="scope.row" />
          </template>
        </el-table-column> -->
        <el-table-column
          label="操作"
          width="70"
          align="center"
          fixed="right"
          show-overflow-tooltip
        >
          <template #default="scope">
            <el-button
              icon="ele-Edit"
              size="small"
              text
              type="primary"
              @click="
                editDialogRef.openDialog(scope.row, '编辑政府采购投诉数据处理')
              "
            />
            <el-button
              icon="ele-Delete"
              size="small"
              text
              type="primary"
              @click="delProcurementComplaint(scope.row)"
            />
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        v-model:currentPage="state.tableParams.page"
        v-model:page-size="state.tableParams.pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :page-sizes="[10, 20, 50, 100, 200, 500]"
        :total="state.tableParams.total"
        size="small"
        background
        @size-change="(val: any) => handleQuery({ pageSize: val })"
        @current-change="(val: any) => handleQuery({ page: val })"
      />
      <ImportData
        ref="importDataRef"
        :import="procurementComplaintApi.importData"
        :download="procurementComplaintApi.downloadTemplate"
        @refresh="handleQuery"
      />
      <printDialog
        ref="printDialogRef"
        :title="'打印政府采购投诉数据处理'"
        @reloadTable="handleQuery"
      />
      <editDialog ref="editDialogRef" @reloadTable="handleQuery" />
    </el-card>
  </div>
</template>
<style scoped>
:deep(.el-input),
:deep(.el-select),
:deep(.el-input-number) {
  width: 100%;
}
</style>
src/views/login/index.vue
@@ -172,7 +172,7 @@
            return initRouter().then(() => {
              disabled.value = true;
              router
                .push(getTopMenu(true)?.path)
                .replace("index")
                .then(() => {
                  message("登录成功", { type: "success" });
                })
types/index.d.ts
@@ -14,7 +14,8 @@
type ForDataType<T> = {
  [P in T]?: ForDataType<T[P]>;
};
// 申明 数组
declare type EmptyArrayType<T = any> = T[];
type AnyFunction<T> = (...args: any[]) => T;
type PropType<T> = VuePropType<T>;