flyzto il y a 3 mois
Parent
commit
4fff3647ac

+ 2 - 2
apps/web-antd/src/api/request.ts

@@ -75,9 +75,9 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
   // 处理返回的响应数据格式
   client.addResponseInterceptor(
     defaultResponseInterceptor({
-      codeField: 'code',
+      codeField: 'state',
       dataField: 'data',
-      successCode: 0,
+      successCode: 1,
     }),
   );
 

+ 53 - 0
apps/web-antd/src/api/user/user_list.ts

@@ -0,0 +1,53 @@
+import { requestClient } from "#/api/request";
+
+interface ApiResultData {
+  data: Object;
+  state: number;
+  message: string;
+}
+
+/**
+ * 获取账号列表
+ */
+export async function getAccountList(params: any) {
+  // 使用模拟数据进行测试
+  // return Promise.resolve(mockAccountList);
+  const queryString = new URLSearchParams(params);
+  return requestClient.get<ApiResultData>('/user/list?' + queryString);
+}
+
+/**
+ * 获取账号详情
+ * @param user_id
+ */
+export async function getAccountInfo(user_id: string | number) {
+  return requestClient.get<ApiResultData>('/user/detail?user_id=' + user_id);
+}
+
+/**
+ * 更新账号信息
+ * @param data
+ */
+export async function updateAccountInfo(data: any) {
+  return requestClient.post<ApiResultData>('/user/update', data);
+}
+
+/**
+ * 创建账号
+ * @param data
+ */
+export async function createAccount(data: any) {
+  return requestClient.post<ApiResultData>('/user/create_user', data);
+}
+
+/**
+ * 删除账号
+ * @param user_id
+ */
+export async function deleteAccount(data: any) {
+  // 模拟删除成功
+  // return Promise.resolve({ state: 1, message: '删除成功' });
+
+  // 实际API调用(注释掉)
+  return requestClient.post<ApiResultData>('/user/delete', data);
+}

+ 33 - 0
apps/web-antd/src/locales/langs/en-US/user.json

@@ -0,0 +1,33 @@
+{
+  "title": "User Management",
+  "user_list": "User List",
+  "search": {
+    "user_name": "Username",
+    "nick_name": "Nickname",
+    "phone": "Phone",
+    "user_role": "User Role",
+    "status": "Status"
+  },
+  "table": {
+    "user_id": "User ID",
+    "user_name": "Username",
+    "nick_name": "Nickname",
+    "phone": "Phone",
+    "user_role": "User Role",
+    "role_name": "Role Name",
+    "white_list_ip": "Whitelist IP",
+    "create_time": "Create Time",
+    "login_time": "Last Login Time",
+    "update_time": "Update Time",
+    "action": "Action"
+  },
+  "status": {
+    "active": "Active",
+    "inactive": "Inactive"
+  },
+  "role": {
+    "admin": "Administrator",
+    "operator": "Operator",
+    "user": "User"
+  }
+}

+ 33 - 0
apps/web-antd/src/locales/langs/zh-CN/user.json

@@ -0,0 +1,33 @@
+{
+  "title": "账号管理",
+  "user_list": "账号列表",
+  "search": {
+    "user_name": "用户名",
+    "nick_name": "昵称",
+    "phone": "手机号",
+    "user_role": "用户角色",
+    "status": "状态"
+  },
+  "table": {
+    "user_id": "用户ID",
+    "user_name": "用户名",
+    "nick_name": "昵称",
+    "phone": "手机号",
+    "user_role": "用户角色",
+    "role_name": "角色名称",
+    "white_list_ip": "白名单IP",
+    "create_time": "创建时间",
+    "login_time": "最后登录时间",
+    "update_time": "更新时间",
+    "action": "操作"
+  },
+  "status": {
+    "active": "启用",
+    "inactive": "禁用"
+  },
+  "role": {
+    "admin": "管理员",
+    "operator": "运营管理员",
+    "user": "普通用户"
+  }
+}

+ 29 - 0
apps/web-antd/src/router/routes/modules/user_list.ts

@@ -0,0 +1,29 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    meta: {
+      icon: 'solar:user-outline',
+      keepAlive: true,
+      order: 4,
+      title: $t('user.title'),
+    },
+    name: 'UserManagement',
+    path: '/user',
+    children: [
+      {
+        meta: {
+          title: $t('user.user_list'),
+          icon: 'solar:users-group-rounded-outline',
+        },
+        name: 'UserList',
+        path: '/user/user-list',
+        component: () => import('#/views/user/user_list/index.vue'),
+      },
+    ],
+  },
+];
+
+export default routes;

+ 281 - 0
apps/web-antd/src/views/user/user_list/index.vue

@@ -0,0 +1,281 @@
+<script setup>
+import { Page, useVbenModal } from '@vben/common-ui';
+import { Button, Card, Tag, Avatar, Switch, Space, Popconfirm, message } from "ant-design-vue";
+import { useVbenForm } from '#/adapter/form';
+import { $t } from "@vben/locales";
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getAccountList, deleteAccount, getAccountInfo, updateAccountInfo, createAccount } from "#/api/user/user_list";
+import { ref } from 'vue';
+import dayjs from 'dayjs';
+
+import ExtraModal from './user_info.vue';
+
+// const [QueryForm] = useVbenForm({
+//   // 默认展开
+//   collapsed: false,
+//   // 所有表单项共用,可单独在表单内覆盖
+//   commonConfig: {
+//     // 所有表单项
+//     componentProps: {
+//       class: 'w-full',
+//     },
+//   },
+//   // 提交函数
+//   handleSubmit: onSearch,
+//   // 垂直布局,label和input在不同行,值为vertical
+//   // 水平布局,label和input在同一行
+//   layout: 'horizontal',
+//   schema: [
+//     {
+//       component: 'Input',
+//       componentProps: {
+//         placeholder: $t('user.search.user_name'),
+//         allowClear: true,
+//       },
+//       fieldName: 'user_name',
+//       label: $t('user.search.user_name'),
+//     },
+//     {
+//       component: 'Input',
+//       componentProps: {
+//         placeholder: $t('user.search.nick_name'),
+//         allowClear: true,
+//       },
+//       fieldName: 'nick_name',
+//       label: $t('user.search.nick_name'),
+//     },
+//     {
+//       component: 'Input',
+//       componentProps: {
+//         placeholder: $t('user.search.phone'),
+//         allowClear: true,
+//       },
+//       fieldName: 'phone',
+//       label: $t('user.search.phone'),
+//     },
+//     {
+//       component: 'Select',
+//       componentProps: {
+//         allowClear: true,
+//         filterOption: true,
+//         options: [
+//           {
+//             label: $t('user.role.admin'),
+//             value: 0,
+//           },
+//           {
+//             label: $t('user.role.operator'),
+//             value: 1,
+//           },
+//           {
+//             label: $t('user.role.user'),
+//             value: 2,
+//           },
+//         ],
+//         placeholder: $t('common.placeholder_select'),
+//         showSearch: true,
+//       },
+//       fieldName: 'user_role',
+//       label: $t('user.search.user_role'),
+//     },
+//   ],
+//   // 是否可展开
+//   showCollapseButton: true,
+//   submitButtonOptions: {
+//     content: '查询',
+//   },
+//   wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
+// });
+
+// function onSearch(values) {
+//   // 触发表格重新加载
+//   gridApi.reload();
+// }
+
+const gridOptions = {
+  border: true,
+  stripe: true,
+  checkboxConfig: {
+    highlight: true,
+  },
+  columns: [
+    { fixed: 'left', title: '序号', type: 'seq', width: 60 },
+    { field: 'user_id', title: $t('user.table.user_id'), width: 60 },
+    { field: 'user_name', title: $t('user.table.user_name'), width: 100 },
+    { field: 'nick_name', title: $t('user.table.nick_name'), width: 150 },
+    { field: 'phone', title: $t('user.table.phone'), width: 150 },
+    { field: 'role_name', title: $t('user.table.role_name'), width: 150, slots: { default: 'role' } },
+    { field: 'white_list_ip', title: $t('user.table.white_list_ip'), width: 150, slots: { default: 'ip' } },
+    { field: 'create_time', title: $t('user.table.create_time'), width: 180 },
+    { field: 'login_time', title: $t('user.table.login_time'), width: 180, slots: { default: 'login_time' } },
+    { field: 'update_time', title: $t('user.table.update_time'), width: 180 },
+    { fixed: 'right', title: $t('user.table.action'), width: 120, slots: { default: 'action' } },
+  ],
+  exportConfig: {},
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        let params = {
+          page: page.currentPage,
+          limit: page.pageSize,
+        };
+        try {
+          const res = await getAccountList(params);
+          return {
+            total: res.total,
+            items: res.list
+          }
+        } catch (error) {
+          console.error('获取账号列表失败:', error);
+          return {
+            total: 0,
+            items: []
+          };
+        }
+      },
+    },
+  },
+  rowConfig: {
+    isHover: true,
+  },
+  toolbarConfig: {
+    custom: true,
+    export: true,
+    refresh: true,
+    zoom: true,
+  },
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions,
+});
+
+// 格式化登录时间
+const formatLoginTime = (timestamp) => {
+  if (!timestamp || timestamp === 0) {
+    return '从未登录';
+  }
+  return dayjs(timestamp * 1000).format('YYYY-MM-DD HH:mm:ss');
+};
+
+// 格式化IP地址
+const formatIP = (ip) => {
+  return ip || '无限制';
+};
+
+// 获取角色标签颜色
+const getRoleColor = (roleId) => {
+  if (roleId === 1) {
+    return 'red';
+  } else if (roleId === 100) {
+    return 'blue';
+  }
+  return 'green';
+};
+
+const [Modal, modalApi] = useVbenModal({
+  connectedComponent: ExtraModal,
+  onBeforeClose() {
+    const { formData } = modalApi.getData();
+    if (!formData) {
+      return true;
+    }
+    if (formData.user_id) {
+      updateAccountInfo(formData)
+      .then(res => {
+        message.success('编辑账号成功');
+        gridApi.reload();
+      })
+      .catch(err => {
+        message.error('编辑账号失败');
+        console.error('编辑账号失败:', err);
+      });
+    }
+    else {
+      createAccount(formData)
+      .then(res => {
+        message.success('新增账号成功');
+        gridApi.reload();
+      })
+      .catch(err => {
+        message.error('新增账号失败');
+        console.error('新增账号失败:', err);
+      });
+    }
+  },
+});
+
+// 新增账号
+const addAccount = () => {
+  modalApi.setState({ title: '新增账号' })
+    .open();
+};
+
+// 编辑账号
+const editAccount = (row) => {
+  modalApi.setData({ formData: row })
+    .setState({ title: '编辑账号' })
+    .open();
+};
+
+// 处理删除账号
+const handleDelete = async (row) => {
+  try {
+    await deleteAccount({ user_id: row.user_id });
+    message.success('删除成功');
+    gridApi.reload();
+  } catch (error) {
+    message.error('删除失败');
+  }
+};
+
+</script>
+
+<template>
+  <Page>
+    <!-- <Card class="mb-5">
+      <QueryForm />
+    </Card> -->
+    <Card>
+      <div class="vp-raw w-full">
+        <Grid>
+          <template #toolbar-actions>
+            <Button type="primary" @click="addAccount">新增账号</Button>
+          </template>
+          <template #role="{ row }">
+            <Tag :color="getRoleColor(row.user_role)" size="large">
+              {{ row.role_name }}
+            </Tag>
+          </template>
+          <template #ip="{ row }">
+            <span>{{ formatIP(row.white_list_ip) }}</span>
+          </template>
+          <template #login_time="{ row }">
+            <span>{{ formatLoginTime(row.login_time) }}</span>
+          </template>
+          <template #action="{ row }">
+            <Space>
+              <Button type="link" size="small" @click="editAccount(row)">编辑</Button>
+              <Popconfirm
+                title="确定要删除这个账号吗?"
+                @confirm="handleDelete(row)"
+                ok-text="确定"
+                cancel-text="取消"
+              >
+                <Button type="link" size="small" danger>删除</Button>
+              </Popconfirm>
+            </Space>
+          </template>
+        </Grid>
+      </div>
+    </Card>
+    <Modal />
+  </Page>
+</template>
+
+<style scoped>
+.ant-card {
+  margin-bottom: 16px;
+}
+</style>

+ 151 - 0
apps/web-antd/src/views/user/user_list/user_info.vue

@@ -0,0 +1,151 @@
+<script setup>
+import { useVbenModal } from '@vben/common-ui';
+import { Form, Input, Select, Button, message } from "ant-design-vue";
+import { ref, reactive, computed, watch } from 'vue';
+
+const formProps = ['user_id', 'user_name', 'nick_name', 'password', 'confirm_password', 'phone', 'user_role', 'white_list_ip'];
+
+const formRef = ref();
+
+const addState = reactive({
+  white_list_ip: '',
+});
+
+const editState = reactive({});
+const editCache = {};
+
+const rulesAdd = {
+  user_name: [{ required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 16, message: '用户名长度为3-16位', trigger: 'blur' }],
+  nick_name: [{ required: true, message: '请输入昵称', trigger: 'blur' }, { min: 3, max: 16, message: '昵称长度为3-16位', trigger: 'blur' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 16, message: '密码长度为6-16位', trigger: 'blur' }],
+  confirm_password: [{ required: true, trigger: 'blur',
+    validator: (rule, value) => {
+      if (!value) {
+        return Promise.reject(new Error('请确认密码'));
+      }
+      if (value !== addState.password) {
+        return Promise.reject(new Error('两次密码不一致'));
+      }
+      return Promise.resolve();
+    }
+   }, { min: 6, max: 16, message: '密码长度为6-16位', trigger: 'blur' }],
+  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
+  user_role: [{ required: true, message: '请选择角色', trigger: 'blur' }],
+};
+
+const rulesEdit = {
+  nick_name: [{ min: 3, max: 16, message: '昵称长度为3-16位', trigger: 'blur' }],
+  password: [{ min: 6, max: 16, message: '密码长度为6-16位', trigger: 'blur' }],
+  confirm_password: [{
+    trigger: 'blur',
+    validator: (rule, value) => {
+      if (value !== editState.password) {
+        return Promise.reject(new Error('两次密码不一致'));
+      }
+      return Promise.resolve();
+    }
+  }, { min: 6, max: 16, message: '密码长度为6-16位', trigger: 'blur' }],
+  phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
+}
+
+const rules = computed(() => {
+  return editState.user_id ? rulesEdit : rulesAdd;
+});
+
+const formState = computed(() => {
+  return isEdit.value ? editState : addState;
+});
+
+const isEdit = computed(() => {
+  return !!editState.user_id;
+});
+
+const resetForm = () => {
+  formRef.value.resetFields();
+  Object.keys(editCache).forEach(key => {
+    delete editCache[key];
+  });
+  delete editState.user_id;
+};
+
+const [Modal, modalApi] = useVbenModal({
+  title: 'Account Info',
+  onOpened() {
+    const { formData } = modalApi.getData();
+    if (formData) {
+      Object.keys(formData).forEach(key => {
+        if (formProps.includes(key)) {
+          editState[key] = formData[key];
+          editCache[key] = formData[key];
+        }
+      });
+    }
+    modalApi.setData({ formData: null });
+  },
+  onCancel() {
+    modalApi.setData({ formData: null });
+    modalApi.close();
+  },
+  onConfirm() {
+    formRef.value.validate().then(() => {
+      if (isEdit.value) {
+        let needEdit = false;
+        const editData = {};
+        Object.keys(editState).forEach(key => {
+          if (editState[key] !== editCache[key]) {
+            editData[key] = editState[key];
+            needEdit = true;
+          }
+        });
+        if (needEdit) {
+          editData.user_id = editState.user_id;
+          delete editData.confirm_password;
+          modalApi.setData({ formData: { ...editData } });
+        }
+      }
+      else {
+        const { confirm_password, ...formData } = addState;
+        modalApi.setData({ formData: { ...formData } });
+      }
+      modalApi.close();
+    })
+    .catch(err => {
+      console.log('请检查输入内容', err);
+    });
+  },
+  onClosed() {
+    resetForm();
+  }
+});
+
+</script>
+<template>
+  <Modal>
+    <Form ref="formRef" :model="formState" :label-col="{ span: 6 }" :wrapper-col="{ span: 12 }" :rules="rules">
+      <Form.Item label="用户名" name="user_name">
+        <Input v-model:value="formState.user_name" :disabled="isEdit" />
+      </Form.Item>
+      <Form.Item label="昵称" name="nick_name">
+        <Input v-model:value="formState.nick_name" />
+      </Form.Item>
+      <Form.Item label="密码" name="password">
+        <Input.Password v-model:value="formState.password" />
+      </Form.Item>
+      <Form.Item label="确认密码" name="confirm_password">
+        <Input.Password v-model:value="formState.confirm_password" />
+      </Form.Item>
+      <Form.Item label="手机号" name="phone">
+        <Input v-model:value="formState.phone" />
+      </Form.Item>
+      <Form.Item label="角色" name="user_role">
+        <Select v-model:value="formState.user_role">
+          <Select.Option :value="1">超级管理员</Select.Option>
+          <Select.Option :value="100">业务管理员</Select.Option>
+        </Select>
+      </Form.Item>
+      <Form.Item label="IP白名单" name="white_list_ip">
+        <Input v-model:value="formState.white_list_ip" />
+      </Form.Item>
+    </Form>
+  </Modal>
+</template>