mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat(ui): 新增系统管理员
This commit is contained in:
		@@ -3,7 +3,7 @@ import tokenHandler from "./token";
 | 
			
		||||
 | 
			
		||||
const { _tokenData, refreshToken, setCurRequest } = tokenHandler();
 | 
			
		||||
 | 
			
		||||
const createInstance = (baseURL: string = (import.meta as any).env.VITE_PROXY_BASE_URL) => {
 | 
			
		||||
const createInstance = (baseURL: string) => {
 | 
			
		||||
 | 
			
		||||
  const instance = axios.create({
 | 
			
		||||
    baseURL,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										104
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -127,110 +127,6 @@ importers:
 | 
			
		||||
        specifier: ^1.8.27
 | 
			
		||||
        version: 1.8.27(typescript@5.3.3)
 | 
			
		||||
 | 
			
		||||
  projects/vue-mobile:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      pinia:
 | 
			
		||||
        specifier: ^2.1.7
 | 
			
		||||
        version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
 | 
			
		||||
      vue:
 | 
			
		||||
        specifier: ^3.4.15
 | 
			
		||||
        version: 3.4.21(typescript@5.3.3)
 | 
			
		||||
      vue-router:
 | 
			
		||||
        specifier: ^4.2.5
 | 
			
		||||
        version: 4.3.0(vue@3.4.21)
 | 
			
		||||
    devDependencies:
 | 
			
		||||
      '@rushstack/eslint-patch':
 | 
			
		||||
        specifier: ^1.3.3
 | 
			
		||||
        version: 1.7.2
 | 
			
		||||
      '@tsconfig/node20':
 | 
			
		||||
        specifier: ^20.1.2
 | 
			
		||||
        version: 20.1.2
 | 
			
		||||
      '@types/node':
 | 
			
		||||
        specifier: ^20.11.10
 | 
			
		||||
        version: 20.11.24
 | 
			
		||||
      '@vitejs/plugin-vue':
 | 
			
		||||
        specifier: ^5.0.3
 | 
			
		||||
        version: 5.0.4(vite@5.1.5)(vue@3.4.21)
 | 
			
		||||
      '@vitejs/plugin-vue-jsx':
 | 
			
		||||
        specifier: ^3.1.0
 | 
			
		||||
        version: 3.1.0(vite@5.1.5)(vue@3.4.21)
 | 
			
		||||
      '@vue/eslint-config-typescript':
 | 
			
		||||
        specifier: ^12.0.0
 | 
			
		||||
        version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
 | 
			
		||||
      '@vue/tsconfig':
 | 
			
		||||
        specifier: ^0.5.1
 | 
			
		||||
        version: 0.5.1
 | 
			
		||||
      eslint:
 | 
			
		||||
        specifier: ^8.49.0
 | 
			
		||||
        version: 8.57.0
 | 
			
		||||
      eslint-plugin-vue:
 | 
			
		||||
        specifier: ^9.17.0
 | 
			
		||||
        version: 9.22.0(eslint@8.57.0)
 | 
			
		||||
      npm-run-all2:
 | 
			
		||||
        specifier: ^6.1.1
 | 
			
		||||
        version: 6.1.2
 | 
			
		||||
      typescript:
 | 
			
		||||
        specifier: ~5.3.0
 | 
			
		||||
        version: 5.3.3
 | 
			
		||||
      vite:
 | 
			
		||||
        specifier: ^5.0.11
 | 
			
		||||
        version: 5.1.5(@types/node@20.11.24)(less@4.2.0)
 | 
			
		||||
      vue-tsc:
 | 
			
		||||
        specifier: ^1.8.27
 | 
			
		||||
        version: 1.8.27(typescript@5.3.3)
 | 
			
		||||
 | 
			
		||||
  projects/vue-web:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      pinia:
 | 
			
		||||
        specifier: ^2.1.7
 | 
			
		||||
        version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
 | 
			
		||||
      vue:
 | 
			
		||||
        specifier: ^3.4.15
 | 
			
		||||
        version: 3.4.21(typescript@5.3.3)
 | 
			
		||||
      vue-router:
 | 
			
		||||
        specifier: ^4.2.5
 | 
			
		||||
        version: 4.3.0(vue@3.4.21)
 | 
			
		||||
    devDependencies:
 | 
			
		||||
      '@rushstack/eslint-patch':
 | 
			
		||||
        specifier: ^1.3.3
 | 
			
		||||
        version: 1.7.2
 | 
			
		||||
      '@tsconfig/node20':
 | 
			
		||||
        specifier: ^20.1.2
 | 
			
		||||
        version: 20.1.2
 | 
			
		||||
      '@types/node':
 | 
			
		||||
        specifier: ^20.11.10
 | 
			
		||||
        version: 20.11.24
 | 
			
		||||
      '@vitejs/plugin-vue':
 | 
			
		||||
        specifier: ^5.0.3
 | 
			
		||||
        version: 5.0.4(vite@5.1.5)(vue@3.4.21)
 | 
			
		||||
      '@vitejs/plugin-vue-jsx':
 | 
			
		||||
        specifier: ^3.1.0
 | 
			
		||||
        version: 3.1.0(vite@5.1.5)(vue@3.4.21)
 | 
			
		||||
      '@vue/eslint-config-typescript':
 | 
			
		||||
        specifier: ^12.0.0
 | 
			
		||||
        version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
 | 
			
		||||
      '@vue/tsconfig':
 | 
			
		||||
        specifier: ^0.5.1
 | 
			
		||||
        version: 0.5.1
 | 
			
		||||
      eslint:
 | 
			
		||||
        specifier: ^8.49.0
 | 
			
		||||
        version: 8.57.0
 | 
			
		||||
      eslint-plugin-vue:
 | 
			
		||||
        specifier: ^9.17.0
 | 
			
		||||
        version: 9.22.0(eslint@8.57.0)
 | 
			
		||||
      npm-run-all2:
 | 
			
		||||
        specifier: ^6.1.1
 | 
			
		||||
        version: 6.1.2
 | 
			
		||||
      typescript:
 | 
			
		||||
        specifier: ~5.3.0
 | 
			
		||||
        version: 5.3.3
 | 
			
		||||
      vite:
 | 
			
		||||
        specifier: ^5.0.11
 | 
			
		||||
        version: 5.1.5(@types/node@20.11.24)(less@4.2.0)
 | 
			
		||||
      vue-tsc:
 | 
			
		||||
        specifier: ^1.8.27
 | 
			
		||||
        version: 1.8.27(typescript@5.3.3)
 | 
			
		||||
 | 
			
		||||
packages:
 | 
			
		||||
 | 
			
		||||
  /@aashutoshrathi/word-wrap@1.2.6:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,2 @@
 | 
			
		||||
VITE_PROXY_BASE_URL="/api"
 | 
			
		||||
VITE_TARGET_URL="http://172.22.11.2:5678"
 | 
			
		||||
VITE_SOCKET_IO_URL="http://172.28.1.3:8899"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,2 @@
 | 
			
		||||
VITE_PROXY_BASE_URL=""
 | 
			
		||||
VITE_TARGET_URL="/"
 | 
			
		||||
VITE_SOCKET_IO_URL="/"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ type OriginProps = SwitchInstance["$props"];
 | 
			
		||||
interface Props extends /* @vue-ignore */ OriginProps {
 | 
			
		||||
  modelValue: boolean | string | number;
 | 
			
		||||
  api: (params?: any) => Promise<BaseResponse<any>>;
 | 
			
		||||
  onSuccess?: (res?: any) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>();
 | 
			
		||||
@@ -23,8 +24,9 @@ const _value = computed({
 | 
			
		||||
 | 
			
		||||
const onBeforeChange = async (params) => {
 | 
			
		||||
  try {
 | 
			
		||||
    await props.api({ ...params, value: !_value.value });
 | 
			
		||||
    const res = await props.api({ ...params, value: !_value.value });
 | 
			
		||||
    Message.success("操作成功");
 | 
			
		||||
    props?.onSuccess?.(res);
 | 
			
		||||
    return true;
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(err);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import type { BaseResponse } from "@gpt-vue/packages/type";
 | 
			
		||||
 | 
			
		||||
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/admin/upload";
 | 
			
		||||
 | 
			
		||||
export const instance = createInstance()
 | 
			
		||||
export const instance = createInstance(import.meta.env.VITE_PROXY_BASE_URL)
 | 
			
		||||
 | 
			
		||||
instance.interceptors.request.use((config) => {
 | 
			
		||||
  config.headers[__AUTH_KEY] = localStorage.getItem(__AUTH_KEY);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import {
 | 
			
		||||
  IconLock,
 | 
			
		||||
  IconCodepen,
 | 
			
		||||
  IconWechatpay,
 | 
			
		||||
  IconRobot,
 | 
			
		||||
} from "@arco-design/web-vue/es/icon";
 | 
			
		||||
 | 
			
		||||
const menu = [
 | 
			
		||||
@@ -123,6 +124,15 @@ const menu = [
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import("@/views/LoginLog.vue"),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/sysAdmin",
 | 
			
		||||
    name: "SysAdmin",
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: "系统管理员",
 | 
			
		||||
      icon: IconRobot,
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import("@/views/SysAdmin/SysAdminContainer.vue"),
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default menu;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,115 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { dateFormat } from "@gpt-vue/packages/utils";
 | 
			
		||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
 | 
			
		||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
 | 
			
		||||
import ConfirmSwitch from "@/components/ConfirmSwitch.vue";
 | 
			
		||||
import usePopup from "@/composables/usePopup";
 | 
			
		||||
import SysAdminForm from "./SysAdminForm.vue";
 | 
			
		||||
import SysAdminResetPWD from "./SysAdminResetPWD.vue";
 | 
			
		||||
import { getList, save, remove, resetPass } from "./api";
 | 
			
		||||
 | 
			
		||||
const columns: SearchTableColumns[] = [
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "username",
 | 
			
		||||
    title: "账号",
 | 
			
		||||
    search: {
 | 
			
		||||
      valueType: "input",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "last_login_ip",
 | 
			
		||||
    title: "最后登录IP",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "last_login_at",
 | 
			
		||||
    title: "最后登录时间",
 | 
			
		||||
    render: ({ record }) => dateFormat(record.last_login_at),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "created_at",
 | 
			
		||||
    title: "创建时间",
 | 
			
		||||
    render: ({ record }) => dateFormat(record.created_at),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "updated_at",
 | 
			
		||||
    title: "更新时间",
 | 
			
		||||
    render: ({ record }) => dateFormat(record.updated_at),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "status",
 | 
			
		||||
    title: "状态",
 | 
			
		||||
    slotName: "switch",
 | 
			
		||||
    width: 80,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "操作",
 | 
			
		||||
    slotName: "actions",
 | 
			
		||||
    fixed: "right",
 | 
			
		||||
    width: 180,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const openFormModal = usePopup(SysAdminForm, {
 | 
			
		||||
  nodeProps: ([_, record]) => ({ record }),
 | 
			
		||||
  popupProps: ([reload, record], exposed) => ({
 | 
			
		||||
    title: `${record?.id ? "编辑" : "新增"}系统管理员`,
 | 
			
		||||
    onBeforeOk: async (done) => {
 | 
			
		||||
      await exposed()?.handleSubmit(save, {
 | 
			
		||||
        id: record?.id,
 | 
			
		||||
      });
 | 
			
		||||
      await reload();
 | 
			
		||||
      done(true);
 | 
			
		||||
    },
 | 
			
		||||
  }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const openResetPWDModal = usePopup(SysAdminResetPWD, {
 | 
			
		||||
  popupProps: ([reload, record], exposed) => ({
 | 
			
		||||
    title: `修改密码`,
 | 
			
		||||
    onBeforeOk: async (done) => {
 | 
			
		||||
      await exposed()?.handleSubmit(resetPass, {
 | 
			
		||||
        id: record?.id,
 | 
			
		||||
      });
 | 
			
		||||
      Message.success("修改成功");
 | 
			
		||||
      await reload();
 | 
			
		||||
      done(true);
 | 
			
		||||
    },
 | 
			
		||||
  }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleRemove = async (id, reload) => {
 | 
			
		||||
  await remove({ id });
 | 
			
		||||
  Message.success("删除成功");
 | 
			
		||||
  await reload();
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <SearchTable :request="getList" :columns="columns">
 | 
			
		||||
    <template #header-option="{ reload }">
 | 
			
		||||
      <a-button type="primary" @click="openFormModal(reload, {})">
 | 
			
		||||
        <template #icon> <icon-plus /> </template>
 | 
			
		||||
        新增
 | 
			
		||||
      </a-button>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #switch="{ record, column }">
 | 
			
		||||
      <ConfirmSwitch
 | 
			
		||||
        v-model="record[column.dataIndex]"
 | 
			
		||||
        :api="async () => save({ ...record, status: !record.status })"
 | 
			
		||||
      />
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #actions="{ record, reload }">
 | 
			
		||||
      <a-link @click="openFormModal(reload, record)">编辑</a-link>
 | 
			
		||||
      <a-link @click="openResetPWDModal(reload, record)">修改密码</a-link>
 | 
			
		||||
      <a-popconfirm
 | 
			
		||||
        content="是否删除?"
 | 
			
		||||
        position="left"
 | 
			
		||||
        type="warning"
 | 
			
		||||
        :on-before-ok="() => handleRemove(record.id, reload)"
 | 
			
		||||
      >
 | 
			
		||||
        <a-link status="danger">删除</a-link>
 | 
			
		||||
      </a-popconfirm>
 | 
			
		||||
    </template>
 | 
			
		||||
  </SearchTable>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="formData" auto-label-width>
 | 
			
		||||
    <a-form-item field="username" label="账号" :rules="[{ required: true, message: '请输入账号' }]">
 | 
			
		||||
      <a-input v-model="formData.username" placeholder="请输入账号" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      v-if="!props.record?.id"
 | 
			
		||||
      field="password"
 | 
			
		||||
      label="密码"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入密码' }]"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-password
 | 
			
		||||
        v-model="formData.password"
 | 
			
		||||
        placeholder="请输入密码"
 | 
			
		||||
        autocomplete="new-password"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="status" label="启用状态">
 | 
			
		||||
      <a-switch v-model="formData.status" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  record: Object,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { formRef, formData, handleSubmit } = useSubmit({
 | 
			
		||||
  username: "",
 | 
			
		||||
  password: "",
 | 
			
		||||
  status: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  handleSubmit,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="formData" auto-label-width>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="password"
 | 
			
		||||
      label="密码"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入密码' }]"
 | 
			
		||||
      showable
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-password
 | 
			
		||||
        v-model="formData.password"
 | 
			
		||||
        placeholder="请输入密码"
 | 
			
		||||
        autocomplete="new-password"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
 | 
			
		||||
const { formRef, formData, handleSubmit } = useSubmit({
 | 
			
		||||
  password: "",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  handleSubmit,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										33
									
								
								gpt-vue/projects/vue-admin/src/views/SysAdmin/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								gpt-vue/projects/vue-admin/src/views/SysAdmin/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import http from "@/http/config";
 | 
			
		||||
 | 
			
		||||
export const getList = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/sysUser/list",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const save = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/sysUser/save",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const remove = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/sysUser/remove",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const resetPass = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/sysUser/resetPass",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@@ -36,12 +36,9 @@ export default defineConfig(({ mode }) => {
 | 
			
		||||
        output: {
 | 
			
		||||
          manualChunks: (id) => {
 | 
			
		||||
            if (id.includes("node_modules")) {
 | 
			
		||||
              if (id.includes("arco")) {
 | 
			
		||||
                return `arco`;
 | 
			
		||||
              }
 | 
			
		||||
              if (id.includes("vue") && !id.includes("arco")) {
 | 
			
		||||
                return `vue`;
 | 
			
		||||
              }
 | 
			
		||||
              if (id.includes("echats")) return `echats`;
 | 
			
		||||
              if (id.includes("arco")) return `arco`;
 | 
			
		||||
              if (id.includes("vue") && !id.includes("arco")) return `vue`;
 | 
			
		||||
              return `vendor`;
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user