From 36b6a8437b015e772513125fd7bbb22308436d89 Mon Sep 17 00:00:00 2001 From: xie7654 <765462425@qq.com> Date: Tue, 1 Jul 2025 15:08:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/generate_crud.py | 77 ++---- .../commands/tpl/frontend_data.ts.tpl | 57 +---- .../commands/tpl/frontend_list.vue.tpl | 2 +- ...ame_avatarurl_user_avatar_url_user_role.py | 29 +++ .../0005_remove_user_login_date_menu_sort.py | 24 ++ backend/system/models.py | 12 +- backend/system/urls.py | 1 + backend/system/views/__init__.py | 1 + backend/system/views/user.py | 78 ++++-- web/apps/web-antd/src/layouts/basic.vue | 2 +- .../src/locales/langs/en-US/system.json | 7 + .../src/locales/langs/zh-CN/system.json | 7 + web/apps/web-antd/src/models/system/user.ts | 40 +++ .../src/router/routes/modules/system.ts | 9 + .../web-antd/src/views/system/menu/data.ts | 7 +- .../src/views/system/menu/modules/form.vue | 11 + .../web-antd/src/views/system/user/data.ts | 230 ++++++++++++++++++ .../web-antd/src/views/system/user/list.vue | 132 ++++++++++ .../src/views/system/user/modules/form.vue | 85 +++++++ 19 files changed, 686 insertions(+), 125 deletions(-) create mode 100644 backend/system/migrations/0004_rename_avatarurl_user_avatar_url_user_role.py create mode 100644 backend/system/migrations/0005_remove_user_login_date_menu_sort.py create mode 100644 web/apps/web-antd/src/models/system/user.ts create mode 100644 web/apps/web-antd/src/views/system/user/data.ts create mode 100644 web/apps/web-antd/src/views/system/user/list.vue create mode 100644 web/apps/web-antd/src/views/system/user/modules/form.vue diff --git a/backend/system/management/commands/generate_crud.py b/backend/system/management/commands/generate_crud.py index e82bd6d..a1e0a89 100644 --- a/backend/system/management/commands/generate_crud.py +++ b/backend/system/management/commands/generate_crud.py @@ -28,7 +28,7 @@ def camel_to_snake(name): return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() def ensure_view_dirs(app_name, model_name_snake): - base_dir = f'web/apps/web-antd/src/views/{app_name.lower()}' + base_dir = f'../web/apps/web-antd/src/views/{app_name.lower()}' model_dir = os.path.join(base_dir, model_name_snake) if not os.path.exists(base_dir): os.makedirs(model_dir, exist_ok=True) @@ -77,7 +77,7 @@ class Command(BaseCommand): raise CommandError(f'模型 {app_name}.{model_name} 不存在') # 生成后端代码 - self.generate_backend_code(app_name, model_name, model, model_name_snake) + # self.generate_backend_code(app_name, model_name, model, model_name_snake) if generate_frontend: # 生成前端代码 @@ -148,15 +148,29 @@ class Command(BaseCommand): self.stdout.write(f'生成前端列表页面: {list_path}') def generate_frontend_data(self, app_name, model_name, model, model_name_snake): - form_fields = [] + CORE_FIELDS = ['create_time', 'update_time', 'creator', 'modifier', 'is_deleted', 'remark'] + business_fields = [] + core_fields = [] for field in model._meta.fields: - if field.name in ['id', 'create_time', 'update_time', 'creator', 'modifier']: - continue + if field.name in CORE_FIELDS: + core_fields.append(field) + else: + business_fields.append(field) + # 生成 useSchema + form_fields = [] + for field in business_fields + core_fields: + if field.name in ['id', 'create_time', 'update_time', 'creator', 'modifier', 'is_deleted']: + continue # 这些一般不在表单里 field_config = self.generate_form_field(field) if field_config: form_fields.append(field_config) + # 生成 useColumns + columns = [] + for field in business_fields + core_fields: + columns.append(f" {{\n field: '{field.name}',\n title: '{getattr(field, 'verbose_name', field.name)}',\n }},") context = get_context(app_name, model_name, model, model_name_snake) context['form_fields'] = '\n'.join(form_fields) + context['columns'] = '\n'.join(columns) data_path = f'../web/apps/web-antd/src/views/{app_name.lower()}/{model_name_snake}/data.ts' data_code = render_tpl('frontend_data.ts.tpl', context) with open(data_path, 'w', encoding='utf-8') as f: @@ -196,55 +210,14 @@ class Command(BaseCommand): """生成表单字段配置""" field_name = field.name field_label = getattr(field, 'verbose_name', field_name) - + col_props = ",\n colProps: { span: 12 }" if isinstance(field, models.CharField): - return f''' {{ - component: 'Input', - fieldName: '{field_name}', - label: '{field_label}', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['{field_label}'])) - .max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])), - }},''' + return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',{col_props}\n rules: z\n .string()\n .min(1, $t('ui.formRules.required', ['{field_label}']))\n .max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])),\n }},''' elif isinstance(field, models.TextField): - return f''' {{ - component: 'Input', - componentProps: {{ - rows: 3, - showCount: true, - }}, - fieldName: '{field_name}', - label: '{field_label}', - rules: z - .string() - .max(500, $t('ui.formRules.maxLength', ['{field_label}', 500])) - .optional(), - }},''' + return f''' {{\n component: 'Input',\n componentProps: {{\n rows: 3,\n showCount: true,\n }},\n fieldName: '{field_name}',\n label: '{field_label}',{col_props}\n rules: z\n .string()\n .max(500, $t('ui.formRules.maxLength', ['{field_label}', 500]))\n .optional(),\n }},''' elif isinstance(field, models.IntegerField): - return f''' {{ - component: 'InputNumber', - fieldName: '{field_name}', - label: '{field_label}', - }},''' + return f''' {{\n component: 'InputNumber',\n fieldName: '{field_name}',\n label: '{field_label}',{col_props}\n }},''' elif isinstance(field, models.BooleanField): - return f''' {{ - component: 'RadioGroup', - componentProps: {{ - buttonStyle: 'solid', - options: [ - {{ label: '开启', value: true }}, - {{ label: '关闭', value: false }}, - ], - optionType: 'button', - }}, - defaultValue: true, - fieldName: '{field_name}', - label: '{field_label}', - }},''' + return f''' {{\n component: 'RadioGroup',\n componentProps: {{\n buttonStyle: 'solid',\n options: [\n {{ label: '开启', value: 1 }},\n {{ label: '关闭', value: 0 }},\n ],\n optionType: 'button',\n }},{col_props}\n defaultValue: 1,\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' else: - return f''' {{ - component: 'Input', - fieldName: '{field_name}', - label: '{field_label}', - }},''' \ No newline at end of file + return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',{col_props}\n }},''' \ No newline at end of file diff --git a/backend/system/management/commands/tpl/frontend_data.ts.tpl b/backend/system/management/commands/tpl/frontend_data.ts.tpl index 75049c8..c0dfe1d 100644 --- a/backend/system/management/commands/tpl/frontend_data.ts.tpl +++ b/backend/system/management/commands/tpl/frontend_data.ts.tpl @@ -2,7 +2,7 @@ import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; import type { VbenFormSchema } from '#/adapter/form'; import type { OnActionClickFn } from '#/adapter/vxe-table'; -import type { ${app_name_camel}${model_name}Api } from '#/models/$app_name/${model_name_snake}'; +import type { ${app_name_camel}${model_name}Api } from '#/models/${app_name}/${model_name_snake}'; import { z } from '#/adapter/form'; import { $$t } from '#/locales'; @@ -13,66 +13,35 @@ import { format_datetime } from '#/utils/date'; */ export function useSchema(): VbenFormSchema[] { return [ -$form_fields +${form_fields} ]; } +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ export function useColumns( onActionClick?: OnActionClickFn<${app_name_camel}${model_name}Api.${app_name_camel}${model_name}>, ): VxeTableGridOptions<${app_name_camel}${model_name}Api.${app_name_camel}${model_name}>['columns'] { return [ +${columns} { - align: 'left', - field: 'id', - }, - { - cellRender: { - name: 'CellTag', - options: [ - { label: $$t('common.enabled'), value: true }, - { label: $$t('common.disabled'), value: false }, - ], - }, - field: 'status', - title: '状态', - width: 100, - }, - { - field: 'remark', - title: '备注', - }, - { - field: 'create_time', - title: '创建时间', - width: 180, - formatter: ({ cellValue }) => format_datetime(cellValue), - }, - { - align: 'right', + align: 'center', cellRender: { attrs: { nameField: 'name', - nameTitle: $$t('${app_name}.{model_name_snake}.name'), + nameTitle: $t('${app_name}.${model_name_snake}.name'), onClick: onActionClick, }, name: 'CellOperation', - options: [ - 'edit', // 默认的编辑按钮 - { - code: 'view', // 新增查看详情按钮(可自定义code) - text: '数据', // 按钮文本(国际化) - }, - { - code: 'delete', // 默认的删除按钮 - }, - ], + options: ['edit', 'delete'], }, - field: 'operation', + field: 'action', fixed: 'right', - headerAlign: 'center', - showOverflow: false, title: '操作', - width: 200, + width: 120, }, ]; } diff --git a/backend/system/management/commands/tpl/frontend_list.vue.tpl b/backend/system/management/commands/tpl/frontend_list.vue.tpl index 1ed07f4..b27e64e 100644 --- a/backend/system/management/commands/tpl/frontend_list.vue.tpl +++ b/backend/system/management/commands/tpl/frontend_list.vue.tpl @@ -124,7 +124,7 @@ function refreshGrid() { - {{ $$t('ui.actionTitle.create', [$$t('${app_name}.${model_name_snake}.name')]) }} + {{ $$t('ui.actionTitle.create', [$$t('${app_name}.${model_name_snake}.name')]) }} diff --git a/backend/system/migrations/0004_rename_avatarurl_user_avatar_url_user_role.py b/backend/system/migrations/0004_rename_avatarurl_user_avatar_url_user_role.py new file mode 100644 index 0000000..c39777a --- /dev/null +++ b/backend/system/migrations/0004_rename_avatarurl_user_avatar_url_user_role.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.1 on 2025-07-01 05:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("system", "0003_alter_dictdata_status_alter_dicttype_status_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="user", + old_name="avatarUrl", + new_name="avatar_url", + ), + migrations.AddField( + model_name="user", + name="role", + field=models.ManyToManyField( + blank=True, + db_constraint=False, + related_name="users", + to="system.role", + verbose_name="角色", + ), + ), + ] diff --git a/backend/system/migrations/0005_remove_user_login_date_menu_sort.py b/backend/system/migrations/0005_remove_user_login_date_menu_sort.py new file mode 100644 index 0000000..98051de --- /dev/null +++ b/backend/system/migrations/0005_remove_user_login_date_menu_sort.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.1 on 2025-07-01 07:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("system", "0004_rename_avatarurl_user_avatar_url_user_role"), + ] + + operations = [ + migrations.RemoveField( + model_name="user", + name="login_date", + ), + migrations.AddField( + model_name="menu", + name="sort", + field=models.IntegerField( + default=0, help_text="数值越小越靠前", verbose_name="显示排序" + ), + ), + ] diff --git a/backend/system/models.py b/backend/system/models.py index 57c4eb0..b249a3f 100644 --- a/backend/system/models.py +++ b/backend/system/models.py @@ -97,6 +97,11 @@ class Menu(CoreModel): name = models.CharField(max_length=100, verbose_name='菜单名称') status = models.IntegerField(choices=CommonStatus.choices, default=CommonStatus.ENABLED, verbose_name='状态') type = models.CharField(choices=MenuType.choices, max_length=20, verbose_name='菜单类型') + sort = models.IntegerField( + default=0, + verbose_name="显示排序", + help_text="数值越小越靠前" + ) path = models.CharField(max_length=200, blank=True, verbose_name='路由路径') component = models.CharField(max_length=200, blank=True, verbose_name='组件路径') auth_code = models.CharField(max_length=100, blank=True, verbose_name='权限编码') @@ -244,12 +249,16 @@ class User(AbstractUser, CoreModel): city = models.CharField('城市', max_length=20, blank=True, null=True, db_comment="城市") province = models.CharField('省份', max_length=50, blank=True, null=True, db_comment="省份") country = models.CharField('国家', max_length=50, blank=True, null=True, db_comment="国家") - avatarUrl = models.URLField('头像', blank=True, null=True, db_comment="头像") + avatar_url = models.URLField('头像', blank=True, null=True, db_comment="头像") dept = models.ManyToManyField( 'Dept', blank=True, verbose_name='部门', db_constraint=False, related_name='users' ) + role = models.ManyToManyField( + 'Role', blank=True, verbose_name='角色', db_constraint=False, + related_name='users' + ) post = models.ManyToManyField( 'Post', blank=True, verbose_name='岗位', db_constraint=False, related_name='users' @@ -259,7 +268,6 @@ class User(AbstractUser, CoreModel): default=CommonStatus.ENABLED, verbose_name='状态' ) - login_date = models.DateTimeField("<最后登录时间>", blank=True, null=True, db_comment="最后登录时间") login_ip = models.GenericIPAddressField(blank=True, null=True, db_comment="最后登录IP") class Meta: diff --git a/backend/system/urls.py b/backend/system/urls.py index 5cc3427..7243609 100644 --- a/backend/system/urls.py +++ b/backend/system/urls.py @@ -11,6 +11,7 @@ router.register(r'role', views.RoleViewSet) router.register(r'dict_data', views.DictDataViewSet) router.register(r'dict_type', views.DictTypeViewSet) router.register(r'post', views.PostViewSet) +router.register(r'user', views.UserViewSet) urlpatterns = [ path('', include(router.urls)), diff --git a/backend/system/views/__init__.py b/backend/system/views/__init__.py index 28b807b..d4eabc7 100644 --- a/backend/system/views/__init__.py +++ b/backend/system/views/__init__.py @@ -6,6 +6,7 @@ __all__ = [ 'DictDataViewSet', 'DictTypeViewSet', 'PostViewSet', + 'UserViewSet', ] from system.views.dict_data import DictDataViewSet diff --git a/backend/system/views/user.py b/backend/system/views/user.py index f7f6409..2882300 100644 --- a/backend/system/views/user.py +++ b/backend/system/views/user.py @@ -1,17 +1,42 @@ +from django.utils import timezone +from rest_framework import serializers from rest_framework.authtoken.models import Token from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.response import Response from rest_framework.views import APIView -from rest_framework.viewsets import ModelViewSet +from django.contrib.auth.hashers import make_password from system.models import User + +from utils.serializers import CustomModelSerializer from utils.custom_model_viewSet import CustomModelViewSet -class UserSerializer(CustomModelViewSet): +class UserSerializer(CustomModelSerializer): + roles = serializers.SerializerMethodField() # 新增字段 + """ + 用户数据 序列化器 + """ class Meta: model = User - exclude = ('password',) + fields = '__all__' + read_only_fields = ['id', 'create_time', 'update_time'] + + def get_roles(self, obj): + """ + 返回用户所有角色的名称列表 + """ + return list(obj.role.values_list('name', flat=True)) + + def create(self, validated_data): + if 'password' in validated_data: + validated_data['password'] = make_password(validated_data['password']) + return super().create(validated_data) + + def update(self, instance, validated_data): + if 'password' in validated_data: + validated_data['password'] = make_password(validated_data['password']) + return super().update(instance, validated_data) class UserLogin(ObtainAuthToken): @@ -22,18 +47,16 @@ class UserLogin(ObtainAuthToken): serializer.is_valid(raise_exception=True) user = serializer.validated_data['user'] token, created = Token.objects.get_or_create(user=user) + # 更新登录IP和登录时间 + user.login_ip = request.META.get('REMOTE_ADDR') + user.last_login = timezone.now() + user.save(update_fields=['login_ip', 'last_login']) + user_data = UserSerializer(user).data + # 在序列化后的数据中加入 accessToken + user_data['accessToken'] = token.key return Response({ "code": 0, - "data": { - "id": user.id, - "password": user.password, - "realName": user.nickname, - "roles": [ - "super" - ], - "username": user.username, - "accessToken": token.key - }, + "data": user_data, "error": None, "message": "ok" }) @@ -43,16 +66,12 @@ class UserInfo(APIView): def get(self, request, *args, **kwargs): user = self.request.user + user_data = UserSerializer(user).data + if user.is_superuser: + user_data['roles'] = ['admin'] return Response({ "code": 0, - "data": { - "id": user.id, - "realName": user.username, - "roles": [ - "super" - ], - "username": user.username, - }, + "data": user_data, "error": None, "message": "ok" }) @@ -74,6 +93,17 @@ class Codes(APIView): }) -class UserViewSet(ModelViewSet): - queryset = User.objects.all().order_by('id') - serializer_class = UserSerializer \ No newline at end of file +class UserViewSet(CustomModelViewSet): + """ + 用户数据 视图集 + """ + queryset = User.objects.filter(is_deleted=False).order_by('-id') + serializer_class = UserSerializer + read_only_fields = ['id', 'create_time', 'update_time', 'login_ip'] + filterset_fields = ['username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'remark', 'creator', + 'modifier', 'is_deleted', 'mobile', 'nickname', 'gender', 'language', 'city', 'province', + 'country', 'avatar_url', 'status'] + search_fields = ['name'] # 根据实际字段调整 + ordering_fields = ['create_time', 'id'] + ordering = ['-create_time'] + diff --git a/web/apps/web-antd/src/layouts/basic.vue b/web/apps/web-antd/src/layouts/basic.vue index 1481dc5..baa47d2 100644 --- a/web/apps/web-antd/src/layouts/basic.vue +++ b/web/apps/web-antd/src/layouts/basic.vue @@ -129,7 +129,7 @@ watch( :avatar :menus :text="userStore.userInfo?.realName" - description="ann.vben@gmail.com" + :description="userStore.userInfo?.email" tag-text="Pro" @logout="handleLogout" /> diff --git a/web/apps/web-antd/src/locales/langs/en-US/system.json b/web/apps/web-antd/src/locales/langs/en-US/system.json index 46ba602..c391bba 100644 --- a/web/apps/web-antd/src/locales/langs/en-US/system.json +++ b/web/apps/web-antd/src/locales/langs/en-US/system.json @@ -28,6 +28,7 @@ "path": "Route Path", "component": "Component", "status": "Status", + "sort": "sort", "authCode": "Permission Code", "badge": "Badge", "operation": "Actions", @@ -94,8 +95,14 @@ "name": "Post", "title": "Post Management" }, + "user": { + "name": "User", + "title": "User Management" + }, "status": "Status", "remark": "Remarks", + "creator": "creator", + "modifier": "modifier", "createTime": "Created At", "operation": "Actions", "updateTime": "Updated At" diff --git a/web/apps/web-antd/src/locales/langs/zh-CN/system.json b/web/apps/web-antd/src/locales/langs/zh-CN/system.json index 8af02c9..9d4d913 100644 --- a/web/apps/web-antd/src/locales/langs/zh-CN/system.json +++ b/web/apps/web-antd/src/locales/langs/zh-CN/system.json @@ -14,6 +14,7 @@ "menu": { "list": "菜单列表", "activeIcon": "激活图标", + "sort": "排序", "activePath": "激活路径", "activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径", "activePathMustExist": "该路径未能找到有效的菜单", @@ -95,8 +96,14 @@ "name": "岗位", "title": "岗位管理" }, + "user": { + "name": "用户", + "title": "用户管理" + }, "status": "状态", "remark": "备注", + "creator": "创建人", + "modifier": "修改人", "createTime": "创建时间", "operation": "操作", "updateTime": "更新时间" diff --git a/web/apps/web-antd/src/models/system/user.ts b/web/apps/web-antd/src/models/system/user.ts new file mode 100644 index 0000000..d061a66 --- /dev/null +++ b/web/apps/web-antd/src/models/system/user.ts @@ -0,0 +1,40 @@ +import { BaseModel } from '#/models/base'; + +export namespace SystemUserApi { + export interface SystemUser { + id: number; + password: string; + last_login: string; + is_superuser: boolean; + username: string; + first_name: string; + last_name: string; + email: string; + is_staff: boolean; + is_active: boolean; + date_joined: string; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + mobile: string; + nickname: string; + gender: number; + language: string; + city: string; + province: string; + country: string; + avatar_url: string; + status: number; + login_date: string; + login_ip: any; + } +} + +export class SystemUserModel extends BaseModel { + constructor() { + super('/system/user/'); + } +} diff --git a/web/apps/web-antd/src/router/routes/modules/system.ts b/web/apps/web-antd/src/router/routes/modules/system.ts index 8266256..279deef 100644 --- a/web/apps/web-antd/src/router/routes/modules/system.ts +++ b/web/apps/web-antd/src/router/routes/modules/system.ts @@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [ name: 'System', path: '/system', children: [ + { + path: '/system/user', + name: 'SystemUser', + meta: { + icon: 'mdi:account-group', + title: $t('system.user.title'), + }, + component: () => import('#/views/system/user/list.vue'), + }, { path: '/system/role', name: 'SystemRole', diff --git a/web/apps/web-antd/src/views/system/menu/data.ts b/web/apps/web-antd/src/views/system/menu/data.ts index dd4db56..494b8dd 100644 --- a/web/apps/web-antd/src/views/system/menu/data.ts +++ b/web/apps/web-antd/src/views/system/menu/data.ts @@ -43,7 +43,12 @@ export function useColumns( }, { field: 'auth_code', - title: $t('system.menu.authCode'), + title: $t('system.menu.auth_code'), + width: 200, + }, + { + field: 'sort', + title: $t('system.menu.sort'), width: 200, }, { diff --git a/web/apps/web-antd/src/views/system/menu/modules/form.vue b/web/apps/web-antd/src/views/system/menu/modules/form.vue index 0fdd033..c6c6e30 100644 --- a/web/apps/web-antd/src/views/system/menu/modules/form.vue +++ b/web/apps/web-antd/src/views/system/menu/modules/form.vue @@ -254,6 +254,17 @@ const schema: VbenFormSchema[] = [ fieldName: 'auth_code', label: $t('system.menu.authCode'), }, + { + component: 'InputNumber', + dependencies: { + rules: () => { + return 'required'; + }, + triggerFields: ['type'], + }, + fieldName: 'sort', + label: $t('system.menu.sort'), + }, { component: 'RadioGroup', componentProps: { diff --git a/web/apps/web-antd/src/views/system/user/data.ts b/web/apps/web-antd/src/views/system/user/data.ts new file mode 100644 index 0000000..a206903 --- /dev/null +++ b/web/apps/web-antd/src/views/system/user/data.ts @@ -0,0 +1,230 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/models/system/user'; + +import { z } from '#/adapter/form'; +import { getDeptList, getRoleList } from '#/api/system'; +import { $t } from '#/locales'; +import { SystemPostModel } from '#/models/system/post'; +import { format_datetime } from '#/utils/date'; + +const systemPost = new SystemPostModel(); + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'username', + label: '用户名', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['用户名'])) + .max(100, $t('ui.formRules.maxLength', ['用户名', 100])), + }, + { + component: 'Input', + fieldName: 'email', + label: '电子邮件地址', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['电子邮件地址'])) + .max(100, $t('ui.formRules.maxLength', ['电子邮件地址', 100])), + }, + { + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + multiple: true, // 允许多选 + api: getDeptList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'dept', + label: $t('system.dept.name'), + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + mode: 'multiple', // 允许多选 + api: getRoleList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + }, + fieldName: 'role', + label: $t('system.role.name'), + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + mode: 'multiple', // 允许多选 + api: () => systemPost.list(), + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'post', + label: $t('system.post.name'), + }, + { + component: 'Input', + fieldName: 'mobile', + label: '手机号', + }, + { + component: 'Input', + fieldName: 'nickname', + label: '昵称', + }, + { + component: 'InputPassword', + fieldName: 'password', + label: '密码', + }, + { + component: 'Input', + fieldName: 'city', + label: '城市', + }, + { + component: 'Input', + fieldName: 'province', + label: '省份', + }, + { + component: 'Input', + fieldName: 'country', + label: '国家', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.status'), + }, + { + component: 'Input', + fieldName: 'remark', + label: $t('system.remark'), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ + +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'username', + title: '用户名', + width: 100, + }, + + { + field: 'is_superuser', + title: '超级用户状态', + }, + { + field: 'date_joined', + title: '加入日期', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'mobile', + title: 'mobile', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'status', + title: $t('system.status'), + width: 100, + }, + { + field: 'login_ip', + title: 'login ip', + width: 150, + }, + { + field: 'last_login', + title: '最后登录', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'remark', + title: $t('system.remark'), + }, + { + field: 'creator', + title: $t('system.creator'), + width: 80, + }, + { + field: 'modifier', + title: $t('system.modifier'), + width: 80, + }, + { + field: 'update_time', + title: $t('system.updateTime'), + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'create_time', + title: $t('system.createTime'), + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.user.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: ['edit', 'delete'], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-antd/src/views/system/user/list.vue b/web/apps/web-antd/src/views/system/user/list.vue new file mode 100644 index 0000000..82968f6 --- /dev/null +++ b/web/apps/web-antd/src/views/system/user/list.vue @@ -0,0 +1,132 @@ + + + + + + + + + + {{ $t('ui.actionTitle.create', [$t('system.user.name')]) }} + + + + + diff --git a/web/apps/web-antd/src/views/system/user/modules/form.vue b/web/apps/web-antd/src/views/system/user/modules/form.vue new file mode 100644 index 0000000..28421b5 --- /dev/null +++ b/web/apps/web-antd/src/views/system/user/modules/form.vue @@ -0,0 +1,85 @@ + + + + + + + + + {{ $t('common.reset') }} + + + + + +