liu_lake 6 сар өмнө
parent
commit
c2d719a09a
59 өөрчлөгдсөн 7091 нэмэгдсэн , 28 устгасан
  1. 1 1
      admin-ui/.env.development
  2. 2 2
      admin-ui/src/api/monitor/cache.ts
  3. 3 3
      admin-ui/src/api/monitor/quartz.ts
  4. 69 0
      admin-ui/src/api/order/index.ts
  5. 2 2
      admin-ui/src/api/platform/index.ts
  6. 2 2
      admin-ui/src/api/sys/config.ts
  7. 2 2
      admin-ui/src/api/sys/permission.ts
  8. 4 4
      admin-ui/src/api/sys/role.ts
  9. 4 4
      admin-ui/src/api/sys/user.ts
  10. 6 0
      admin-ui/src/constants/constants.ts
  11. 19 0
      admin-ui/src/views/merchant/index.vue
  12. 155 0
      admin-ui/src/views/merchant/modules/edit.vue
  13. 371 0
      admin-ui/src/views/merchant/modules/list.vue
  14. 155 0
      admin-ui/src/views/order/edit.vue
  15. 387 7
      admin-ui/src/views/order/index.vue
  16. 1 1
      agent-ui/.env.development
  17. 47 0
      agent-ui/api/agent/index.ts
  18. 22 0
      agent-ui/api/auth/index.ts
  19. 9 0
      agent-ui/api/auth/routes.ts
  20. 52 0
      agent-ui/api/base.ts
  21. 24 0
      agent-ui/api/monitor/cache.ts
  22. 77 0
      agent-ui/api/monitor/quartz.ts
  23. 38 0
      agent-ui/api/platform/index.ts
  24. 51 0
      agent-ui/api/sys/config.ts
  25. 41 0
      agent-ui/api/sys/permission.ts
  26. 66 0
      agent-ui/api/sys/role.ts
  27. 83 0
      agent-ui/api/sys/user.ts
  28. 47 0
      agent-ui/src/api/merchant/index.ts
  29. 19 0
      agent-ui/src/views/merchant/index.vue
  30. 155 0
      agent-ui/src/views/merchant/modules/edit.vue
  31. 371 0
      agent-ui/src/views/merchant/modules/list.vue
  32. 19 0
      agent-ui/views/agent/index.vue
  33. 155 0
      agent-ui/views/agent/modules/edit.vue
  34. 371 0
      agent-ui/views/agent/modules/list.vue
  35. 145 0
      agent-ui/views/business/template/hook/template-edit.vue
  36. 355 0
      agent-ui/views/business/template/hook/template-list.vue
  37. 19 0
      agent-ui/views/business/template/index.vue
  38. 18 0
      agent-ui/views/channel/index.vue
  39. 70 0
      agent-ui/views/error/403.vue
  40. 70 0
      agent-ui/views/error/404.vue
  41. 70 0
      agent-ui/views/error/500.vue
  42. 204 0
      agent-ui/views/login/index.vue
  43. 40 0
      agent-ui/views/login/utils/motion.ts
  44. 30 0
      agent-ui/views/login/utils/rule.ts
  45. 5 0
      agent-ui/views/login/utils/static.ts
  46. 18 0
      agent-ui/views/order/index.vue
  47. 19 0
      agent-ui/views/platform/index.vue
  48. 334 0
      agent-ui/views/system/config.vue
  49. 328 0
      agent-ui/views/system/menu.vue
  50. 257 0
      agent-ui/views/system/modules/config-edit.vue
  51. 431 0
      agent-ui/views/system/modules/menu-edit.vue
  52. 238 0
      agent-ui/views/system/modules/reset-passwd.vue
  53. 174 0
      agent-ui/views/system/modules/role-edit.vue
  54. 170 0
      agent-ui/views/system/modules/role-permission.vue
  55. 457 0
      agent-ui/views/system/modules/user-edit.vue
  56. 420 0
      agent-ui/views/system/modules/user-list.vue
  57. 325 0
      agent-ui/views/system/role.vue
  58. 40 0
      agent-ui/views/system/user.vue
  59. 24 0
      agent-ui/views/welcome/index.vue

+ 1 - 1
admin-ui/.env.development

@@ -7,5 +7,5 @@ VITE_PUBLIC_PATH = ./
 # 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
 VITE_ROUTER_HISTORY = "hash"
 # API访问路径
- VITE_API_SERVER = "https://admin.gagapay.pro/api"
+ VITE_API_SERVER = "http://127.0.0.1:8088/api"
 #VITE_API_SERVER = "https://www.sortebar.com/api"

+ 2 - 2
admin-ui/src/api/monitor/cache.ts

@@ -4,7 +4,7 @@ import { deleteRequest, get } from "../base";
  * @returns .
  */
 export const queryList = () => {
-  return get("/monitor/cache");
+  return get("/monitor/cache", {}, orderId);
 };
 /**
  * 查找详情
@@ -12,7 +12,7 @@ export const queryList = () => {
  * @returns .
  */
 export const queryInfo = (param: any) => {
-  return get("/monitor/cache/query", param);
+  return get("/monitor/cache/query", {}, orderId);
 };
 /**
  * 移除

+ 3 - 3
admin-ui/src/api/monitor/quartz.ts

@@ -6,7 +6,7 @@ import { get, Result, post, put, deleteRequest } from "../base";
  * @returns .
  */
 export function queryPage<T>(query?: any): Promise<Result<T>> {
-  return get("/monitor/quartz/job/query/page", query);
+  return get("/monitor/quartz/job/query/page", {}, orderId);
 }
 /**
  * 列表查询
@@ -14,7 +14,7 @@ export function queryPage<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function queryList<T>(query?: any): Promise<Result<T>> {
-  return get("/monitor/quartz/job/query/list", query);
+  return get("/monitor/quartz/job/query/list", {}, orderId);
 }
 /**
  *  保存
@@ -47,7 +47,7 @@ export function delByIds<T>(id: string[]): Promise<Result<T>> {
  * @returns .
  */
 export function checkCorn<T>(cron: string): Promise<Result<T>> {
-  return get(`/monitor/quartz/job/check/cron`, { cron: cron });
+  return get(`/monitor/quartz/job/check/cron`, {}, orderId);
 }
 
 /**

+ 69 - 0
admin-ui/src/api/order/index.ts

@@ -0,0 +1,69 @@
+import { Result, deleteRequest, get, post, put } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/order/page", query);
+}
+
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/order/list", query);
+}
+
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function save<T>(data: any): Promise<Result<T>> {
+  return post("/sys/pay/order/save", data);
+}
+
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function update<T>(id: string, data: any): Promise<Result<T>> {
+  return put(`/sys/pay/order/update/${id}`, {}, data);
+}
+
+/**
+ * 删除用户
+ * @param userIds .
+ * @returns  .
+ */
+export function del<T>(userIds: string[]): Promise<Result<T>> {
+  return deleteRequest(`/sys/pay/order/remove`, {}, userIds);
+}
+
+
+/**
+ * 查询支付状态
+ * @param id .
+ * @returns  .
+ */
+export function queryPayStatus<T>(id: string): Promise<Result<T>> {
+  return get(`/sys/pay/order/status/${id}`,{});
+}
+
+
+
+
+/**
+ * 订单回调
+ * @param id .
+ * @returns  .
+ */
+export function orderCallback<T>(id: string): Promise<Result<T>> {
+  return get(`/sys/pay/order/callback/${id}`,{});
+}

+ 2 - 2
admin-ui/src/api/platform/index.ts

@@ -6,7 +6,7 @@ import { Result, deleteRequest, get, post, put } from "../base";
  * @returns .
  */
 export function queryPage<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/pay/agent/page", query);
+  return get("/sys/pay/agent/page", {}, orderId);
 }
 
 /**
@@ -15,7 +15,7 @@ export function queryPage<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function queryList<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/pay/agent/list", query);
+  return get("/sys/pay/agent/list", {}, orderId);
 }
 
 /**

+ 2 - 2
admin-ui/src/api/sys/config.ts

@@ -6,7 +6,7 @@ import { get, Result, post, put, deleteRequest } from "../base";
  * @returns .
  */
 export function queryPage<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/config/query/page", query);
+  return get("/sys/config/query/page", {}, orderId);
 }
 /**
  * 列表查询
@@ -14,7 +14,7 @@ export function queryPage<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function queryList<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/config/query/list", query);
+  return get("/sys/config/query/list", {}, orderId);
 }
 /**
  *  保存

+ 2 - 2
admin-ui/src/api/sys/permission.ts

@@ -8,7 +8,7 @@ import { BaseQuery, deleteRequest, get, post, put, Result } from "../base";
 export function treeList<P extends BaseQuery, T>(
   query?: P
 ): Promise<Result<T>> {
-  return get("/sys/permission/tree", query);
+  return get("/sys/permission/tree", {}, orderId);
 }
 /**
  * 菜单树形列表
@@ -17,7 +17,7 @@ export function treeList<P extends BaseQuery, T>(
 export function treeMenus<P extends BaseQuery, T>(
   query?: P
 ): Promise<Result<T>> {
-  return get("/sys/permission/tree/menu", query);
+  return get("/sys/permission/tree/menu", {}, orderId);
 }
 /**
  * 新增

+ 4 - 4
admin-ui/src/api/sys/role.ts

@@ -6,7 +6,7 @@ import { get, Result, post, put } from "../base";
  * @returns .
  */
 export function roleQueryPage<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/role/query/page", query);
+  return get("/sys/role/query/page", {}, orderId);
 }
 /**
  * 查询角色列表
@@ -14,7 +14,7 @@ export function roleQueryPage<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function roleQueryList<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/role/query/list", query);
+  return get("/sys/role/query/list", {}, orderId);
 }
 /**
  *  检查code是否重复
@@ -27,7 +27,7 @@ export function checkCode<T>(code: string, id?: string): Promise<Result<T>> {
   if (id) {
     _data.id = id;
   }
-  return get("/sys/role/check/code", _data);
+  return get("/sys/role/check/code", {}, orderId);
 }
 /**
  *  保存角色
@@ -52,7 +52,7 @@ export function roleUpdate<T>(id: string, data?: T): Promise<Result<T>> {
  * @returns .
  */
 export function queryPermission(id: string): Promise<Result<string[]>> {
-  return get("/sys/role/permission", { id: id });
+  return get("/sys/role/permission", {}, orderId);
 }
 /**
  * 更新权限

+ 4 - 4
admin-ui/src/api/sys/user.ts

@@ -5,7 +5,7 @@ import { Result, deleteRequest, get, post, put } from "../base";
  * @returns .
  */
 export function queryPage<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/user/query/page", query);
+  return get("/sys/user/query/page", {}, orderId);
 }
 /**
  * 列表查询
@@ -13,7 +13,7 @@ export function queryPage<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function queryList<T>(query?: any): Promise<Result<T>> {
-  return get("/sys/user/query/list", query);
+  return get("/sys/user/query/list", {}, orderId);
 }
 /**
  * 检查用户名是否存在
@@ -21,7 +21,7 @@ export function queryList<T>(query?: any): Promise<Result<T>> {
  * @returns .
  */
 export function hashUsername(username: string): Promise<Result<Boolean>> {
-  return get("/sys/user/check/username", { username: username });
+  return get("/sys/user/check/username", {}, orderId);
 }
 /**
  * 新增
@@ -47,7 +47,7 @@ export function update<T>(id: string, data: any): Promise<Result<T>> {
  * @returns .
  */
 export function queryRoleIds<T>(userId: string): Promise<Result<T>> {
-  return get(`/sys/user/query/role/ids`, { id: userId });
+  return get(`/sys/user/query/role/ids`, {}, orderId);
 }
 /**
  * 删除用户

+ 6 - 0
admin-ui/src/constants/constants.ts

@@ -83,3 +83,9 @@ export const configTypeMap: SelectOptionMap<string> = {
   1: "系统配置",
   2: "业务配置"
 };
+export const payStatusOptions: SelectOption<number>[] = [
+  { label: "支付失败", value: -1 },
+  { label: "待支付", value: 0 },
+  { label: "支付成功", value: 1 },
+  { label: "退款", value: 2 }
+];

+ 19 - 0
admin-ui/src/views/merchant/index.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+import list from "./modules/list.vue";
+
+defineOptions({
+  name: "agentManage"
+});
+const pageData: any = reactive({
+  mode: "table"
+});
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <list :mode="pageData.mode" />
+    </template>
+  </el-card>
+</template>

+ 155 - 0
admin-ui/src/views/merchant/modules/edit.vue

@@ -0,0 +1,155 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import * as $api from "@/api/agent/index";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+
+const title = "";
+defineOptions({ name: "templateEdit1" });
+const isDetail = ref(false);
+const formRef = ref<FormInstance>();
+// 初始化数据
+const initData = {
+  userName: undefined,
+  password: undefined,
+  status: 1
+};
+
+// 表单校验规则
+const formRules = {};
+
+// 页面数据
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "",
+  formLoading: false,
+  mode: "add",
+  isUpdate: false,
+  // 表单数据
+  formData: initData,
+  formRules: formRules
+});
+// 暴露给父级调用
+const emits = defineEmits(["ok", "close"]);
+// 打开弹窗
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.formData = cloneDeep(data) || initData;
+  if (mode === "edit") {
+    pageData.title = `${title} - 编辑`;
+    isDetail.value = false;
+  } else if (mode === "detail") {
+    pageData.title = `${title} - 查看`;
+    isDetail.value = true;
+  } else {
+    pageData.title = `${title} - 新增`;
+    isDetail.value = false;
+  }
+  pageData.dataSource = dataSource;
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+// 关闭弹窗
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+// 确定并关闭弹窗
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .save(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .update(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog v-model="pageData.dialogVisible" destroy-on-close :width="500">
+    <template #header>
+      <el-text class="mx-1" type="primary" size="large">{{
+        pageData.title
+      }}</el-text>
+    </template>
+    <div class="el-dialog-content">
+      <el-form
+        ref="formRef"
+        :model="pageData.formData"
+        style="width: 90%; margin: 20px auto 0"
+        label-width="auto"
+        :rules="pageData.formRules"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="代理名称" prop="userName">
+          <el-input
+            v-model="pageData.formData.userName"
+            clearable
+            placeholder="请输入名称"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input
+            v-model="pageData.formData.password"
+            type="password"
+            clearable
+            placeholder="请输入密码"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button v-if="!isDetail" @click="handleClose">取消</el-button>
+      <el-button v-if="!isDetail" type="primary" @click="handleConfirm"
+        >确认</el-button
+      >
+    </template>
+  </el-dialog>
+</template>

+ 371 - 0
admin-ui/src/views/merchant/modules/list.vue

@@ -0,0 +1,371 @@
+<script setup lang="ts">
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import Edit from "./edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { computed, onBeforeMount, onMounted, reactive, ref, watch } from "vue";
+import * as $api from "@/api/agent/index";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+import { enableOptions } from "@/constants/constants";
+import { cloneDeep } from "@pureadmin/utils";
+
+const editRef = ref();
+
+defineOptions({
+  name: "merchantList"
+});
+
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "代理商名称",
+      prop: "agentname",
+      placeholder: "请输入代理商名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      placeholder: "请选择",
+      dataSourceKey: "enableOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    query: ["user:query"],
+    save: ["user:save"],
+    update: ["user:update"],
+    del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {}
+  },
+  dataSource: {
+    enableOptions: enableOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["user:save"]
+      }
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "ID",
+        prop: "id",
+        slot: "id",
+        width: 220
+      },
+      {
+        label: "代理商名称",
+        prop: "userName",
+        slot: "userName"
+      },
+      {
+        label: "余额",
+        prop: "amount",
+        slot: "amount"
+      },
+      {
+        label: "谷歌校验器",
+        prop: "hasTpop",
+        slot: "hasTpop"
+      },
+      {
+        label: "状态",
+        prop: "status",
+        slot: "status"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      small: true,
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      editRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      break;
+    default:
+      break;
+  }
+};
+
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+/**
+ * 分页 - 改变每页条数
+ * @param val .
+ */
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+/**
+ * 分页 - 改变页码
+ * @param val .
+ */
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+/**
+ * 获取查询参数
+ */
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 获取列表数据
+ * @param page .
+ */
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $api
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+/**
+ * 查看详情
+ * @param row .
+ */
+const handleDetail = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "detail");
+};
+/**
+ * 编辑
+ * @param row .
+ */
+const handleEdit = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "edit");
+};
+/**
+ * 删除
+ * @param row .
+ */
+const handleDel = (row: any) => {
+  message.confirm(`确认删除 id 为 (${row.id}) 的数据?`).then(() => {
+    batchDel([row.id]);
+  });
+};
+/**
+ * 批量删除
+ * @param ids .
+ */
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $api.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {});
+onMounted(() => {
+  _loadData(1);
+});
+</script>
+
+<template>
+  <div>
+    <form-search
+      size="small"
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      :query-permission="pageData.permission.query"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      size="small"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]"
+          >{{ row[column.property] }}
+        </el-tooltip>
+      </template>
+      <template #enableScope="scope">
+        <el-tag v-if="scope.row.enable">启用</el-tag>
+        <el-tag v-else type="info">禁用</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            v-show="hasAuth(pageData.permission.update)"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑
+          </el-link>
+          <el-divider
+            v-show="hasAuth(pageData.permission.del)"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.del)"
+            type="primary"
+            @click="handleDel(row)"
+            >删除
+          </el-link>
+        </div>
+      </template>
+    </pure-table>
+    <edit ref="editRef" @ok="_loadData" />
+  </div>
+</template>

+ 155 - 0
admin-ui/src/views/order/edit.vue

@@ -0,0 +1,155 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import * as $api from "@/api/agent/index";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+
+const title = "";
+defineOptions({ name: "templateEdit" });
+const isDetail = ref(false);
+const formRef = ref<FormInstance>();
+// 初始化数据
+const initData = {
+  userName: undefined,
+  password: undefined,
+  status: 1
+};
+
+// 表单校验规则
+const formRules = {};
+
+// 页面数据
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "",
+  formLoading: false,
+  mode: "add",
+  isUpdate: false,
+  // 表单数据
+  formData: initData,
+  formRules: formRules
+});
+// 暴露给父级调用
+const emits = defineEmits(["ok", "close"]);
+// 打开弹窗
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.formData = cloneDeep(data) || initData;
+  if (mode === "edit") {
+    pageData.title = `${title} - 编辑`;
+    isDetail.value = false;
+  } else if (mode === "detail") {
+    pageData.title = `${title} - 查看`;
+    isDetail.value = true;
+  } else {
+    pageData.title = `${title} - 新增`;
+    isDetail.value = false;
+  }
+  pageData.dataSource = dataSource;
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+// 关闭弹窗
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+// 确定并关闭弹窗
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .save(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .update(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog v-model="pageData.dialogVisible" destroy-on-close :width="500">
+    <template #header>
+      <el-text class="mx-1" type="primary" size="large">{{
+        pageData.title
+      }}</el-text>
+    </template>
+    <div class="el-dialog-content">
+      <el-form
+        ref="formRef"
+        :model="pageData.formData"
+        style="width: 90%; margin: 20px auto 0"
+        label-width="auto"
+        :rules="pageData.formRules"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="代理名称" prop="userName">
+          <el-input
+            v-model="pageData.formData.userName"
+            clearable
+            placeholder="请输入名称"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input
+            v-model="pageData.formData.password"
+            type="password"
+            clearable
+            placeholder="请输入密码"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button v-if="!isDetail" @click="handleClose">取消</el-button>
+      <el-button v-if="!isDetail" type="primary" @click="handleConfirm"
+        >确认</el-button
+      >
+    </template>
+  </el-dialog>
+</template>

+ 387 - 7
admin-ui/src/views/order/index.vue

@@ -1,18 +1,398 @@
 <script setup lang="ts">
-import FormSearch from "@/components/opts/form-search.vue";
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
 import TableButtons from "@/components/opts/btns2.vue";
-import ConfigEdit from "./modules/config-edit.vue";
+import Edit from "./edit.vue";
 import { PureTable } from "@pureadmin/table";
-import { reactive, ref } from "vue";
-import { configTypeOptions } from "@/constants/constants";
-import * as $configApi from "@/api/sys/config";
-import { onMounted } from "vue";
+import { computed, onBeforeMount, onMounted, reactive, ref, watch } from "vue";
+import * as $api from "@/api/order/index";
 import { hasAuth } from "@/router/utils";
 import message from "@/utils/message";
+import {enableOptions, payStatusOptions} from "@/constants/constants";
+import { cloneDeep } from "@pureadmin/utils";
+
+const editRef = ref();
+
+defineOptions({
+  name: "orderList"
+});
+
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "订单号",
+      prop: "orderNo",
+      placeholder: "请输入代理商名模糊查询"
+    },    {
+      type: "input",
+      label: "平台订单号",
+      prop: "platformOrderNo",
+      placeholder: "请输入代理商名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      placeholder: "请选择",
+      dataSourceKey: "enableOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    // query: ["user:query"],
+    // save: ["user:save"],
+    // update: ["user:update"],
+    // del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {}
+  },
+  dataSource: {
+    enableOptions: payStatusOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "ID",
+        prop: "id",
+        slot: "id",
+        width: 220
+      },
+      {
+        label: "订单号",
+        prop: "orderNo",
+        slot: "orderNo"
+      },
+      {
+        label: "平台订单号",
+        prop: "platformOrderNo",
+        slot: "platformOrderNo"
+      },
+      {
+        label: "金额",
+        prop: "amount",
+        slot: "amount"
+      },
+      {
+        label: "手续费",
+        prop: "fee",
+        slot: "fee"
+      },{
+        label: "费率",
+        prop: "rate",
+        slot: "rate"
+      },
+      {
+        label: "状态",
+        prop: "status",
+        slot: "enableOptions"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      small: true,
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      editRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      break;
+    default:
+      break;
+  }
+};
+
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+/**
+ * 分页 - 改变每页条数
+ * @param val .
+ */
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+/**
+ * 分页 - 改变页码
+ * @param val .
+ */
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+/**
+ * 获取查询参数
+ */
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 获取列表数据
+ * @param page .
+ */
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $api
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+/**
+ * 查看详情
+ * @param row .
+ */
+const handleDetail = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "detail");
+};
+/**
+ * 编辑
+ * @param row .
+ */
+const handleEdit = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "edit");
+};
+const handlePayStatus = (row: any) => {
+    $api.queryPayStatus(row.id).then((res: any) => {
+        if (res.success) {
+            message.success("查询支付状态成功");
+            _loadData();
+        } else {
+            message.warning(res.message);
+        }
+    });
+};
+const handleCallback = (row: any) => {
+    $api.orderCallback(row.id).then((res: any) => {
+        if (res.success) {
+            message.success("回调成功");
+            _loadData();
+        } else {
+            message.warning(res.message);
+        }
+    });
+};
+/**
+ * 删除
+ * @param row .
+ */
+const handleDel = (row: any) => {
+  message.confirm(`确认删除 id 为 (${row.id}) 的数据?`).then(() => {
+    batchDel([row.id]);
+  });
+};
+/**
+ * 批量删除
+ * @param ids .
+ */
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $api.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {});
+onMounted(() => {
+  _loadData(1);
+});
 </script>
 
 <template>
   <div>
-    订单管理
+    <form-search
+      size="small"
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      :query-permission="pageData.permission.query"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      size="small"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]"
+        >{{ row[column.property] }}
+        </el-tooltip>
+      </template>
+      <template #enableOptions="scope">
+        <el-tag v-if="scope.row.status==-1">支付失败</el-tag>
+        <el-tag v-if="scope.row.status==0">待支付</el-tag>
+        <el-tag v-if="scope.row.status==1">支付成功</el-tag>
+        <el-tag v-if="scope.row.status==2">退款</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            type="primary"
+            @click="handlePayStatus(row)"
+          >查询支付状态
+          </el-link>
+          <el-divider
+            direction="vertical"
+          />
+          <el-link
+            type="primary"
+            @click="handleCallback(row)"
+          >回调
+          </el-link>
+        </div>
+      </template>
+    </pure-table>
+    <edit ref="editRef" @ok="_loadData" />
   </div>
 </template>

+ 1 - 1
agent-ui/.env.development

@@ -7,5 +7,5 @@ VITE_PUBLIC_PATH = ./
 # 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
 VITE_ROUTER_HISTORY = "hash"
 # API访问路径
- VITE_API_SERVER = "https://agent.gagapay.pro/api"
+ VITE_API_SERVER = "http://127.0.0.1:8089/api"
 #VITE_API_SERVER = "http://www.sortebar.com/api"

+ 47 - 0
agent-ui/api/agent/index.ts

@@ -0,0 +1,47 @@
+import { Result, deleteRequest, get, post, put } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/agent/page", query);
+}
+
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/agent/list", query);
+}
+
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function save<T>(data: any): Promise<Result<T>> {
+  return post("/sys/pay/agent/save", data);
+}
+
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function update<T>(id: string, data: any): Promise<Result<T>> {
+  return put(`/sys/pay/agent/update/${id}`, {}, data);
+}
+
+/**
+ * 删除用户
+ * @param userIds .
+ * @returns  .
+ */
+export function del<T>(userIds: string[]): Promise<Result<T>> {
+  return deleteRequest(`/sys/pay/agent/remove`, {}, userIds);
+}

+ 22 - 0
agent-ui/api/auth/index.ts

@@ -0,0 +1,22 @@
+import { RSA } from "@/utils/crypto";
+import { post, Result } from "../base";
+
+/**
+ * 用户登录换取token
+ * @param username 用户名
+ * @param password 密码
+ */
+export function login(username: string, password: string) {
+  return post<any, Result<any>>("/auth/login", {
+    username: username,
+    password: RSA.encrypt(password)
+  });
+}
+/**
+ *  登出
+ *
+ * @returns .
+ */
+export function logout() {
+  return post<any, Result<any>>("/auth/logout", {});
+}

+ 9 - 0
agent-ui/api/auth/routes.ts

@@ -0,0 +1,9 @@
+import { get, Result } from "../base";
+
+/**
+ * 当前用户的路由信息
+ * @returns .
+ */
+export function getRoutes(): Promise<Result<any>> {
+  return get<any, Result<any>>("/sys/permission/routes", {});
+}

+ 52 - 0
agent-ui/api/base.ts

@@ -0,0 +1,52 @@
+import { http } from "@/utils/http";
+export interface Result<T> {
+  code: number;
+  message: string;
+  result: T;
+  success: boolean;
+  timestamp: number;
+}
+export interface BaseQuery {
+  size?: number;
+  current?: number;
+}
+/**
+ * post
+ */
+export function post<T, P>(url: string, data: T): Promise<Result<P>> {
+  return http.post<T, Result<P>>(url, { data: data });
+}
+/**
+ * get
+ */
+export function get<T, P>(url: string, params?: T): Promise<Result<P>> {
+  return http.get<T, Result<P>>(url, { params: params });
+}
+/**
+ * put
+ * @param url path
+ * @param params query
+ * @param data body
+ * @returns .
+ */
+export function put<T>(
+  url: string,
+  params?: any,
+  data?: T
+): Promise<Result<T>> {
+  return http.request("put", url, { params: params, data: data });
+}
+/**
+ * 删除
+ * @param url .
+ * @param params .
+ * @param data .
+ * @returns .
+ */
+export function deleteRequest<T>(
+  url: string,
+  params?: any,
+  data?: any
+): Promise<Result<T>> {
+  return http.request("delete", url, { params: params, data: data });
+}

+ 24 - 0
agent-ui/api/monitor/cache.ts

@@ -0,0 +1,24 @@
+import { deleteRequest, get } from "../base";
+/**
+ * 列表查询
+ * @returns .
+ */
+export const queryList = () => {
+  return get("/monitor/cache");
+};
+/**
+ * 查找详情
+ * @param param .
+ * @returns .
+ */
+export const queryInfo = (param: any) => {
+  return get("/monitor/cache/query", param);
+};
+/**
+ * 移除
+ * @param param .
+ * @returns .
+ */
+export const remove = (param: any) => {
+  return deleteRequest("/monitor/cache/remove", {}, param);
+};

+ 77 - 0
agent-ui/api/monitor/quartz.ts

@@ -0,0 +1,77 @@
+import { get, Result, post, put, deleteRequest } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/monitor/quartz/job/query/page", query);
+}
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/monitor/quartz/job/query/list", query);
+}
+/**
+ *  保存
+ * @param data .
+ * @returns  .
+ */
+export function save<T>(data?: T): Promise<Result<T>> {
+  return post("/monitor/quartz/job/save", data);
+}
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns .
+ */
+export function update<T>(id: string, data?: T): Promise<Result<T>> {
+  return put(`/monitor/quartz/job/update/${id}`, {}, data);
+}
+/**
+ * 删除
+ * @param id .
+ * @returns .
+ */
+export function delByIds<T>(id: string[]): Promise<Result<T>> {
+  return deleteRequest(`/monitor/quartz/job/delete`, {}, id);
+}
+/**
+ * 检查cron表达式
+ * @param cron .
+ * @returns .
+ */
+export function checkCorn<T>(cron: string): Promise<Result<T>> {
+  return get(`/monitor/quartz/job/check/cron`, { cron: cron });
+}
+
+/**
+ * 启动
+ * @param id .
+ * @returns .
+ */
+export function resume<T>(id: string): Promise<Result<T>> {
+  return put(`/monitor/quartz/job/resume/${id}`);
+}
+
+/**
+ * 立即执行
+ * @param id .
+ * @returns .
+ */
+export function execute<T>(id: string): Promise<Result<T>> {
+  return put(`/monitor/quartz/job/execute/${id}`);
+}
+/**
+ * 根据ID删除
+ * @param id .
+ * @returns .
+ */
+export function delById<T>(id: string): Promise<Result<T>> {
+  return deleteRequest(`/monitor/quartz/job/delete`, { id: id }, {});
+}

+ 38 - 0
agent-ui/api/platform/index.ts

@@ -0,0 +1,38 @@
+import { Result, deleteRequest, get, post, put } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/agent/page", query);
+}
+
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/pay/agent/list", query);
+}
+
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function save<T>(data: any): Promise<Result<T>> {
+  return post("/sys/pay/agent/save", data);
+}
+
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function update<T>(id: string, data: any): Promise<Result<T>> {
+  return put(`/sys/pay/agent/update/${id}`, {}, data);
+}

+ 51 - 0
agent-ui/api/sys/config.ts

@@ -0,0 +1,51 @@
+import { get, Result, post, put, deleteRequest } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/config/query/page", query);
+}
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/config/query/list", query);
+}
+/**
+ *  保存
+ * @param data .
+ * @returns  .
+ */
+export function save<T>(data?: T): Promise<Result<T>> {
+  return post("/sys/config/save", data);
+}
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns .
+ */
+export function update<T>(id: string, data?: T): Promise<Result<T>> {
+  return put(`/sys/config/update/${id}`, {}, data);
+}
+/**
+ * 删除
+ * @param id .
+ * @returns .
+ */
+export function delByIds<T>(id: string[]): Promise<Result<T>> {
+  return deleteRequest(`/sys/config/delete`, {}, id);
+}
+/**
+ * 刷新缓存
+ * @param id .
+ * @returns .
+ */
+export function refreshCache<T>(id: string): Promise<Result<T>> {
+  return put(`/sys/config/refresh/${id}`, {});
+}

+ 41 - 0
agent-ui/api/sys/permission.ts

@@ -0,0 +1,41 @@
+import { BaseQuery, deleteRequest, get, post, put, Result } from "../base";
+
+/**
+ *  菜单与权限树形列表
+ * @param query 查询参数
+ * @returns 树形列表
+ */
+export function treeList<P extends BaseQuery, T>(
+  query?: P
+): Promise<Result<T>> {
+  return get("/sys/permission/tree", query);
+}
+/**
+ * 菜单树形列表
+ * @param query .
+ */
+export function treeMenus<P extends BaseQuery, T>(
+  query?: P
+): Promise<Result<T>> {
+  return get("/sys/permission/tree/menu", query);
+}
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function savePermission<T>(data: any): Promise<Result<T>> {
+  return post("/sys/permission/save", data);
+}
+/**
+ *  更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function updatePermission<T>(id: string, data: any): Promise<Result<T>> {
+  return put("/sys/permission/update", { id: id }, data);
+}
+export function deletePermission<T>(ids: string[]): Promise<Result<T>> {
+  return deleteRequest("/sys/permission/delete", {}, ids);
+}

+ 66 - 0
agent-ui/api/sys/role.ts

@@ -0,0 +1,66 @@
+import { get, Result, post, put } from "../base";
+
+/**
+ * 分页查询角色
+ * @param query .
+ * @returns .
+ */
+export function roleQueryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/role/query/page", query);
+}
+/**
+ * 查询角色列表
+ * @param query .
+ * @returns .
+ */
+export function roleQueryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/role/query/list", query);
+}
+/**
+ *  检查code是否重复
+ * @param code code
+ * @param id 需要排查的ID
+ * @returns .
+ */
+export function checkCode<T>(code: string, id?: string): Promise<Result<T>> {
+  const _data: any = { code: code };
+  if (id) {
+    _data.id = id;
+  }
+  return get("/sys/role/check/code", _data);
+}
+/**
+ *  保存角色
+ * @param data .
+ * @returns  .
+ */
+export function roleSave<T>(data?: T): Promise<Result<T>> {
+  return post("/sys/role/save", data);
+}
+/**
+ * 根据ID更新角色
+ * @param id 角色ID
+ * @param data 需要更新的信息
+ * @returns .
+ */
+export function roleUpdate<T>(id: string, data?: T): Promise<Result<T>> {
+  return put("/sys/role/update", { id: id }, data);
+}
+/**
+ * 角色权限
+ * @param id  角色ID
+ * @returns .
+ */
+export function queryPermission(id: string): Promise<Result<string[]>> {
+  return get("/sys/role/permission", { id: id });
+}
+/**
+ * 更新权限
+ * @param id 角色ID
+ * @param permissionIds 权限ID
+ * @returns .
+ */
+export function updatePermission(id: string, permissionIds?: string[]) {
+  permissionIds = permissionIds || [];
+  return put("/sys/role/permission", { id: id }, permissionIds);
+}

+ 83 - 0
agent-ui/api/sys/user.ts

@@ -0,0 +1,83 @@
+import { Result, deleteRequest, get, post, put } from "../base";
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/user/query/page", query);
+}
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("/sys/user/query/list", query);
+}
+/**
+ * 检查用户名是否存在
+ * @param username .
+ * @returns .
+ */
+export function hashUsername(username: string): Promise<Result<Boolean>> {
+  return get("/sys/user/check/username", { username: username });
+}
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function save<T>(data: any): Promise<Result<T>> {
+  return post("/sys/user/save", data);
+}
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function update<T>(id: string, data: any): Promise<Result<T>> {
+  return put(`/sys/user/update/${id}`, {}, data);
+}
+
+/**
+ * 获取用户角色ID
+ * @param userId .
+ * @returns .
+ */
+export function queryRoleIds<T>(userId: string): Promise<Result<T>> {
+  return get(`/sys/user/query/role/ids`, { id: userId });
+}
+/**
+ * 删除用户
+ * @param userIds .
+ * @returns  .
+ */
+export function del<T>(userIds: string[]): Promise<Result<T>> {
+  return deleteRequest(`/sys/user/delete`, {}, userIds);
+}
+/**
+ * 重置密码
+ * @param username .
+ * @param data .
+ * @returns .
+ */
+export function resetPasswd<T>(
+  username: string,
+  data: any
+): Promise<Result<T>> {
+  return put(`/sys/user/reset/passwd/${username}`, {}, data);
+}
+/**
+ * 更新密码
+ * @param username .
+ * @param data .
+ * @returns .
+ */
+export function changePasswd<T>(
+  username: string,
+  data: any
+): Promise<Result<T>> {
+  return put(`/sys/user/change/passwd/${username}`, {}, data);
+}

+ 47 - 0
agent-ui/src/api/merchant/index.ts

@@ -0,0 +1,47 @@
+import { Result, deleteRequest, get, post, put } from "../base";
+
+/**
+ * 分页查询
+ * @param query .
+ * @returns .
+ */
+export function queryPage<T>(query?: any): Promise<Result<T>> {
+  return get("agent/pay/merchant/page", query);
+}
+
+/**
+ * 列表查询
+ * @param query .
+ * @returns .
+ */
+export function queryList<T>(query?: any): Promise<Result<T>> {
+  return get("agent/pay/merchant/list", query);
+}
+
+/**
+ * 新增
+ * @param data .
+ * @returns .
+ */
+export function save<T>(data: any): Promise<Result<T>> {
+  return post("agent/pay/merchant/save", data);
+}
+
+/**
+ * 更新
+ * @param id .
+ * @param data .
+ * @returns  .
+ */
+export function update<T>(id: string, data: any): Promise<Result<T>> {
+  return put(`agent/pay/merchant/update/${id}`, {}, data);
+}
+
+/**
+ * 删除用户
+ * @param userIds .
+ * @returns  .
+ */
+export function del<T>(userIds: string[]): Promise<Result<T>> {
+  return deleteRequest(`agent/pay/merchant/remove`, {}, userIds);
+}

+ 19 - 0
agent-ui/src/views/merchant/index.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+import list from "./modules/list.vue";
+
+defineOptions({
+  name: "merchantManage"
+});
+const pageData: any = reactive({
+  mode: "table"
+});
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <list :mode="pageData.mode" />
+    </template>
+  </el-card>
+</template>

+ 155 - 0
agent-ui/src/views/merchant/modules/edit.vue

@@ -0,0 +1,155 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import * as $api from "@/api/merchant/index";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+
+const title = "";
+defineOptions({ name: "templateEdit" });
+const isDetail = ref(false);
+const formRef = ref<FormInstance>();
+// 初始化数据
+const initData = {
+  userName: undefined,
+  password: undefined,
+  status: 1
+};
+
+// 表单校验规则
+const formRules = {};
+
+// 页面数据
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "",
+  formLoading: false,
+  mode: "add",
+  isUpdate: false,
+  // 表单数据
+  formData: initData,
+  formRules: formRules
+});
+// 暴露给父级调用
+const emits = defineEmits(["ok", "close"]);
+// 打开弹窗
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.formData = cloneDeep(data) || initData;
+  if (mode === "edit") {
+    pageData.title = `${title} - 编辑`;
+    isDetail.value = false;
+  } else if (mode === "detail") {
+    pageData.title = `${title} - 查看`;
+    isDetail.value = true;
+  } else {
+    pageData.title = `${title} - 新增`;
+    isDetail.value = false;
+  }
+  pageData.dataSource = dataSource;
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+// 关闭弹窗
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+// 确定并关闭弹窗
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .save(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .update(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog v-model="pageData.dialogVisible" destroy-on-close :width="500">
+    <template #header>
+      <el-text class="mx-1" type="primary" size="large">{{
+        pageData.title
+      }}</el-text>
+    </template>
+    <div class="el-dialog-content">
+      <el-form
+        ref="formRef"
+        :model="pageData.formData"
+        style="width: 90%; margin: 20px auto 0"
+        label-width="auto"
+        :rules="pageData.formRules"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="代理名称" prop="userName">
+          <el-input
+            v-model="pageData.formData.userName"
+            clearable
+            placeholder="请输入名称"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input
+            v-model="pageData.formData.password"
+            type="password"
+            clearable
+            placeholder="请输入密码"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button v-if="!isDetail" @click="handleClose">取消</el-button>
+      <el-button v-if="!isDetail" type="primary" @click="handleConfirm"
+        >确认</el-button
+      >
+    </template>
+  </el-dialog>
+</template>

+ 371 - 0
agent-ui/src/views/merchant/modules/list.vue

@@ -0,0 +1,371 @@
+<script setup lang="ts">
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import Edit from "./edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { computed, onBeforeMount, onMounted, reactive, ref, watch } from "vue";
+import * as $api from "@/api/merchant/index";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+import { enableOptions } from "@/constants/constants";
+import { cloneDeep } from "@pureadmin/utils";
+
+const editRef = ref();
+
+defineOptions({
+  name: "agentList"
+});
+
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "代理商名称",
+      prop: "agentname",
+      placeholder: "请输入代理商名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      placeholder: "请选择",
+      dataSourceKey: "enableOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    query: ["user:query"],
+    save: ["user:save"],
+    update: ["user:update"],
+    del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {}
+  },
+  dataSource: {
+    enableOptions: enableOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["user:save"]
+      }
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "ID",
+        prop: "id",
+        slot: "id",
+        width: 220
+      },
+      {
+        label: "代理商名称",
+        prop: "userName",
+        slot: "userName"
+      },
+      {
+        label: "余额",
+        prop: "amount",
+        slot: "amount"
+      },
+      {
+        label: "谷歌校验器",
+        prop: "hasTpop",
+        slot: "hasTpop"
+      },
+      {
+        label: "状态",
+        prop: "status",
+        slot: "status"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      small: true,
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      editRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      break;
+    default:
+      break;
+  }
+};
+
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+/**
+ * 分页 - 改变每页条数
+ * @param val .
+ */
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+/**
+ * 分页 - 改变页码
+ * @param val .
+ */
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+/**
+ * 获取查询参数
+ */
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 获取列表数据
+ * @param page .
+ */
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $api
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+/**
+ * 查看详情
+ * @param row .
+ */
+const handleDetail = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "detail");
+};
+/**
+ * 编辑
+ * @param row .
+ */
+const handleEdit = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "edit");
+};
+/**
+ * 删除
+ * @param row .
+ */
+const handleDel = (row: any) => {
+  message.confirm(`确认删除 id 为 (${row.id}) 的数据?`).then(() => {
+    batchDel([row.id]);
+  });
+};
+/**
+ * 批量删除
+ * @param ids .
+ */
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $api.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {});
+onMounted(() => {
+  _loadData(1);
+});
+</script>
+
+<template>
+  <div>
+    <form-search
+      size="small"
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      :query-permission="pageData.permission.query"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      size="small"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]"
+          >{{ row[column.property] }}
+        </el-tooltip>
+      </template>
+      <template #enableScope="scope">
+        <el-tag v-if="scope.row.enable">启用</el-tag>
+        <el-tag v-else type="info">禁用</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            v-show="hasAuth(pageData.permission.update)"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑
+          </el-link>
+          <el-divider
+            v-show="hasAuth(pageData.permission.del)"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.del)"
+            type="primary"
+            @click="handleDel(row)"
+            >删除
+          </el-link>
+        </div>
+      </template>
+    </pure-table>
+    <edit ref="editRef" @ok="_loadData" />
+  </div>
+</template>

+ 19 - 0
agent-ui/views/agent/index.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+import list from "./modules/list.vue";
+
+defineOptions({
+  name: "agentManage"
+});
+const pageData: any = reactive({
+  mode: "table"
+});
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <list :mode="pageData.mode" />
+    </template>
+  </el-card>
+</template>

+ 155 - 0
agent-ui/views/agent/modules/edit.vue

@@ -0,0 +1,155 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import * as $api from "@/api/agent/index";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+
+const title = "";
+defineOptions({ name: "templateEdit" });
+const isDetail = ref(false);
+const formRef = ref<FormInstance>();
+// 初始化数据
+const initData = {
+  userName: undefined,
+  password: undefined,
+  status: 1
+};
+
+// 表单校验规则
+const formRules = {};
+
+// 页面数据
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "",
+  formLoading: false,
+  mode: "add",
+  isUpdate: false,
+  // 表单数据
+  formData: initData,
+  formRules: formRules
+});
+// 暴露给父级调用
+const emits = defineEmits(["ok", "close"]);
+// 打开弹窗
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.formData = cloneDeep(data) || initData;
+  if (mode === "edit") {
+    pageData.title = `${title} - 编辑`;
+    isDetail.value = false;
+  } else if (mode === "detail") {
+    pageData.title = `${title} - 查看`;
+    isDetail.value = true;
+  } else {
+    pageData.title = `${title} - 新增`;
+    isDetail.value = false;
+  }
+  pageData.dataSource = dataSource;
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+// 关闭弹窗
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+// 确定并关闭弹窗
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .save(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  $api
+    .update(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog v-model="pageData.dialogVisible" destroy-on-close :width="500">
+    <template #header>
+      <el-text class="mx-1" type="primary" size="large">{{
+        pageData.title
+      }}</el-text>
+    </template>
+    <div class="el-dialog-content">
+      <el-form
+        ref="formRef"
+        :model="pageData.formData"
+        style="width: 90%; margin: 20px auto 0"
+        label-width="auto"
+        :rules="pageData.formRules"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="代理名称" prop="userName">
+          <el-input
+            v-model="pageData.formData.userName"
+            clearable
+            placeholder="请输入名称"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input
+            v-model="pageData.formData.password"
+            type="password"
+            clearable
+            placeholder="请输入密码"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button v-if="!isDetail" @click="handleClose">取消</el-button>
+      <el-button v-if="!isDetail" type="primary" @click="handleConfirm"
+        >确认</el-button
+      >
+    </template>
+  </el-dialog>
+</template>

+ 371 - 0
agent-ui/views/agent/modules/list.vue

@@ -0,0 +1,371 @@
+<script setup lang="ts">
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import Edit from "./edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { computed, onBeforeMount, onMounted, reactive, ref, watch } from "vue";
+import * as $api from "@/api/agent/index";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+import { enableOptions } from "@/constants/constants";
+import { cloneDeep } from "@pureadmin/utils";
+
+const editRef = ref();
+
+defineOptions({
+  name: "agentList"
+});
+
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "代理商名称",
+      prop: "agentname",
+      placeholder: "请输入代理商名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      placeholder: "请选择",
+      dataSourceKey: "enableOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    query: ["user:query"],
+    save: ["user:save"],
+    update: ["user:update"],
+    del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {}
+  },
+  dataSource: {
+    enableOptions: enableOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["user:save"]
+      }
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "ID",
+        prop: "id",
+        slot: "id",
+        width: 220
+      },
+      {
+        label: "代理商名称",
+        prop: "userName",
+        slot: "userName"
+      },
+      {
+        label: "余额",
+        prop: "amount",
+        slot: "amount"
+      },
+      {
+        label: "谷歌校验器",
+        prop: "hasTpop",
+        slot: "hasTpop"
+      },
+      {
+        label: "状态",
+        prop: "status",
+        slot: "status"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      small: true,
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      editRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      break;
+    default:
+      break;
+  }
+};
+
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+/**
+ * 分页 - 改变每页条数
+ * @param val .
+ */
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+/**
+ * 分页 - 改变页码
+ * @param val .
+ */
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+/**
+ * 获取查询参数
+ */
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 获取列表数据
+ * @param page .
+ */
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $api
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+/**
+ * 查看详情
+ * @param row .
+ */
+const handleDetail = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "detail");
+};
+/**
+ * 编辑
+ * @param row .
+ */
+const handleEdit = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "edit");
+};
+/**
+ * 删除
+ * @param row .
+ */
+const handleDel = (row: any) => {
+  message.confirm(`确认删除 id 为 (${row.id}) 的数据?`).then(() => {
+    batchDel([row.id]);
+  });
+};
+/**
+ * 批量删除
+ * @param ids .
+ */
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $api.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {});
+onMounted(() => {
+  _loadData(1);
+});
+</script>
+
+<template>
+  <div>
+    <form-search
+      size="small"
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      :query-permission="pageData.permission.query"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      size="small"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]"
+          >{{ row[column.property] }}
+        </el-tooltip>
+      </template>
+      <template #enableScope="scope">
+        <el-tag v-if="scope.row.enable">启用</el-tag>
+        <el-tag v-else type="info">禁用</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            v-show="hasAuth(pageData.permission.update)"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑
+          </el-link>
+          <el-divider
+            v-show="hasAuth(pageData.permission.del)"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.del)"
+            type="primary"
+            @click="handleDel(row)"
+            >删除
+          </el-link>
+        </div>
+      </template>
+    </pure-table>
+    <edit ref="editRef" @ok="_loadData" />
+  </div>
+</template>

+ 145 - 0
agent-ui/views/business/template/hook/template-edit.vue

@@ -0,0 +1,145 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import * as api from "@/api/sys/user";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+
+const title = "";
+defineOptions({ name: "templateEdit" });
+const isDetail = ref(false);
+const formRef = ref<FormInstance>();
+// 初始化数据
+const initData = {
+  username: undefined,
+  password: undefined,
+  status: 1
+};
+
+// 表单校验规则
+const formRules = {};
+
+// 页面数据
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "",
+  formLoading: false,
+  mode: "add",
+  isUpdate: false,
+  // 表单数据
+  formData: initData,
+  formRules: formRules
+});
+// 暴露给父级调用
+const emits = defineEmits(["ok", "close"]);
+// 打开弹窗
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.formData = cloneDeep(data) || initData;
+  if (mode === "edit") {
+    pageData.title = `${title} - 编辑`;
+    isDetail.value = false;
+  } else if (mode === "detail") {
+    pageData.title = `${title} - 查看`;
+    isDetail.value = true;
+  } else {
+    pageData.title = `${title} - 新增`;
+    isDetail.value = false;
+  }
+  pageData.dataSource = dataSource;
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+// 关闭弹窗
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+// 确定并关闭弹窗
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  api
+    .save(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  api
+    .update(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog v-model="pageData.dialogVisible" destroy-on-close :width="500">
+    <template #header>
+      <el-text class="mx-1" type="primary" size="large">{{
+        pageData.title
+      }}</el-text>
+    </template>
+    <div class="el-dialog-content">
+      <el-form
+        ref="formRef"
+        :model="pageData.formData"
+        style="width: 90%; margin: 20px auto 0"
+        label-width="auto"
+        :rules="pageData.formRules"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="用户名" prop="username">
+          <el-input
+            v-model="pageData.formData.username"
+            clearable
+            placeholder="请输入名称"
+            size="small"
+            :readonly="isDetail"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button v-if="!isDetail" @click="handleClose">取消</el-button>
+      <el-button v-if="!isDetail" type="primary" @click="handleConfirm"
+        >确认</el-button
+      >
+    </template>
+  </el-dialog>
+</template>

+ 355 - 0
agent-ui/views/business/template/hook/template-list.vue

@@ -0,0 +1,355 @@
+<script setup lang="ts">
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import Edit from "./template-edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { computed, onBeforeMount, onMounted, reactive, ref, watch } from "vue";
+import * as $api from "@/api/sys/user";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+import { enableOptions } from "@/constants/constants";
+import { cloneDeep } from "@pureadmin/utils";
+
+const editRef = ref();
+defineOptions({
+  name: "templateList"
+});
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "用户名",
+      prop: "username",
+      placeholder: "请输入用户名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      placeholder: "请选择",
+      dataSourceKey: "enableOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    query: ["user:query"],
+    save: ["user:save"],
+    update: ["user:update"],
+    del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {}
+  },
+  dataSource: {
+    enableOptions: enableOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["user:save"]
+      }
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "ID",
+        prop: "id",
+        slot: "id",
+        width: 220
+      },
+      {
+        label: "用户名",
+        prop: "username",
+        slot: "username"
+      },
+      {
+        label: "状态",
+        prop: "status",
+        slot: "status"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      small: true,
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      editRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      break;
+    default:
+      break;
+  }
+};
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+/**
+ * 分页 - 改变每页条数
+ * @param val .
+ */
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+/**
+ * 分页 - 改变页码
+ * @param val .
+ */
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+/**
+ * 获取查询参数
+ */
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 获取列表数据
+ * @param page .
+ */
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $api
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+/**
+ * 查看详情
+ * @param row .
+ */
+const handleDetail = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "detail");
+};
+/**
+ * 编辑
+ * @param row .
+ */
+const handleEdit = (row: any) => {
+  editRef.value!.open(cloneDeep(row), pageData.dataSource, "edit");
+};
+/**
+ * 删除
+ * @param row .
+ */
+const handleDel = (row: any) => {
+  message.confirm(`确认删除 id 为 (${row.id}) 的数据?`).then(() => {
+    batchDel([row.id]);
+  });
+};
+/**
+ * 批量删除
+ * @param ids .
+ */
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $api.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {});
+onMounted(() => {
+  _loadData(1);
+});
+</script>
+
+<template>
+  <div>
+    <form-search
+      size="small"
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      :query-permission="pageData.permission.query"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      size="small"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]"
+          >{{ row[column.property] }}
+        </el-tooltip>
+      </template>
+      <template #enableScope="scope">
+        <el-tag v-if="scope.row.enable">启用</el-tag>
+        <el-tag v-else type="info">禁用</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            v-show="hasAuth(pageData.permission.update)"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑
+          </el-link>
+          <el-divider
+            v-show="hasAuth(pageData.permission.del)"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.del)"
+            type="primary"
+            @click="handleDel(row)"
+            >删除
+          </el-link>
+        </div>
+      </template>
+    </pure-table>
+    <edit ref="editRef" @ok="_loadData" />
+  </div>
+</template>

+ 19 - 0
agent-ui/views/business/template/index.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+import list from "./hook/template-list.vue";
+
+defineOptions({
+  name: "templateIndex"
+});
+const pageData: any = reactive({
+  mode: "table"
+});
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <list :mode="pageData.mode" />
+    </template>
+  </el-card>
+</template>

+ 18 - 0
agent-ui/views/channel/index.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import FormSearch from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import ConfigEdit from "./modules/config-edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { reactive, ref } from "vue";
+import { configTypeOptions } from "@/constants/constants";
+import * as $configApi from "@/api/sys/config";
+import { onMounted } from "vue";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+</script>
+
+<template>
+  <div>
+    通道管理
+  </div>
+</template>

+ 70 - 0
agent-ui/views/error/403.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import noAccess from "@/assets/status/403.svg?component";
+
+defineOptions({
+  name: "403"
+});
+
+const router = useRouter();
+</script>
+
+<template>
+  <div class="flex justify-center items-center h-[640px]">
+    <noAccess />
+    <div class="ml-12">
+      <p
+        class="font-medium text-4xl mb-4 dark:text-white"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 100
+          }
+        }"
+      >
+        403
+      </p>
+      <p
+        class="mb-4 text-gray-500"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 300
+          }
+        }"
+      >
+        抱歉,你无权访问该页面
+      </p>
+      <el-button
+        type="primary"
+        @click="router.push('/')"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 500
+          }
+        }"
+      >
+        返回首页
+      </el-button>
+    </div>
+  </div>
+</template>

+ 70 - 0
agent-ui/views/error/404.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import noExist from "@/assets/status/404.svg?component";
+
+defineOptions({
+  name: "404"
+});
+
+const router = useRouter();
+</script>
+
+<template>
+  <div class="flex justify-center items-center h-[640px]">
+    <noExist />
+    <div class="ml-12">
+      <p
+        class="font-medium text-4xl mb-4 dark:text-white"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 100
+          }
+        }"
+      >
+        404
+      </p>
+      <p
+        class="mb-4 text-gray-500"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 300
+          }
+        }"
+      >
+        抱歉,你访问的页面不存在
+      </p>
+      <el-button
+        type="primary"
+        @click="router.push('/')"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 500
+          }
+        }"
+      >
+        返回首页
+      </el-button>
+    </div>
+  </div>
+</template>

+ 70 - 0
agent-ui/views/error/500.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import noServer from "@/assets/status/500.svg?component";
+
+defineOptions({
+  name: "500"
+});
+
+const router = useRouter();
+</script>
+
+<template>
+  <div class="flex justify-center items-center h-[640px]">
+    <noServer />
+    <div class="ml-12">
+      <p
+        class="font-medium text-4xl mb-4 dark:text-white"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 100
+          }
+        }"
+      >
+        500
+      </p>
+      <p
+        class="mb-4 text-gray-500"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 300
+          }
+        }"
+      >
+        抱歉,服务器出错了
+      </p>
+      <el-button
+        type="primary"
+        @click="router.push('/')"
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 500
+          }
+        }"
+      >
+        返回首页
+      </el-button>
+    </div>
+  </div>
+</template>

+ 204 - 0
agent-ui/views/login/index.vue

@@ -0,0 +1,204 @@
+<script setup lang="ts">
+import Motion from "./utils/motion";
+import { useRouter } from "vue-router";
+import { message } from "@/utils/message";
+import { loginRules } from "./utils/rule";
+import { useNav } from "@/layout/hooks/useNav";
+import type { FormInstance } from "element-plus";
+import { useLayout } from "@/layout/hooks/useLayout";
+import { useAuthStoreHook } from "@/store/modules/auth";
+import { initRouter, getTopMenu } from "@/router/utils";
+import { bg, avatar, illustration } from "./utils/static";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
+import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
+
+import dayIcon from "@/assets/svg/day.svg?component";
+import darkIcon from "@/assets/svg/dark.svg?component";
+import Lock from "@iconify-icons/ri/lock-fill";
+import User from "@iconify-icons/ri/user-3-fill";
+import TypeIt from "@/components/ReTypeit";
+
+defineOptions({
+  name: "Login"
+});
+const router = useRouter();
+const loading = ref(false);
+const ruleFormRef = ref<FormInstance>();
+
+const { initStorage } = useLayout();
+initStorage();
+
+const { dataTheme, dataThemeChange } = useDataThemeChange();
+dataThemeChange();
+const { title } = useNav();
+
+const ruleForm = reactive({
+  username: "",
+  password: "",
+  remember: false
+});
+
+const onLogin = async (formEl: FormInstance | undefined) => {
+  loading.value = true;
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      // useUserStoreHook()
+      //   .loginByUsername({ username: ruleForm.username, password: "admin123" })
+      //   .then(res => {
+      //     if (res.success) {
+      //       // 获取后端路由
+      //       initRouter().then(() => {
+      //         router.push("/");
+      //         message("登录成功", { type: "success" });
+      //       });
+      //     }
+      //   });
+      useAuthStoreHook()
+        .login({
+          username: ruleForm.username,
+          password: ruleForm.password,
+          remember: ruleForm.remember
+        })
+        .then(res => {
+          if (res.success) {
+            // 获取后端路由
+            initRouter().then(() => {
+              router.push(getTopMenu(true).path);
+              message("登录成功", { type: "success" });
+            });
+          } else {
+            loading.value = false;
+          }
+        })
+        .finally(() => {
+          loading.value = false;
+        });
+    } else {
+      loading.value = false;
+      return fields;
+    }
+  });
+};
+
+/** 使用公共函数,避免`removeEventListener`失效 */
+function onkeypress({ code }: KeyboardEvent) {
+  if (code === "Enter") {
+    onLogin(ruleFormRef.value);
+  }
+}
+
+onMounted(() => {
+  window.document.addEventListener("keypress", onkeypress);
+  ruleForm.username = useAuthStoreHook().loginUser.username;
+  ruleForm.password = useAuthStoreHook().loginUser.password;
+  ruleForm.remember = useAuthStoreHook().loginUser.remember;
+});
+
+onBeforeUnmount(() => {
+  window.document.removeEventListener("keypress", onkeypress);
+});
+</script>
+
+<template>
+  <div class="select-none">
+    <img :src="bg" class="wave" />
+    <div class="flex-c absolute right-5 top-3">
+      <!-- 主题 -->
+      <el-switch
+        v-model="dataTheme"
+        inline-prompt
+        :active-icon="dayIcon"
+        :inactive-icon="darkIcon"
+        @change="dataThemeChange"
+      />
+    </div>
+    <div class="login-container">
+      <div class="img">
+        <component :is="toRaw(illustration)" />
+      </div>
+      <div class="login-box">
+        <div class="login-form">
+          <avatar class="avatar" />
+          <Motion>
+            <h2 class="outline-none">
+              <TypeIt :values="[title]" :cursor="false" :speed="150" />
+            </h2>
+            <!--            <h2 class="outline-none">{{ title }}</h2>-->
+          </Motion>
+
+          <el-form
+            ref="ruleFormRef"
+            :model="ruleForm"
+            :rules="loginRules"
+            size="large"
+          >
+            <Motion :delay="100">
+              <el-form-item
+                :rules="[
+                  {
+                    required: true,
+                    message: '请输入账号',
+                    trigger: 'blur'
+                  }
+                ]"
+                prop="username"
+              >
+                <el-input
+                  clearable
+                  v-model="ruleForm.username"
+                  placeholder="账号"
+                  :prefix-icon="useRenderIcon(User)"
+                />
+              </el-form-item>
+            </Motion>
+
+            <Motion :delay="150">
+              <el-form-item prop="password">
+                <el-input
+                  clearable
+                  show-password
+                  v-model="ruleForm.password"
+                  placeholder="密码"
+                  :prefix-icon="useRenderIcon(Lock)"
+                />
+              </el-form-item>
+            </Motion>
+            <Motion :delay="150">
+              <el-form-item prop="remember">
+                <div class="w-full h-20px flex justify-between items-center">
+                  <el-checkbox v-model="ruleForm.remember"
+                    >记住密码</el-checkbox
+                  >
+                </div>
+              </el-form-item>
+            </Motion>
+
+            <Motion :delay="250">
+              <el-button
+                class="w-full mt-4"
+                size="default"
+                type="primary"
+                :loading="loading"
+                @click="onLogin(ruleFormRef)"
+              >
+                登录
+              </el-button>
+            </Motion>
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+@import url("@/style/login.css");
+</style>
+
+<style lang="scss" scoped>
+:deep(.el-input-group__append, .el-input-group__prepend) {
+  padding: 0;
+}
+</style>

+ 40 - 0
agent-ui/views/login/utils/motion.ts

@@ -0,0 +1,40 @@
+import { h, defineComponent, withDirectives, resolveDirective } from "vue";
+
+/** 封装@vueuse/motion动画库中的自定义指令v-motion */
+export default defineComponent({
+  name: "Motion",
+  props: {
+    delay: {
+      type: Number,
+      default: 50
+    }
+  },
+  render() {
+    const { delay } = this;
+    const motion = resolveDirective("motion");
+    return withDirectives(
+      h(
+        "div",
+        {},
+        {
+          default: () => [this.$slots.default()]
+        }
+      ),
+      [
+        [
+          motion,
+          {
+            initial: { opacity: 0, y: 100 },
+            enter: {
+              opacity: 1,
+              y: 0,
+              transition: {
+                delay
+              }
+            }
+          }
+        ]
+      ]
+    );
+  }
+});

+ 30 - 0
agent-ui/views/login/utils/rule.ts

@@ -0,0 +1,30 @@
+import { reactive } from "vue";
+import type { FormRules } from "element-plus";
+
+/** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */
+export const REGEXP_PWD =
+  /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
+
+/** 登录校验 */
+const loginRules = reactive(<FormRules>{
+  password: [
+    {
+      validator: (rule, value, callback) => {
+        if (value === "") {
+          callback(new Error("请输入密码"));
+        }
+        // else if (!REGEXP_PWD.test(value)) {
+        //   callback(
+        //     new Error("密码格式应为8-18位数字、字母、符号的任意两种组合")
+        //   );
+        // }
+        else {
+          callback();
+        }
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+export { loginRules };

+ 5 - 0
agent-ui/views/login/utils/static.ts

@@ -0,0 +1,5 @@
+import bg from "@/assets/login/bg.png";
+import avatar from "@/assets/login/avatar.svg?component";
+import illustration from "@/assets/login/illustration.svg?component";
+
+export { bg, avatar, illustration };

+ 18 - 0
agent-ui/views/order/index.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import FormSearch from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import ConfigEdit from "./modules/config-edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { reactive, ref } from "vue";
+import { configTypeOptions } from "@/constants/constants";
+import * as $configApi from "@/api/sys/config";
+import { onMounted } from "vue";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+</script>
+
+<template>
+  <div>
+    订单管理
+  </div>
+</template>

+ 19 - 0
agent-ui/views/platform/index.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+
+defineOptions({
+  name: "platformSetting"
+});
+const pageData: any = reactive({
+  mode: "table"
+});
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+        <div>服务商id</div>
+        <div>密钥</div>
+        <div>公钥</div>
+        <div>Google检验器</div>
+  </el-card>
+</template>

+ 334 - 0
agent-ui/views/system/config.vue

@@ -0,0 +1,334 @@
+<script setup lang="ts">
+import FormSearch from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import ConfigEdit from "./modules/config-edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { reactive, ref } from "vue";
+import { configTypeOptions } from "@/constants/constants";
+import * as $configApi from "@/api/sys/config";
+import { onMounted } from "vue";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+const configEditRef = ref();
+const pageData: any = reactive({
+  permission: {
+    query: ["sys:config:query"],
+    save: ["sys:config:save"],
+    update: ["sys:config:update"],
+    del: ["sys:config:del"],
+    refreshCache: ["sys:config:refresh"]
+  },
+  searchParam: {
+    searchState: true,
+    searchForm: {},
+    formFields: [
+      {
+        type: "select",
+        label: "配置类型",
+        prop: "type",
+        placeholder: "请选择配置类型",
+        dataSourceKey: "configTypeOptions",
+        options: {
+          clearable: true,
+          filterable: true,
+          keys: {
+            prop: "value",
+            value: "value",
+            label: "label"
+          }
+        }
+      },
+      {
+        type: "input",
+        label: "配置项",
+        prop: "code",
+        placeholder: "请输入配置项",
+        options: {
+          clearable: true
+        }
+      }
+    ]
+  },
+  dataSource: {
+    configTypeOptions: configTypeOptions
+  },
+  btnOpts: {
+    size: "small",
+    left: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["sys:config:save"]
+      }
+      // {
+      //   key: "update",
+      //   label: "修改",
+      //   type: "success",
+      //   icon: "ep:edit",
+      //   state: false,
+      //   permission: ["sys:config:update"]
+      // }
+    ],
+    right: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    columns: [
+      {
+        label: "配置类型",
+        prop: "typeName"
+      },
+      {
+        label: "配置项",
+        prop: "code"
+      },
+      {
+        label: "配置值",
+        prop: "value"
+      },
+      {
+        label: "备注",
+        prop: "memo"
+      },
+      {
+        label: "创建时间",
+        prop: "createTime"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    list: [],
+    loading: false,
+    pagination: {
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+const handleBtnClick = (val: string) => {
+  switch (val) {
+    case "add":
+      configEditRef.value!.open({}, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+  }
+};
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+/**
+ * 查询数据
+ */
+const _loadData = (page?: number) => {
+  pageData.tableParam.loading = true;
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  $configApi
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+const handleEdit = (row: any) => {
+  configEditRef.value!.open(row, pageData.dataSource, "update");
+};
+const handleDel = (row: any) => {
+  message.confirm("是否删除当前数据").then(() => {
+    _batchDel([row.id]);
+  });
+};
+const _batchDel = (ids: string[]) => {
+  pageData.tableParam.loading = true;
+  $configApi
+    .delByIds(ids)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+const handleRefreshCache = (row: any) => {
+  message
+    .confirm("是否刷新缓存")
+    .then(() => {
+      _refreshCache(row.id);
+    })
+    .catch(() => {});
+};
+const _refreshCache = (id: string) => {
+  pageData.tableParam.loading = true;
+  $configApi
+    .refreshCache(id)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("刷新成功");
+        _loadData();
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+onMounted(() => {
+  _loadData();
+});
+defineOptions({
+  name: "SysConfig"
+});
+</script>
+
+<template>
+  <div>
+    <el-card :shadow="'never'">
+      <template #default>
+        <!--form search-->
+        <FormSearch
+          :show="pageData.searchParam.searchState"
+          :size="'default'"
+          :form-field="pageData.searchParam.formFields"
+          :query-permission="pageData.permission.query"
+          :data-source="pageData.dataSource"
+          @search-form="_updateSearchFormData"
+          @search="_searchForm"
+          @reset="_resetSearchForm"
+        />
+        <table-buttons
+          :size="pageData.btnOpts.size"
+          :left-btns="pageData.btnOpts.left"
+          :right-btns="pageData.btnOpts.right"
+          @click="handleBtnClick"
+        />
+        <pure-table
+          :columns="pageData.tableParam.columns"
+          :data="pageData.tableParam.list"
+          :border="true"
+          :stripe="true"
+          :loading="pageData.tableParam.loading"
+          :pagination="pageData.tableParam.pagination"
+          :header-row-class-name="'table-header'"
+          @page-current-change="handleChangeCurrentPage"
+          @page-size-change="handleChangePageSize"
+        >
+          <!---->
+          <template #operation="{ row }">
+            <el-link
+              v-show="hasAuth(pageData.permission.update)"
+              type="primary"
+              @click="handleEdit(row)"
+              >编辑</el-link
+            >
+            <el-divider
+              direction="vertical"
+              v-show="hasAuth(pageData.permission.refreshCache)"
+            />
+            <el-link
+              v-show="hasAuth(pageData.permission.refreshCache)"
+              type="primary"
+              @click="handleRefreshCache(row)"
+              >刷新缓存</el-link
+            >
+            <el-divider
+              v-show="hasAuth(pageData.permission.del)"
+              direction="vertical"
+            />
+            <el-link
+              v-show="hasAuth(pageData.permission.del)"
+              type="primary"
+              @click="handleDel(row)"
+              >删除</el-link
+            >
+          </template>
+        </pure-table>
+      </template>
+    </el-card>
+    <config-edit ref="configEditRef" @confirm="_loadData()" />
+  </div>
+</template>

+ 328 - 0
agent-ui/views/system/menu.vue

@@ -0,0 +1,328 @@
+<script setup lang="ts">
+import FormSearch from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import MenuEdit from "./modules/menu-edit.vue";
+import { reactive, onMounted, ref } from "vue";
+import * as permissionApi from "@/api/sys/permission";
+import { PureTable } from "@pureadmin/table";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+const menuEditRef = ref();
+const pageData: any = reactive({
+  permission: {
+    query: ["menu:query"],
+    add: ["menu:save"],
+    update: ["menu:update"],
+    delete: ["menu:del"]
+  },
+  searchState: true,
+  /*搜索字段定义 */
+  searchField: [
+    {
+      type: "input",
+      label: "菜单名称",
+      prop: "title",
+      placeholder: "请输入菜单名称"
+    }
+  ],
+  searchForm: {},
+  /*按钮 */
+  btnOpts: {
+    // add: {
+    //   show: true,
+    //   state: true,
+    //   permission: ["menu:save"]
+    // },
+    // update: {
+    //   show: true,
+    //   state: true,
+    //   permission: ["menu:update"]
+    // }
+    size: "small",
+    left: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["menu:save"]
+      }
+      // {
+      //   key: "update",
+      //   label: "修改",
+      //   type: "success",
+      //   icon: "ep:edit",
+      //   state: false,
+      //   permission: ["menu:update"]
+      // }
+    ],
+    right: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParams: {
+    /*加载 */
+    loading: false,
+    /*数据 */
+    list: [],
+    columns: [
+      {
+        label: "菜单名称",
+        prop: "title"
+      },
+      {
+        label: "菜单图标",
+        prop: "icon",
+        slot: "iconScope"
+      },
+      {
+        label: "权限标识",
+        prop: "perms"
+      },
+      {
+        label: "是否缓存",
+        prop: "keepAlive",
+        slot: "booleanScope"
+      },
+      {
+        label: "是否可见",
+        prop: "showLink",
+        slot: "booleanScope"
+      },
+      {
+        label: "菜单类型",
+        prop: "menuTypeName"
+      },
+      {
+        label: "状态",
+        prop: "enable",
+        slot: "enabledScope"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    currentData: {},
+    pagination: {
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?) => {
+  pageData.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchState = !pageData.searchState;
+};
+/**
+ * 查询数据
+ */
+const _loadData = () => {
+  pageData.tableParams.loading = true;
+  const query = getQueryParams();
+  permissionApi
+    .treeList(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParams.list = res.result || [];
+      } else {
+        message.warning(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.tableParams.loading = false;
+    });
+};
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchForm);
+  return param;
+};
+const handleBtnClick = (val: String) => {
+  switch (val) {
+    case "add":
+      _handlerAdd();
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+  }
+};
+/**
+ * 更新
+ * @param data .
+ */
+const _editMenu = (data: any) => {
+  pageData.tableParams.currentData = data;
+  menuEditRef.value!.open("update", data, "修改菜单/权限");
+};
+/**
+ * table列新增
+ * @param data .
+ */
+const _handleRowAdd = (data: any) => {
+  pageData.tableParams.currentData = data;
+  menuEditRef.value!.open("save", { parentId: data.id }, "修改菜单/权限");
+};
+const _handleRowDel = (data: any) => {
+  batchDel([data.id]);
+};
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    permissionApi.deletePermission(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+/**
+ * 新增
+ */
+const _handlerAdd = () => {
+  menuEditRef.value!.open("save", {}, "新增菜单/权限");
+};
+onMounted(() => {
+  _loadData();
+});
+defineOptions({ name: "sysMenu" });
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <!--form search-->
+      <FormSearch
+        :show="pageData.searchState"
+        :size="'default'"
+        :form-field="pageData.searchField"
+        @search-form="_updateSearchFormData"
+        @search="_searchForm"
+        @reset="_resetSearchForm"
+        :query-permission="pageData.permission.query"
+      />
+      <!--operator-->
+      <!-- <TableOperation
+        :size="'small'"
+        :add="pageData.btnOpts.add"
+        @click-search="_updateSearchState"
+        @click-refresh="_loadData"
+        @click-add="_handlerAdd"
+      /> -->
+      <table-buttons
+        :size="pageData.btnOpts.size"
+        :left-btns="pageData.btnOpts.left"
+        :right-btns="pageData.btnOpts.right"
+        @click="handleBtnClick"
+      />
+      <!--table-->
+      <pure-table
+        :loading="pageData.tableParams.loading"
+        :columns="pageData.tableParams.columns"
+        :data="pageData.tableParams.list"
+        :border="true"
+        :stripe="true"
+        :header-row-class-name="'table-header'"
+        row-key="id"
+      >
+        <template #booleanScope="scope">
+          {{ scope.row.keepAlive ? "是" : "否" }}
+        </template>
+        <template #iconScope="scope">
+          <component :is="useRenderIcon(scope.row.icon)" />
+        </template>
+        <template #enabledScope="scope">
+          <el-tag v-if="scope.row.enable">启用</el-tag>
+          <el-tag v-else type="info">禁用</el-tag>
+        </template>
+        <template #operation="scope">
+          <el-link
+            :disabled="!hasAuth(pageData.permission.update)"
+            type="primary"
+            @click="_editMenu(scope.row)"
+            >编辑</el-link
+          >
+          <el-divider direction="vertical" />
+          <el-link
+            :disabled="!hasAuth(pageData.permission.add)"
+            type="primary"
+            @click="_handleRowAdd(scope.row)"
+            >新增</el-link
+          >
+          <el-divider direction="vertical" />
+          <el-link
+            :disabled="!hasAuth(pageData.permission.delete)"
+            type="primary"
+            @click="_handleRowDel(scope.row)"
+            >删除</el-link
+          >
+        </template>
+      </pure-table>
+
+      <menu-edit ref="menuEditRef" @ok="_loadData()" />
+    </template>
+  </el-card>
+</template>
+
+<style scoped lang="scss">
+// :deep(.el-table .el-table__header-wrapper th) {
+//   word-break: break-word;
+//   background-color: #f8f8f9;
+//   color: #515a6e;
+//   height: 40px;
+//   font-size: 13px;
+// }
+</style>

+ 257 - 0
agent-ui/views/system/modules/config-edit.vue

@@ -0,0 +1,257 @@
+<script setup lang="ts">
+import { computed, reactive, ref } from "vue";
+import * as $configApi from "@/api/sys/config";
+import message from "@/utils/message";
+const formRef = ref();
+const pageData: any = reactive({
+  mode: "add",
+  modalParam: {
+    visible: false,
+    title: "新增系统配置",
+    closeOnclickModal: true
+  },
+  formParam: {
+    infoForm: {},
+    fields: [
+      {
+        type: "select",
+        label: "配置类型",
+        prop: "type",
+        placeholder: "请选择配置类型",
+        dataSourceKey: "configTypeOptions",
+        options: {
+          clearable: true,
+          filterable: true,
+          keys: {
+            prop: "value",
+            value: "value",
+            label: "label"
+          }
+        }
+      },
+      {
+        type: "input",
+        label: "配置项",
+        prop: "code",
+        placeholder: "请输入配置项",
+        max: 50,
+        options: {
+          clearable: true
+        }
+      },
+      {
+        type: "textarea",
+        label: "配置值",
+        prop: "value",
+        placeholder: "请输入配置值",
+        max: 255,
+        options: {
+          clearable: true
+        }
+      },
+      {
+        type: "input",
+        label: "备注",
+        prop: "memo",
+        placeholder: "请输入备注",
+        max: 255,
+        options: {
+          clearable: true
+        }
+      }
+    ],
+    rules: {
+      type: [{ required: true, message: "请选择配置类型", trigger: "change" }],
+      code: [{ required: true, message: "请输入配置项", trigger: "blur" }],
+      value: [{ required: true, message: "请输入配置值", trigger: "blur" }]
+    },
+    loading: false
+  },
+  dataSource: {}
+});
+const disabledField = computed(() => (item: any) => {
+  if (pageData.mode === "add") {
+    return false;
+  }
+  return item.prop === "code" || item.prop === "type";
+});
+const showBtn = computed(() => {
+  if (pageData.mode === "detail") {
+    return false;
+  }
+  return true;
+});
+const initData = () => {
+  pageData.modalParam.visible = false;
+  pageData.modalParam.title = "新增系统配置";
+  pageData.modalParam.closeOnclickModal = true;
+  pageData.formParam.infoForm = {};
+};
+const _handleClose = () => {
+  initData();
+  emits("close");
+};
+const open = (data: any, dataSource: any, mode: string) => {
+  pageData.mode = mode;
+  pageData.dataSource = dataSource;
+  pageData.formParam.infoForm = data;
+  pageData.modalParam.visible = true;
+};
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      if (pageData.mode === "add") {
+        save();
+      } else {
+        update();
+      }
+    }
+  });
+};
+const save = () => {
+  pageData.formParam.loading = true;
+  const data = pageData.formParam.infoForm;
+  const params = {
+    type: data.type,
+    code: data.code,
+    value: data.value,
+    memo: data.memo
+  };
+  $configApi
+    .save(params)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("新增成功");
+        initData();
+        emits("confirm");
+        pageData.modalParam.visible = false;
+      } else {
+        message.warning(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+const update = () => {
+  pageData.formParam.loading = true;
+  const data = pageData.formParam.infoForm;
+  const params = {
+    id: data.id,
+    type: data.type,
+    code: data.code,
+    value: data.value,
+    memo: data.memo
+  };
+  $configApi
+    .update(data.id, params)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("修改成功");
+        initData();
+        emits("confirm");
+        pageData.modalParam.visible = false;
+      } else {
+        message.warning(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+const emits = defineEmits(["close", "confirm"]);
+defineOptions({
+  name: "ConfigEdit"
+});
+defineExpose({
+  open
+});
+</script>
+
+<template>
+  <div>
+    <el-drawer
+      v-model="pageData.modalParam.visible"
+      :title="pageData.modalParam.title"
+      destroy-on-close
+      modal
+      :close-on-click-modal="pageData.modalParam.closeOnclickModal"
+      :before-close="_handleClose"
+    >
+      <template #header>
+        <span style="font-weight: 700">{{ pageData.modalParam.title }}</span>
+      </template>
+      <el-form
+        :model="pageData.formParam.infoForm"
+        :rules="pageData.formParam.rules"
+        label-position="right"
+        ref="formRef"
+        v-loading="pageData.formParam.loading"
+      >
+        <el-form-item
+          v-for="(item, index) in pageData.formParam.fields"
+          :key="index"
+          :prop="item.prop"
+          :label="item.label"
+        >
+          <template v-if="item.type === 'input'">
+            <el-input
+              v-model="pageData.formParam.infoForm[item.prop]"
+              :clearable="item.options?.clearable ?? true"
+              :max="item.max ?? 255"
+              :disabled="disabledField(item)"
+            />
+          </template>
+          <template v-else-if="item.type === 'textarea'">
+            <el-input
+              rows="25"
+              v-model="pageData.formParam.infoForm[item.prop]"
+              :clearable="item.options?.clearable ?? true"
+              :max="item.max ?? 255"
+              :disabled="disabledField(item)"
+              type="textarea"
+            />
+          </template>
+          <template v-else-if="item.type === 'select'">
+            <el-select
+              v-model="pageData.formParam.infoForm[item.prop]"
+              :filterable="item.options?.filterable ?? true"
+              :clearable="item.options?.clearable ?? true"
+              :placeholder="item.placeholder"
+              style="width: 100%"
+              :disabled="disabledField(item)"
+            >
+              <el-option
+                v-for="su in pageData.dataSource[item.dataSourceKey]"
+                :key="su[item.options?.keys?.prop ?? 'value']"
+                :label="su[item.options?.keys?.label ?? 'label']"
+                :value="su[item.options?.keys?.value ?? 'value']"
+              />
+            </el-select>
+          </template>
+        </el-form-item>
+        <!---->
+      </el-form>
+      <template #footer>
+        <el-button plain @click="_handleClose">取 消</el-button>
+        <el-popconfirm
+          title="确认提交?"
+          @confirm="handleConfirm"
+          confirm-button-text="确定"
+          cancel-button-text="取消"
+        >
+          <template #reference>
+            <el-button
+              type="primary"
+              plain
+              v-show="showBtn"
+              v-loading="pageData.formParam.loading"
+              >提 交</el-button
+            >
+          </template>
+        </el-popconfirm>
+      </template>
+      <!---->
+    </el-drawer>
+  </div>
+</template>

+ 431 - 0
agent-ui/views/system/modules/menu-edit.vue

@@ -0,0 +1,431 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import {
+  MenuTypeOptions,
+  ifNumberOptions,
+  ifOptions,
+  ifEnableOptions
+} from "@/constants/constants";
+import * as permissionApi from "@/api/sys/permission";
+import FromQuestion from "@/components/form-question/index.vue";
+import { IconSelect } from "@/components/icon-select";
+import type { FormInstance } from "element-plus";
+import { message } from "@/utils/message";
+import { cloneDeep } from "@pureadmin/utils";
+const formRef = ref<FormInstance>();
+const pageData: any = reactive({
+  isShow: false,
+  title: "新增菜单/权限",
+  formLoading: false,
+  treeMenus: [],
+  menuIcon: "",
+  formData: {
+    menuType: 0,
+    isFrame: 0,
+    showLink: true,
+    enable: true,
+    keepAlive: false,
+    showParent: true
+  },
+  formRules: {},
+  dirFormRules: {
+    menuType: [
+      { required: true, message: "请选择菜单类型", trigger: "change" }
+    ],
+    title: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
+    rank: [{ required: true, message: "请输入排序", trigger: "blur" }],
+    path: [{ required: true, message: "请输入路由地址", trigger: "blur" }]
+  },
+  menuFormRules: {
+    menuType: [
+      { required: true, message: "请选择菜单类型", trigger: "change" }
+    ],
+    title: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
+    rank: [{ required: true, message: "请输入排序", trigger: "blur" }],
+    path: [{ required: true, message: "请输入路由地址", trigger: "blur" }]
+  },
+  permissionFormRules: {
+    menuType: [
+      { required: true, message: "请选择菜单类型", trigger: "change" }
+    ],
+    title: [{ required: true, message: "请输入权限名称", trigger: "blur" }],
+    perms: [{ required: true, message: "请输入权限标识", trigger: "blur" }],
+    rank: [{ required: true, message: "请输入排序", trigger: "blur" }]
+  }
+});
+const emits = defineEmits(["close", "ok"]);
+const open = (action: "save" | "update", data?: any, title?: string) => {
+  pageData.title = title;
+  const menuType = data?.menuType | 0;
+  if (menuType === 0) {
+    pageData.formRules = pageData.dirFormRules;
+  } else if (menuType === 1) {
+    pageData.formRules = pageData.menuFormRules;
+  } else {
+    pageData.formRules = pageData.permissionFormRules;
+  }
+  queryTreeMenu();
+  switch (action) {
+    case "save":
+      pageData.formData = {
+        menuType: 0,
+        isFrame: 0,
+        showLink: true,
+        enable: true,
+        keepAlive: false,
+        showParent: true
+      };
+      if (data) {
+        pageData.formData = Object.assign(pageData.formData, data);
+      }
+
+      break;
+    case "update":
+      pageData.formData = cloneDeep(data);
+      break;
+  }
+  pageData.isShow = true;
+};
+/**
+ * 查询菜单树形列表
+ */
+const queryTreeMenu = () => {
+  pageData.formLoading = true;
+  permissionApi
+    .treeMenus({})
+    .then((res: any) => {
+      if (res.success) {
+        pageData.treeMenus = res.result;
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const handleChangeMenuType = (val: number) => {
+  setTimeout(function () {
+    formRef.value!.clearValidate([
+      "menuType",
+      "title",
+      "rank",
+      "path",
+      "perms"
+    ]);
+  }, 30);
+
+  if (val === 0) {
+    pageData.formRules = pageData.dirFormRules;
+  } else if (val === 1) {
+    pageData.formRules = pageData.menuFormRules;
+  } else {
+    pageData.formRules = pageData.permissionFormRules;
+  }
+};
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      if (pageData.formData.id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const data = cloneDeep(pageData.formData);
+  permissionApi
+    .updatePermission(pageData.formData.id, data)
+    .then((res: any) => {
+      if (res.success) {
+        _handleConfirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const data = cloneDeep(pageData.formData);
+  permissionApi
+    .savePermission(data)
+    .then((res: any) => {
+      if (res.success) {
+        _handleConfirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+/**
+ * 关闭之前会调用
+ */
+const _handleClose = () => {
+  pageData.isShow = false;
+  emits("close");
+};
+const _handleConfirm = () => {
+  pageData.isShow = false;
+  emits("ok");
+};
+defineOptions({ name: "MenuEdit" });
+defineExpose({ open });
+</script>
+
+<template>
+  <el-drawer
+    size="40%"
+    v-model="pageData.isShow"
+    destroy-on-close
+    modal
+    :close-on-click-modal="false"
+    :title="pageData.title"
+    :before-close="_handleClose"
+  >
+    <el-form
+      ref="formRef"
+      :model="pageData.formData"
+      v-loading="pageData.formLoading"
+      :rules="pageData.formRules"
+      label-position="left"
+    >
+      <el-form-item label="菜单类型" prop="menuType">
+        <el-radio-group
+          v-model="pageData.formData.menuType"
+          @change="handleChangeMenuType"
+        >
+          <el-radio-button
+            v-for="(item, index) in MenuTypeOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="菜单图标"
+        prop="icon"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <icon-select v-model="pageData.formData.icon" style="width: 100%" />
+      </el-form-item>
+      <el-form-item
+        label="是否外链"
+        prop="isFrame"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <template #label>
+          <from-question
+            label="是否外链"
+            description="选择是外链则路由地址需要以`http(s)://`开头"
+          />
+        </template>
+        <el-radio-group v-model="pageData.formData.isFrame">
+          <el-radio-button
+            v-for="(item, index) in ifNumberOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="菜单可见"
+        props="showLink"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <template #label>
+          <from-question
+            label="菜单可见"
+            description="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问"
+          />
+        </template>
+        <el-radio-group v-model="pageData.formData.showLink">
+          <el-radio-button
+            v-for="(item, index) in ifOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="菜单名称"
+        prop="title"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <el-input
+          v-model="pageData.formData.title"
+          clearable
+          placeholder="请输入菜单名称"
+        />
+      </el-form-item>
+      <el-form-item
+        label="是否缓存"
+        prop="keepAlive"
+        v-if="pageData.formData.menuType === 1"
+      >
+        <template #label>
+          <from-question
+            label="是否缓存"
+            description="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致"
+          />
+        </template>
+        <el-radio-group v-model="pageData.formData.keepAlive">
+          <el-radio-button
+            v-for="(item, index) in ifOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="显示父级菜单"
+        prop="showParent"
+        v-if="pageData.formData.menuType === 1"
+      >
+        <el-radio-group v-model="pageData.formData.showParent">
+          <el-radio-button
+            v-for="(item, index) in ifOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="组件名称"
+        prop="name"
+        v-if="pageData.formData.menuType === 1"
+      >
+        <el-input
+          v-model="pageData.formData.name"
+          clearable
+          placeholder="组件名称"
+        />
+      </el-form-item>
+      <el-form-item
+        label="组件路径"
+        prop="component"
+        v-if="pageData.formData.menuType === 1"
+      >
+        <template #label>
+          <from-question
+            label="组件路径"
+            description="访问的组件路径,如:`system/user/index`,默认在`views`目录下"
+          />
+        </template>
+        <el-input
+          v-model="pageData.formData.component"
+          clearable
+          placeholder="请输入组件路径"
+        />
+      </el-form-item>
+      <el-form-item
+        label="路由地址"
+        prop="path"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <template #label>
+          <from-question
+            label="路由地址"
+            description="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头"
+          />
+        </template>
+        <el-input
+          v-model="pageData.formData.path"
+          clearable
+          placeholder="请输入路由地址"
+        />
+      </el-form-item>
+      <el-form-item
+        label="菜单状态"
+        prop="enable"
+        v-if="pageData.formData.menuType !== 2"
+      >
+        <template #label>
+          <from-question
+            label="菜单状态"
+            description="选择停用则路由将不会出现在侧边栏,也不能被访问"
+          />
+        </template>
+        <el-radio-group v-model="pageData.formData.enable">
+          <el-radio-button
+            v-for="(item, index) in ifEnableOptions"
+            :label="item.value"
+            :key="index"
+            >{{ item.label }}</el-radio-button
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item
+        label="权限名称"
+        prop="title"
+        v-if="pageData.formData.menuType === 2"
+      >
+        <el-input
+          v-model="pageData.formData.title"
+          clearable
+          placeholder="请输入权限名称"
+        />
+      </el-form-item>
+      <el-form-item
+        label="权限标识"
+        prop="perms"
+        v-if="pageData.formData.menuType === 2"
+      >
+        <template #label>
+          <from-question
+            label="权限标识"
+            description="控制器中定义的权限字符,如:@PreAuthorize(`@permission.hashPermission('menu:query')`)"
+          />
+        </template>
+        <el-input
+          v-model="pageData.formData.perms"
+          clearable
+          placeholder="请输入权限标识"
+        />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="rank">
+        <el-input-number
+          style="width: 100%"
+          v-model="pageData.formData.rank"
+          :min="0"
+          :max="999"
+          controls-position="right"
+        />
+      </el-form-item>
+      <el-form-item label="上级菜单" prop="parentId">
+        <el-tree-select
+          v-model="pageData.formData.parentId"
+          :data="pageData.treeMenus"
+          :props="{ children: 'children', label: 'title' }"
+          node-key="id"
+          accordion
+          filterable
+          check-strictly
+          clearable
+          style="width: 100%"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="demo-drawer__footer">
+        <el-button @click="_handleClose">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">确认</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<style scoped></style>

+ 238 - 0
agent-ui/views/system/modules/reset-passwd.vue

@@ -0,0 +1,238 @@
+<script setup lang="ts">
+import { reactive, computed, ref } from "vue";
+import { useUserStoreHook } from "@/store/modules/user";
+import * as $userApi from "@/api/sys/user";
+import message from "@/utils/message";
+const userStore = useUserStoreHook();
+const form = ref();
+const pageData: any = reactive({
+  mode: "self",
+  modalParam: {
+    visible: false,
+    closeOnClickModal: true
+  },
+  currentData: {},
+  formParam: {
+    infoForm: {},
+    loading: false
+  }
+});
+const validatePass = (rule: any, value: any, callback: any) => {
+  if (value === "") {
+    callback(new Error("请输入新密码"));
+  } else if (
+    value === pageData.formParam.infoForm.oldPassword &&
+    pageData.mode === "self"
+  ) {
+    callback(new Error("输入新密码与旧密码不能相同"));
+  } else {
+    callback();
+  }
+};
+const validatePass2 = (rule: any, value: any, callback: any) => {
+  if (value === "") {
+    callback(new Error("请再次输入新密码"));
+  } else if (value !== pageData.formParam.infoForm.newPassword) {
+    callback(new Error("两次输入密码不正确"));
+  } else {
+    callback();
+  }
+};
+const username = computed(() => {
+  if (pageData.currentData) {
+    return pageData.currentData.username;
+  }
+  return userStore.username;
+});
+const formRules = computed(() => {
+  const rules: any = {
+    newPassword: [
+      {
+        required: true,
+        message: "请输入新密码",
+        trigger: "blur"
+      },
+      { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+      { validator: validatePass, trigger: "blur" }
+    ],
+    confirmPassword: [
+      {
+        required: true,
+        message: "请输入确认密码",
+        trigger: "blur"
+      },
+      { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+      { validator: validatePass2, trigger: "blur" }
+    ]
+  };
+  if (username.value === userStore.username) {
+    rules.oldPassword = [
+      {
+        required: true,
+        message: "请输入原密码",
+        trigger: "blur"
+      }
+    ];
+  }
+  return rules;
+});
+const formFields = computed(() => {
+  const fields: any[] = [
+    {
+      type: "input",
+      label: "新密码",
+      prop: "newPassword",
+      placeholder: "请输入新密码"
+    },
+    {
+      type: "input",
+      label: "确认密码",
+      prop: "confirmPassword",
+      placeholder: "请输入确认密码"
+    }
+  ];
+  if (username.value === userStore.username) {
+    fields.splice(1, 0, {
+      type: "input",
+      label: "原密码",
+      prop: "oldPassword",
+      placeholder: "请输入原密码"
+    });
+  }
+  return fields;
+});
+const open = (data?: any) => {
+  pageData.currentData = data;
+  if (data) {
+    pageData.mode = "other";
+  }
+  pageData.modalParam.visible = true;
+};
+const initData = () => {
+  pageData.formParam.infoForm = {};
+  pageData.currentData = undefined;
+  pageData.modalParam.visible = false;
+};
+const handleClose = () => {
+  initData();
+  emits("close");
+};
+const handleConfirm = () => {
+  form.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      if (pageData.mode === "self") {
+        changePasswd();
+      } else {
+        resetPasswd();
+      }
+    }
+  });
+};
+const changePasswd = () => {
+  const data = pageData.formParam.infoForm;
+  pageData.formParam.loading = true;
+  $userApi
+    .changePasswd(username.value, data)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("更新密码成功,下次登陆生效");
+        initData();
+        emits("confirm");
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+const resetPasswd = () => {
+  const data = pageData.formParam.infoForm;
+  pageData.formParam.loading = true;
+  $userApi
+    .resetPasswd(username.value, data)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("重置密码成功,下次登陆生效");
+        initData();
+        emits("confirm");
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+const emits = defineEmits(["close", "confirm"]);
+defineExpose({
+  open
+});
+defineOptions({
+  name: "UserResetPasswd"
+});
+</script>
+
+<template>
+  <div>
+    <el-dialog
+      title="重置密码"
+      v-model="pageData.modalParam.visible"
+      :close-on-click-modal="pageData.modalParam.closeOnClickModal"
+      width="30%"
+      @close="handleClose"
+    >
+      <!---->
+      <div class="flex justify-center items-center m-5">
+        <div class="font-bold text-lg">{{ username }}</div>
+      </div>
+      <el-form
+        :model="pageData.formParam.infoForm"
+        :rules="formRules"
+        ref="form"
+        label-width="100px"
+        class="w-full"
+        v-loading="pageData.formParam.loading"
+      >
+        <el-form-item
+          v-for="(item, index) in formFields"
+          :key="index"
+          :prop="item.prop"
+          :label="item.label"
+        >
+          <template v-if="item.type === 'input'">
+            <el-input
+              v-model="pageData.formParam.infoForm[item.prop]"
+              :placeholder="item.placeholder"
+              type="password"
+              show-password
+              clearable
+            />
+          </template>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button plain @click="handleClose">取消</el-button>
+          <el-popconfirm
+            title="确认提交?"
+            @confirm="handleConfirm"
+            confirm-button-text="确定"
+            cancel-button-text="取消"
+          >
+            <template #reference>
+              <el-button
+                type="primary"
+                plain
+                v-loading="pageData.formParam.loading"
+                >提 交</el-button
+              >
+            </template>
+          </el-popconfirm>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped></style>

+ 174 - 0
agent-ui/views/system/modules/role-edit.vue

@@ -0,0 +1,174 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import { enableOptions } from "@/constants/constants";
+import * as roleApi from "@/api/sys/role";
+import { message } from "@/utils/message";
+import type { FormInstance } from "element-plus";
+import { cloneDeep } from "@pureadmin/utils";
+const checkRoleCode = (rule: any, value: any, callback: any) => {
+  if (value === "") {
+    callback(new Error("请输入角色编码"));
+  } else {
+    const id = pageData.formData.id;
+    roleApi.checkCode(value, id).then((res: any) => {
+      if (res.success) {
+        if (res.result) {
+          callback(new Error("编码重复"));
+        }
+      }
+    });
+    callback();
+  }
+};
+const formRef = ref<FormInstance>();
+const pageData: any = reactive({
+  dialogVisible: false,
+  title: "新增角色",
+  formLoading: false,
+  isUpdate: false,
+  formData: {
+    roleCode: "",
+    roleName: "",
+    description: "",
+    enable: 1
+  },
+  formRules: {
+    roleCode: [
+      { required: true, message: "请输入角色编码", trigger: "blur" },
+      { validator: checkRoleCode, trigger: "blur" }
+    ],
+    roleName: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
+    enable: [{ required: true, message: "请选择状态", trigger: "change" }]
+  }
+});
+const emits = defineEmits(["ok", "close"]);
+const open = (data?: any, title?: string) => {
+  pageData.formData = data || {
+    roleCode: "",
+    roleName: "",
+    description: "",
+    enable: 1
+  };
+  pageData.title = title || "新增角色";
+  pageData.isUpdate = !!pageData.formData.id;
+  pageData.dialogVisible = true;
+};
+const handleClose = () => {
+  pageData.dialogVisible = false;
+  emits("close");
+};
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      const { id } = pageData.formData;
+      if (id) {
+        _update();
+      } else {
+        _save();
+      }
+    } else {
+      message("表单校验失败", { type: "warning" });
+    }
+  });
+};
+const _save = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  roleApi
+    .roleSave(_data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _update = () => {
+  pageData.formLoading = true;
+  const _data = cloneDeep(pageData.formData);
+  roleApi
+    .roleUpdate(pageData.formData.id, _data)
+    .then((res: any) => {
+      if (res.success) {
+        _confirm();
+      } else {
+        message(res.message, { type: "warning" });
+      }
+    })
+    .finally(() => {
+      pageData.formLoading = false;
+    });
+};
+const _confirm = () => {
+  pageData.dialogVisible = false;
+  emits("ok");
+};
+defineExpose({ open });
+defineOptions({ name: "RoleEdit" });
+</script>
+
+<template>
+  <el-dialog
+    v-model="pageData.dialogVisible"
+    :title="pageData.title"
+    destroy-on-close
+    :width="800"
+  >
+    <div class="el-dialog-content">
+      <el-form
+        :model="pageData.formData"
+        style="margin-top: 20px; width: 70%"
+        label-width="auto"
+        :rules="pageData.formRules"
+        ref="formRef"
+        :loading="pageData.formLoading"
+      >
+        <el-form-item label="角色编码" prop="roleCode">
+          <el-input
+            v-model="pageData.formData.roleCode"
+            clearable
+            placeholder="请输入角色编码"
+            :disabled="pageData.isUpdate"
+          />
+        </el-form-item>
+        <el-form-item label="角色名称" prop="roleName">
+          <el-input
+            v-model="pageData.formData.roleName"
+            clearable
+            placeholder="请输入角色名称"
+          />
+        </el-form-item>
+        <el-form-item label="描述" prop="description">
+          <el-input
+            type="textarea"
+            v-model="pageData.formData.description"
+            :autosize="{ minRows: 3 }"
+            clearable
+            placeholder="文本输入,不得超过200"
+            max="200"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="enable">
+          <el-radio-group v-model="pageData.formData.enable">
+            <el-radio
+              v-for="(item, index) in enableOptions"
+              :label="item.value"
+              :key="index"
+              >{{ item.label }}</el-radio
+            >
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button @click="handleClose">取消</el-button>
+      <el-button type="primary" @click="handleConfirm">确认</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped></style>

+ 170 - 0
agent-ui/views/system/modules/role-permission.vue

@@ -0,0 +1,170 @@
+<script setup lang="ts">
+import { reactive, onMounted, ref } from "vue";
+import { treeList as permissionTree } from "@/api/sys/permission";
+import * as roleApi from "@/api/sys/role";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { Result } from "@/api/base";
+import message from "@/utils/message";
+import { ElMessageBox, type ElTree } from "element-plus";
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const pageData: any = reactive({
+  isShow: false,
+  title: "角色权限",
+  treeLoading: false,
+  role: {},
+  rolePermission: [],
+  permissionTree: []
+});
+const emits = defineEmits(["close", "ok"]);
+const open = (role: any) => {
+  pageData.role = role;
+  loadRolePermission();
+  pageData.isShow = true;
+};
+
+const handleSelect = (all: boolean) => {
+  all
+    ? treeRef.value!.setCheckedNodes(pageData.permissionTree)
+    : treeRef.value!.setCheckedNodes([]);
+};
+const handleExpand = (all: boolean) => {
+  all
+    ? foldAll(pageData.permissionTree, true)
+    : foldAll(pageData.permissionTree, false);
+};
+const foldAll = (data: any, expanded: boolean) => {
+  data.forEach((el: any) => {
+    const node = treeRef.value!.store.nodesMap[el.id];
+    if (node) {
+      node.expanded = expanded;
+    }
+    el.children && el.children.length > 0 ? foldAll(el.children, expanded) : "";
+  });
+};
+const _handleClose = () => {
+  pageData.isShow = false;
+  emits("close");
+};
+const handleConfirm = (close?: boolean) => {
+  const keys = treeRef.value!.getCheckedKeys(true);
+  ElMessageBox.confirm("确认是否更新授权", "更新授权", { type: "warning" })
+    .then(() => {
+      _updatePermission(keys, close);
+    })
+    .catch(() => {});
+};
+const _updatePermission = (permissionIds: any, close?: boolean) => {
+  pageData.treeLoading = true;
+  roleApi
+    .updatePermission(pageData.role.id, permissionIds)
+    .then((res: any) => {
+      if (res.success) {
+        message.success("更新授权成功");
+        if (close) {
+          _handleClose();
+        } else {
+          loadRolePermission();
+        }
+      } else {
+        message.warning(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.treeLoading = false;
+    });
+};
+onMounted(() => {
+  loadPermission();
+});
+const loadPermission = () => {
+  permissionTree<any, any>({ enable: 1 }).then((res: any) => {
+    if (res.success) {
+      pageData.permissionTree = res.result;
+    }
+  });
+};
+const loadRolePermission = () => {
+  pageData.treeLoading = true;
+  roleApi
+    .queryPermission(pageData.role.id)
+    .then((res: Result<string[]>) => {
+      if (res.success) {
+        pageData.rolePermission = res.result;
+        treeRef.value!.setCheckedKeys(res.result);
+      } else {
+        message.warning(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.treeLoading = false;
+    });
+};
+defineExpose({ open });
+defineOptions({ name: "RolePermission" });
+</script>
+
+<template>
+  <el-drawer
+    v-model="pageData.isShow"
+    destroy-on-close
+    modal
+    :close-on-click-modal="false"
+    :title="pageData.title"
+    :before-close="_handleClose"
+    class="el-drawer-header"
+  >
+    <div class="flex flex-col h-full">
+      <el-form label-position="top">
+        <el-form-item label="所拥有的权限:">
+          <el-tree
+            ref="treeRef"
+            :data="pageData.permissionTree"
+            :props="{ label: 'title', children: 'children' }"
+            node-key="id"
+            show-checkbox
+            default-expand-all
+            :check-strictly="false"
+            style="width: 100%"
+            v-loading="pageData.treeLoading"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <div class="flex justify-between items-center">
+        <div class="right">
+          <el-dropdown trigger="click" placement="top">
+            <el-button>
+              批量操作 <component :is="useRenderIcon('ep:arrow-up')"
+            /></el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="handleSelect(true)"
+                  >全部勾选</el-dropdown-item
+                >
+                <el-dropdown-item @click="handleSelect(false)"
+                  >取消全部</el-dropdown-item
+                >
+                <el-dropdown-item @click="handleExpand(true)"
+                  >展开所有</el-dropdown-item
+                >
+                <el-dropdown-item @click="handleExpand(false)"
+                  >合并所有</el-dropdown-item
+                >
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+        <div class="left">
+          <el-button @click="_handleClose">取消</el-button>
+          <el-button type="primary" @click="handleConfirm(false)" plain
+            >确认</el-button
+          >
+          <el-button type="primary" @click="handleConfirm(true)"
+            >确认并关闭</el-button
+          >
+        </div>
+      </div>
+    </template>
+  </el-drawer>
+</template>

+ 457 - 0
agent-ui/views/system/modules/user-edit.vue

@@ -0,0 +1,457 @@
+<script setup lang="ts">
+import { reactive, ref, computed } from "vue";
+import * as $userApi from "@/api/sys/user";
+import message from "@/utils/message";
+const formRef = ref();
+
+const checkUsername = (rule: any, value: any, callback: any) => {
+  if (value === "") {
+    callback(new Error("请输入帐号"));
+  } else {
+    if (pageData.mode === "add") {
+      $userApi.hashUsername(value).then((res: any) => {
+        if (res.success) {
+          if (res.result) {
+            callback(new Error("帐号已存在,请重新输入"));
+          } else {
+            callback();
+          }
+        } else {
+          callback(new Error("帐号已存在,请重新输入"));
+        }
+      });
+    }
+  }
+};
+const validatePass2 = (rule: any, value: any, callback: any) => {
+  if (value === "") {
+    callback(new Error("请再次输入密码"));
+  } else if (value !== pageData.formParam.infoForm.password) {
+    callback(new Error("两次输入密码不正确"));
+  } else {
+    callback();
+  }
+};
+const pageData: any = reactive({
+  mode: "add",
+  modalParam: {
+    visible: false,
+    title: "用户新增",
+    closeOnclickModal: true
+  },
+  formParam: {
+    formFields: [
+      {
+        type: "input",
+        label: "帐号",
+        prop: "username",
+        placeholder: "请输入帐号"
+      },
+      {
+        type: "input",
+        label: "姓名",
+        prop: "nickname",
+        placeholder: "请输入姓名"
+      },
+      {
+        type: "input",
+        label: "密码",
+        prop: "password",
+        placeholder: "请输入密码",
+        options: {
+          type: "password",
+          showPassword: true
+        }
+      },
+      {
+        type: "input",
+        label: "确认密码",
+        prop: "confirmPassword",
+        placeholder: "请输入确认密码",
+        options: {
+          type: "password",
+          showPassword: true
+        }
+      },
+      {
+        type: "select",
+        label: "分配角色",
+        prop: "roleIds",
+        placeholder: "请选择分配角色",
+        dataSourceKey: "roleList",
+        options: {
+          filterable: true,
+          multiple: true,
+          keys: {
+            prop: "id",
+            value: "id",
+            label: "roleName"
+          }
+        }
+      },
+      {
+        type: "select",
+        label: "状态",
+        prop: "enable",
+        placeholder: "请选择",
+        dataSourceKey: "enabledOptions",
+        options: {
+          keys: {
+            label: "label",
+            value: "value",
+            prop: "value"
+          },
+          filterable: true
+        }
+      }
+    ],
+    infoForm: {},
+    rules: {
+      orgId: [{ required: true, message: "请选择所属机构", trigger: "change" }],
+      username: [
+        { required: true, message: "请输入帐号", trigger: "blur" },
+        { min: 4, max: 20, message: "长度在 4 到 20 个字符", trigger: "blur" },
+        { validator: checkUsername, trigger: "blur" },
+        {
+          pattern: /^[a-zA-Z0-9]{4,20}$/,
+          message: "只能输入4-20个字母、数字",
+          trigger: "blur"
+        }
+      ],
+      nickname: [
+        { required: true, message: "请输入姓名", trigger: "blur" },
+        { min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" }
+      ],
+      enable: [{ required: true, message: "请选择状态", trigger: "change" }],
+      password: [
+        { required: true, message: "请输入密码", trigger: "blur" },
+        { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }
+      ],
+      confirmPassword: [
+        { required: true, message: "请输入确认密码", trigger: "blur" },
+        { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+        { validator: validatePass2, trigger: "blur" }
+      ]
+    },
+    loading: false
+  },
+  dataSource: {
+    enabledOptions: [],
+    roleList: []
+  }
+});
+const showFormItem = computed(() => (item: any) => {
+  if (pageData.mode === "add") {
+    return true;
+  }
+  return item.prop !== "confirmPassword" && item.prop !== "password";
+});
+const formFields = computed(() => {
+  const fields: any[] = [
+    {
+      type: "input",
+      label: "帐号",
+      prop: "username",
+      placeholder: "请输入帐号"
+    },
+    {
+      type: "input",
+      label: "姓名",
+      prop: "nickname",
+      placeholder: "请输入姓名"
+    },
+    {
+      type: "select",
+      label: "分配角色",
+      prop: "roleIds",
+      placeholder: "请选择分配角色",
+      dataSourceKey: "roleList",
+      options: {
+        filterable: true,
+        multiple: true,
+        keys: {
+          prop: "id",
+          value: "id",
+          label: "roleName"
+        }
+      }
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "enable",
+      placeholder: "请选择",
+      dataSourceKey: "enabledOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (pageData.mode === "add") {
+    fields.splice(
+      3,
+      0,
+      {
+        type: "input",
+        label: "密码",
+        prop: "password",
+        placeholder: "请输入密码",
+        options: {
+          type: "password",
+          showPassword: true
+        }
+      },
+      {
+        type: "input",
+        label: "确认密码",
+        prop: "confirmPassword",
+        placeholder: "请输入确认密码",
+        options: {
+          type: "password",
+          showPassword: true
+        }
+      }
+    );
+  }
+  return fields;
+});
+const formRules = computed(() => {
+  const rule: any = {
+    orgId: [{ required: true, message: "请选择所属机构", trigger: "change" }],
+    username: [
+      { required: true, message: "请输入帐号", trigger: "blur" },
+      { min: 4, max: 20, message: "长度在 4 到 20 个字符", trigger: "blur" },
+
+      {
+        pattern: /^[a-zA-Z0-9]{4,20}$/,
+        message: "只能输入4-20个字母、数字",
+        trigger: "blur"
+      }
+    ],
+    nickname: [
+      { required: true, message: "请输入姓名", trigger: "blur" },
+      { min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" }
+    ],
+    enable: [{ required: true, message: "请选择状态", trigger: "change" }]
+  };
+
+  if (pageData.mode === "add") {
+    rule["password"] = [
+      { required: true, message: "请输入密码", trigger: "blur" },
+      { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }
+    ];
+    rule["confirmPassword"] = [
+      { required: true, message: "请输入确认密码", trigger: "blur" },
+      { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+      { validator: validatePass2, trigger: "blur" }
+    ];
+    rule.username.push({
+      validator: checkUsername,
+      trigger: "blur"
+    });
+  }
+
+  return rule;
+});
+const disabledFields = computed(() => (item: any) => {
+  if (pageData.mode === "add") {
+    return false;
+  }
+  if (pageData.mode === "detail") {
+    return true;
+  }
+  return item.prop === "orgId" || item.prop === "username";
+});
+const showBtn = computed(() => {
+  if (pageData.mode === "detail") {
+    return false;
+  }
+  return true;
+});
+const queryUserRoleId = () => {
+  if (!pageData.formParam.infoForm.id) {
+    return;
+  }
+  $userApi.queryRoleIds(pageData.formParam.infoForm.id).then((res: any) => {
+    if (res.success) {
+      pageData.formParam.infoForm.roleIds = res.result;
+    }
+  });
+};
+const open = (infoForm: any, dataSource: any, mode: string) => {
+  pageData.mode = mode;
+  pageData.formParam.infoForm = infoForm;
+  pageData.dataSource = dataSource;
+  if (mode !== "add") {
+    queryUserRoleId();
+  }
+  if (mode === "edit") {
+    pageData.modalParam.title = "会员编辑";
+  }
+  if (mode === "detail") {
+    pageData.modalParam.closeOnclickModal = true;
+  } else {
+    pageData.modalParam.closeOnclickModal = false;
+  }
+  pageData.modalParam.visible = true;
+};
+const initData = () => {
+  pageData.formParam.infoForm = { enable: 1 };
+  pageData.formParam.loading = false;
+  pageData.modalParam.closeOnclickModal = true;
+  pageData.mode = "add";
+  pageData.modalParam.visible = false;
+};
+const _handleClose = () => {
+  initData();
+  emits("close");
+};
+const handleCancel = () => {
+  pageData.modalParam.visible = false;
+};
+const handleConfirm = () => {
+  formRef.value!.validate((isValid: boolean) => {
+    if (isValid) {
+      if (pageData.mode === "add") {
+        save();
+      } else {
+        update();
+      }
+    }
+  });
+};
+const save = () => {
+  pageData.formParam.loading = true;
+  const params = pageData.formParam.infoForm;
+  $userApi
+    .save(params)
+    .then((res: any) => {
+      if (res.success) {
+        initData();
+        emits("confirm");
+        message.success("保存成功");
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+const update = () => {
+  pageData.formParam.loading = true;
+  const params = pageData.formParam.infoForm;
+  $userApi
+    .update(params.id, params)
+    .then((res: any) => {
+      if (res.success) {
+        initData();
+        emits("confirm");
+        message.success("保存成功");
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      pageData.formParam.loading = false;
+    });
+};
+defineOptions({
+  name: "UserEdit"
+});
+const emits = defineEmits(["close", "confirm"]);
+defineExpose({ open });
+</script>
+
+<template>
+  <div>
+    <el-drawer
+      v-model="pageData.modalParam.visible"
+      :title="pageData.modalParam.title"
+      destroy-on-close
+      modal
+      :close-on-click-modal="pageData.modalParam.closeOnclickModal"
+      :before-close="_handleClose"
+    >
+      <template #header>
+        <span style="font-weight: 700">{{ pageData.modalParam.title }}</span>
+      </template>
+      <el-form
+        :model="pageData.formParam.infoForm"
+        :rules="formRules"
+        label-position="left"
+        ref="formRef"
+        v-loading="pageData.formParam.loading"
+      >
+        <el-form-item
+          v-for="(item, index) in formFields"
+          :label="item.label"
+          :prop="item.prop"
+          :key="index"
+          class="form-row"
+          v-show="showFormItem(item)"
+        >
+          <template v-if="item.type === 'input'">
+            <el-input
+              v-model="pageData.formParam.infoForm[item.prop]"
+              clearable
+              :type="item.options?.type ?? 'text'"
+              :show-password="item.options?.showPassword ?? false"
+              :disabled="disabledFields(item)"
+            />
+          </template>
+          <template v-if="item.type === 'select'">
+            <el-select
+              style="width: 1000%"
+              v-model="pageData.formParam.infoForm[item.prop]"
+              :clearable="item.options?.clearable ?? false"
+              :filterable="item.options?.filterable ?? false"
+              :multiple="item.options?.multiple ?? false"
+              :disabled="disabledFields(item)"
+            >
+              <el-option
+                v-for="val in pageData.dataSource[item.dataSourceKey]"
+                :label="val[item.options?.keys?.label ?? 'label']"
+                :key="val[item.options?.keys?.value ?? 'value']"
+                :value="val[item.options?.keys?.value ?? 'value']"
+              />
+            </el-select>
+          </template>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-popconfirm
+          title="确定放弃编辑?"
+          ok-text="确定"
+          cancel-text="取消"
+          @confirm="handleCancel"
+        >
+          <template #reference>
+            <el-button v-show="showBtn" plain>取 消</el-button>
+          </template>
+        </el-popconfirm>
+        <el-popconfirm
+          title="确认提交?"
+          @confirm="handleConfirm"
+          confirm-button-text="确定"
+          cancel-button-text="取消"
+        >
+          <template #reference>
+            <el-button
+              type="primary"
+              plain
+              v-show="showBtn"
+              v-loading="pageData.formParam.loading"
+              >提 交</el-button
+            >
+          </template>
+        </el-popconfirm>
+      </template>
+    </el-drawer>
+  </div>
+</template>

+ 420 - 0
agent-ui/views/system/modules/user-list.vue

@@ -0,0 +1,420 @@
+<script setup lang="ts">
+import FormSearch, { type FormField } from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import UserResetPasswd from "./reset-passwd.vue";
+import UserEdit from "./user-edit.vue";
+import { PureTable } from "@pureadmin/table";
+import { enableOptions } from "@/constants/constants";
+import { reactive, computed, ref, onMounted } from "vue";
+import * as $userApi from "@/api/sys/user";
+import { roleQueryList } from "@/api/sys/role";
+import { onBeforeMount } from "vue";
+import { hasAuth } from "@/router/utils";
+import message from "@/utils/message";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { watch } from "vue";
+const userEditRef = ref();
+const userResetPasswdRef = ref();
+defineOptions({
+  name: "userList"
+});
+const props: any = defineProps({
+  mode: {
+    required: false,
+    type: String,
+    default: "table"
+  }
+});
+const searchFormFields = computed((): FormField[] => {
+  const fields: FormField[] = [
+    {
+      type: "input",
+      label: "帐号",
+      prop: "username",
+      placeholder: "请输入帐号模糊查询"
+    },
+    {
+      type: "input",
+      label: "姓名",
+      prop: "nickname",
+      placeholder: "请输入姓名模糊查询"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "enable",
+      placeholder: "请选择",
+      dataSourceKey: "enabledOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ];
+  if (props.mode === "table") {
+    return fields;
+  } else {
+    return fields;
+  }
+});
+const pageData: any = reactive({
+  mode: "table",
+  permission: {
+    query: ["user:query:page"],
+    save: ["user:save"],
+    update: ["user:update"],
+    del: ["user:del"]
+  },
+  searchParam: {
+    searchState: true,
+    fields: [
+      {
+        type: "input",
+        label: "帐号",
+        prop: "username",
+        placeholder: "请输入帐号模糊查询"
+      },
+      {
+        type: "input",
+        label: "姓名",
+        prop: "nickname",
+        placeholder: "请输入姓名模糊查询"
+      },
+      {
+        type: "select",
+        label: "状态",
+        prop: "enable",
+        placeholder: "请选择",
+        dataSourceKey: "enabledOptions",
+        options: {
+          keys: {
+            label: "label",
+            value: "value",
+            prop: "value"
+          },
+          filterable: true
+        }
+      }
+    ],
+    searchForm: {}
+  },
+  dataSource: {
+    enabledOptions: enableOptions
+  },
+  /*按钮 */
+  btnOpts: {
+    size: "small",
+    leftBtns: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["user:save"]
+      }
+      // {
+      //   key: "update",
+      //   label: "修改",
+      //   type: "success",
+      //   icon: "ep:edit",
+      //   state: false,
+      //   permission: ["user:update"]
+      // }
+    ],
+    rightBtns: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "switch",
+        label: "切换",
+        icon: "ep:switch",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParam: {
+    list: [],
+    columns: [
+      {
+        label: "用户帐号",
+        prop: "username",
+        slot: "username"
+      },
+      {
+        label: "姓名",
+        prop: "nickname",
+        slot: "ellipsis"
+      },
+      {
+        label: "状态",
+        prop: "enable",
+        slot: "enableScope"
+      },
+      {
+        label: "更新人",
+        prop: "updateUser",
+        slot: "ellipsis"
+      },
+      {
+        label: "更新时间",
+        prop: "updateTime",
+        slot: "ellipsis"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    loading: false,
+    pagination: {
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+const btnClickHandle = (val: string) => {
+  switch (val) {
+    case "add":
+      userEditRef.value!.open({ enable: 1 }, pageData.dataSource, "add");
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+    case "switch":
+      switchTable();
+      break;
+    default:
+      break;
+  }
+};
+const switchTable = () => {
+  emits("switchMode", props.mode === "table" ? "tree" : "table");
+};
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchParam.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?) => {
+  pageData.searchParam.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchParam.searchState = !pageData.searchParam.searchState;
+};
+const handleChangePageSize = (val: any) => {
+  pageData.tableParam.pagination.pageSize = val;
+  _loadData();
+};
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParam.pagination.currentPage = val;
+  _loadData();
+};
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchParam.searchForm);
+  param.current = pageData.tableParam.pagination.currentPage;
+  param.size = pageData.tableParam.pagination.pageSize;
+  return param;
+};
+const _loadData = (page?: number) => {
+  const query = getQueryParams();
+  if (page) {
+    query.current = page;
+  }
+  pageData.tableParam.loading = true;
+  $userApi
+    .queryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParam.list = res.result.records;
+        pageData.tableParam.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParam.loading = false;
+    });
+};
+const queryRoles = () => {
+  roleQueryList({ enable: 1 }).then((res: any) => {
+    if (res.success) {
+      pageData.dataSource.roleList = res.result;
+    }
+  });
+};
+const handleEdit = (row: any) => {
+  userEditRef.value!.open(row, pageData.dataSource, "edit");
+};
+const handleDel = (row: any) => {
+  message.confirm("确认删除当前数据").then(() => {
+    batchDel([row.id]);
+  });
+};
+const batchDel = (ids: string[]) => {
+  if (ids && ids.length > 0) {
+    $userApi.del(ids).then((res: any) => {
+      if (res.success) {
+        message.success("删除成功");
+        _loadData();
+      } else {
+        message.warning(res.message);
+      }
+    });
+  }
+};
+const handleResetPasswd = (row: any) => {
+  userResetPasswdRef.value!.open(row);
+};
+const handleDetail = (row: any) => {
+  userEditRef.value!.open(row, pageData.dataSource, "detail");
+};
+watch(
+  () => props.orgInfo,
+  val => {
+    if (val) {
+      _loadData();
+    }
+  }
+);
+onBeforeMount(() => {
+  queryRoles();
+});
+onMounted(() => {
+  _loadData(1);
+});
+const emits = defineEmits(["switchMode"]);
+</script>
+
+<template>
+  <div>
+    <form-search
+      :show="pageData.searchParam.searchState"
+      :form-field="searchFormFields"
+      :data-source="pageData.dataSource"
+      @search-form="_updateSearchFormData"
+      @search="_searchForm"
+      @reset="_resetSearchForm"
+      :query-permission="pageData.permission.query"
+    />
+    <!--operate-->
+    <table-buttons
+      :size="pageData.btnOpts.size"
+      :left-btns="pageData.btnOpts.leftBtns"
+      :right-btns="pageData.btnOpts.rightBtns"
+      @click="btnClickHandle"
+    />
+    <!--table-->
+    <pure-table
+      :data="pageData.tableParam.list"
+      :columns="pageData.tableParam.columns"
+      row-key="id"
+      border
+      stripe
+      :header-row-class-name="'table-header'"
+      :loading="pageData.tableParam.loading"
+      :pagination="pageData.tableParam.pagination"
+      @page-current-change="handleChangeCurrentPage"
+      @page-size-change="handleChangePageSize"
+    >
+      <template #ellipsis="{ row, column }">
+        <el-tooltip placement="top-start" :content="row[column.property]">{{
+          row[column.property]
+        }}</el-tooltip>
+      </template>
+      <template #username="{ row }">
+        <el-link type="primary" @click="handleDetail(row)">{{
+          row.username
+        }}</el-link>
+      </template>
+      <template #enableScope="scope">
+        <el-tag v-if="scope.row.enable">启用</el-tag>
+        <el-tag v-else type="info">禁用</el-tag>
+      </template>
+      <template #operation="{ row }">
+        <div class="flex justify-center items-center">
+          <el-link
+            v-show="hasAuth(pageData.permission.update) && row.isManage !== 1"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑</el-link
+          >
+          <el-divider
+            v-show="hasAuth(pageData.permission.del) && row.isManage !== 1"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.del) && row.isManage !== 1"
+            type="primary"
+            @click="handleDel(row)"
+            >删除</el-link
+          >
+          <el-divider v-show="row.isManage !== 1" direction="vertical" />
+          <el-dropdown v-show="row.isManage !== 1">
+            <el-link type="primary"
+              >更 多
+              <el-icon class="el-icon--right">
+                <component :is="useRenderIcon('ep:arrow-down')" />
+              </el-icon>
+            </el-link>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="handleResetPasswd(row)"
+                  >重置密码</el-dropdown-item
+                >
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </template>
+    </pure-table>
+    <user-edit ref="userEditRef" @confirm="_loadData" />
+    <user-reset-passwd ref="userResetPasswdRef" />
+  </div>
+</template>

+ 325 - 0
agent-ui/views/system/role.vue

@@ -0,0 +1,325 @@
+<script setup lang="ts">
+import FormSearch from "@/components/opts/form-search.vue";
+import TableButtons from "@/components/opts/btns2.vue";
+import { PureTable } from "@pureadmin/table";
+import { hasAuth } from "@/router/utils";
+import { enableConvert, enabledBooleanConvert } from "@/constants/convert";
+import { enableOptions } from "@/constants/constants";
+import * as roleApi from "@/api/sys/role";
+import RoleEdit from "./modules/role-edit.vue";
+import RolePermission from "./modules/role-permission.vue";
+import { reactive, onMounted, ref } from "vue";
+const roleEditRef = ref();
+const rolePermissionRef = ref();
+const pageData: any = reactive({
+  permission: {
+    query: [],
+    add: ["role:save"],
+    update: ["role:update"],
+    delete: ["role:del"],
+    permission: ["role:permission"]
+  },
+  /**
+   * 是否显示搜索
+   */
+  searchState: true,
+  /*搜索字段定义 */
+  searchField: [
+    {
+      type: "input",
+      label: "角色编码",
+      prop: "roleCode",
+      placeholder: "精准查询角色编码"
+    },
+    {
+      type: "input",
+      label: "角色名称",
+      prop: "roleName",
+      placeholder: "模糊查询角色名称"
+    },
+    {
+      type: "select",
+      label: "状态",
+      prop: "enable",
+      placeholder: "请选择",
+      dataSourceKey: "enabledOptions",
+      options: {
+        keys: {
+          label: "label",
+          value: "value",
+          prop: "value"
+        },
+        filterable: true
+      }
+    }
+  ],
+  dataSource: {
+    enabledOptions: enableOptions
+  },
+  searchForm: {},
+  /*按钮 */
+  btnOpts: {
+    left: [
+      {
+        key: "add",
+        label: "新增",
+        type: "primary",
+        icon: "ep:plus",
+        state: true,
+        permission: ["role:save"]
+      }
+      // {
+      //   key: "update",
+      //   label: "修改",
+      //   type: "success",
+      //   icon: "ep:edit",
+      //   state: false,
+      //   permission: ["role:update"]
+      // }
+    ],
+    right: [
+      {
+        key: "search",
+        label: "查询",
+        icon: "ep:search",
+        state: true,
+        options: {
+          circle: true
+        }
+      },
+      {
+        key: "refresh",
+        label: "刷新",
+        icon: "ep:refresh",
+        state: true,
+        options: {
+          circle: true
+        }
+      }
+    ]
+  },
+  tableParams: {
+    /**
+     * table column
+     */
+    columns: [
+      {
+        label: "角色编码",
+        prop: "roleCode"
+      },
+      {
+        label: "角色名称",
+        prop: "roleName"
+      },
+      {
+        label: "类型",
+        props: "isSystem",
+        slot: "roleType"
+      },
+      {
+        label: "状态",
+        prop: "enable",
+        slot: "roleEnable"
+      },
+      {
+        label: "创建时间",
+        prop: "createTime"
+      },
+      {
+        label: "操作",
+        fixed: "right",
+        slot: "operation"
+      }
+    ],
+    /**
+     * table data
+     */
+    list: [],
+    /*加载 */
+    loading: false,
+    /*数据 */
+    tableData: [],
+    currentData: {},
+    pagination: {
+      pageSize: 10,
+      defaultPageSize: 10,
+      currentPage: 1,
+      background: true,
+      total: 0
+    }
+  }
+});
+/**
+ *  更新搜索表单
+ * @param data .
+ */
+const _updateSearchFormData = (data: any) => {
+  pageData.searchForm = data;
+};
+/**
+ * 点击搜索按钮
+ */
+const _searchForm = (data: any) => {
+  pageData.searchForm = data;
+  _loadData();
+};
+/**
+ * 重置
+ */
+const _resetSearchForm = (data?: any) => {
+  pageData.searchForm = data;
+};
+/**
+ * 更新搜索表达的状态
+ */
+const _updateSearchState = () => {
+  pageData.searchState = !pageData.searchState;
+};
+const handleChangePageSize = (val: any) => {
+  pageData.tableParams.pagination.pageSize = val;
+  _loadData();
+};
+const handleChangeCurrentPage = (val: any) => {
+  pageData.tableParams.pagination.currentPage = val;
+  _loadData();
+};
+const getQueryParams = () => {
+  const sqp = {};
+  const param = Object.assign(sqp, pageData.searchForm);
+  param.current = pageData.tableParams.pagination.currentPage;
+  param.size = pageData.tableParams.pagination.pageSize;
+  return param;
+};
+/**
+ * 查询数据
+ */
+const _loadData = () => {
+  pageData.tableParams.loading = true;
+  const query = getQueryParams();
+  roleApi
+    .roleQueryPage(query)
+    .then((res: any) => {
+      if (res.success) {
+        pageData.tableParams.list = res.result.records;
+        pageData.tableParams.pagination.total = Number(res.result.total);
+      }
+    })
+    .finally(() => {
+      pageData.tableParams.loading = false;
+    });
+};
+const handleBtnClick = (val: String) => {
+  switch (val) {
+    case "add":
+      _handlerAdd();
+      break;
+    case "update":
+      break;
+    case "search":
+      _updateSearchState();
+      break;
+    case "refresh":
+      _loadData();
+      break;
+  }
+};
+/**
+ * 新增
+ */
+const _handlerAdd = () => {
+  roleEditRef.value!.open();
+};
+const handleEdit = (data: any) => {
+  roleEditRef.value!.open(data, "修改角色");
+};
+const handleAuthorize = (row: any) => {
+  rolePermissionRef.value!.open(row);
+};
+onMounted(() => {
+  _loadData();
+});
+defineOptions({ name: "sysRole" });
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <form-search
+        :show="pageData.searchState"
+        :form-field="pageData.searchField"
+        :data-source="pageData.dataSource"
+        @search-form="_updateSearchFormData"
+        @search="_searchForm"
+        @reset="_resetSearchForm"
+        :query-permission="pageData.permission.query"
+      />
+      <table-buttons
+        :size="'small'"
+        :left-btns="pageData.btnOpts.left"
+        :right-btns="pageData.btnOpts.right"
+        @click="handleBtnClick"
+      />
+      <pure-table
+        :columns="pageData.tableParams.columns"
+        :data="pageData.tableParams.list"
+        :border="true"
+        :stripe="true"
+        :loading="pageData.tableParams.loading"
+        :pagination="pageData.tableParams.pagination"
+        :header-row-class-name="'table-header'"
+        @page-current-change="handleChangeCurrentPage"
+        @page-size-change="handleChangePageSize"
+      >
+        <template #roleType="{ row }">
+          <el-tag
+            effect="plain"
+            :type="enabledBooleanConvert(row.isSystem) ? 'danger' : ''"
+            >{{
+              enabledBooleanConvert(row.isSystem) ? "内置" : "自定义"
+            }}</el-tag
+          >
+        </template>
+        <template #roleEnable="{ row }">
+          <el-tag
+            effect="plain"
+            :type="row.enable === 1 ? 'success' : 'info'"
+            >{{ enableConvert(row.enable) }}</el-tag
+          >
+        </template>
+        <template #operation="{ row }">
+          <el-link
+            v-show="
+              hasAuth(pageData.permission.permission) && row.isSystem != 1
+            "
+            type="primary"
+            @click="handleAuthorize(row)"
+            >授权</el-link
+          >
+          <el-divider
+            v-show="
+              hasAuth(pageData.permission.permission) && row.isSystem != 1
+            "
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.update) && row.isSystem !== 1"
+            type="primary"
+            @click="handleEdit(row)"
+            >编辑</el-link
+          >
+          <el-divider
+            v-show="hasAuth(pageData.permission.delete) && row.isSystem !== 1"
+            direction="vertical"
+          />
+          <el-link
+            v-show="hasAuth(pageData.permission.delete) && row.isSystem !== 1"
+            type="primary"
+            >删除</el-link
+          >
+        </template>
+      </pure-table>
+      <role-edit ref="roleEditRef" @ok="_loadData" />
+      <role-permission ref="rolePermissionRef" />
+    </template>
+  </el-card>
+</template>

+ 40 - 0
agent-ui/views/system/user.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { reactive } from "vue";
+import UserList from "./modules/user-list.vue";
+defineOptions({
+  name: "user"
+});
+const pageData: any = reactive({
+  mode: "table",
+  orgInfo: {}
+});
+const handleSwitchMode = (mode: String) => {
+  pageData.mode = mode;
+  if (pageData.mode === "table") {
+    pageData.orgInfo = {};
+  }
+};
+</script>
+
+<template>
+  <el-card :shadow="'never'">
+    <template #default>
+      <template v-if="pageData.mode !== 'table'">
+        <el-col :span="19">
+          <user-list
+            :mode="pageData.mode"
+            :org-info="pageData.orgInfo"
+            @switch-mode="handleSwitchMode"
+          />
+        </el-col>
+      </template>
+      <template v-else>
+        <user-list
+          :mode="pageData.mode"
+          :org-info="pageData.orgInfo"
+          @switch-mode="handleSwitchMode"
+        />
+      </template>
+    </template>
+  </el-card>
+</template>

+ 24 - 0
agent-ui/views/welcome/index.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+defineOptions({
+  name: "Welcome"
+});
+</script>
+
+<template>
+  <div class="max-w-full">
+    <el-card style="width: 480px; border-radius: 8px" shadow="hover"
+      >Always</el-card
+    >
+  </div>
+</template>
+
+<style scoped>
+.grid-content {
+  border-radius: 4px;
+  min-height: 130px;
+}
+
+.ep-bg-purple {
+  background: #fff;
+}
+</style>