From 0ce68379718b7c751b8a84aa4b28cbaf072e6c8f Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 24 六月 2026 18:05:53 +0800
Subject: [PATCH] 开发文档及规范
---
FRONTEND_DEVELOPMENT.md | 1020 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1,020 insertions(+), 0 deletions(-)
diff --git a/FRONTEND_DEVELOPMENT.md b/FRONTEND_DEVELOPMENT.md
new file mode 100644
index 0000000..423b6ba
--- /dev/null
+++ b/FRONTEND_DEVELOPMENT.md
@@ -0,0 +1,1020 @@
+# 鍓嶇寮�鍙戞枃妗�
+
+鏈枃妗e畾涔変簡椤圭洰鐨勫紑鍙戣鑼冦�佺粍浠跺鐢ㄦ寚鍗楀拰浠g爜椋庢牸瑕佹眰锛岀‘淇濆洟闃熷紑鍙戦鏍肩粺涓�銆�
+
+---
+
+## 涓�銆佸紑鍙戣鑼�
+
+### 1.1 缁勪欢寮�鍙戣鑼�
+
+#### 缁勪欢鍛藉悕
+
+```typescript
+// 鉁� 姝g‘锛氱粍浠舵枃浠朵娇鐢� PascalCase
+UserList.vue
+DictTag.vue
+TableAction.vue
+
+// 鉂� 閿欒锛氫娇鐢� kebab-case 鎴栧叾浠栧懡鍚�
+user-list.vue
+dict_tag.vue
+```
+
+#### 缁勪欢瀹氫箟
+
+```vue
+<script lang="ts" setup>
+// 鉁� 姝g‘锛氱粺涓�浣跨敤 <script setup lang="ts">
+defineOptions({ name: 'UserList' });
+
+// Props 瀹氫箟
+interface Props {
+ userId: number;
+ userName?: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ userName: '',
+});
+
+// Emits 瀹氫箟
+interface Emits {
+ (e: 'update', value: string): void;
+ (e: 'delete', id: number): void;
+}
+
+const emit = defineEmits<Emits>();
+</script>
+```
+
+#### TypeScript 瑙勮寖
+
+```typescript
+// 鉁� 姝g‘锛氱姝娇鐢� any锛屼娇鐢ㄥ叿浣撶被鍨�
+interface UserInfo {
+ id: number;
+ name: string;
+ email?: string;
+}
+
+function getUser(id: number): Promise<UserInfo> {
+ return request.get(`/user/${id}`);
+}
+
+// 鉂� 閿欒锛氫娇鐢� any
+function getUser(id: any): any {
+ return request.get(`/user/${id}`);
+}
+
+// 鉁� 姝g‘锛氫娇鐢� import type 瀵煎叆绫诲瀷
+import type { UserInfo } from '#/api/system/user';
+
+// 鉂� 閿欒锛氭櫘閫氬鍏ョ被鍨�
+import { UserInfo } from '#/api/system/user';
+```
+
+### 1.2 鐩綍缁撴瀯瑙勮寖
+
+```
+src/
+鈹溾攢鈹� api/ # API 鎺ュ彛瀹氫箟锛屾寜妯″潡鍒掑垎
+鈹� 鈹溾攢鈹� system/ # 绯荤粺妯″潡
+鈹� 鈹� 鈹溾攢鈹� user/
+鈹� 鈹� 鈹� 鈹斺攢鈹� index.ts
+鈹� 鈹� 鈹斺攢鈹� dept/
+鈹� 鈹� 鈹斺攢鈹� index.ts
+鈹� 鈹斺攢鈹� ...
+鈹溾攢鈹� components/ # 鍏叡缁勪欢锛堝叏灞�娉ㄥ唽锛�
+鈹溾攢鈹� views/ # 椤甸潰缁勪欢锛屾寜涓氬姟妯″潡鍒掑垎
+鈹� 鈹斺攢鈹� system/
+鈹� 鈹斺攢鈹� user/
+鈹� 鈹溾攢鈹� index.vue # 鍒楄〃椤�
+鈹� 鈹溾攢鈹� data.ts # 鏁版嵁瀹氫箟
+鈹� 鈹斺攢鈹� modules/ # 瀛愮粍浠�/寮圭獥
+鈹� 鈹斺攢鈹� form.vue
+鈹溾攢鈹� locales/ # 鍥介檯鍖�
+鈹溾攢鈹� router/ # 璺敱閰嶇疆
+鈹溾攢鈹� stores/ # Pinia 鐘舵�佺鐞�
+鈹溾攢鈹� utils/ # 宸ュ叿鍑芥暟
+鈹斺攢鈹� adapter/ # 閫傞厤鍣ㄩ厤缃�
+```
+
+### 1.3 API 鎺ュ彛瑙勮寖
+
+```typescript
+// src/api/system/user/index.ts
+
+import { request } from '#/utils/request';
+
+export namespace SystemUserApi {
+ /** 鐢ㄦ埛淇℃伅 */
+ export interface User {
+ id: number;
+ username: string;
+ nickname: string;
+ email?: string;
+ status: number;
+ deptId?: number;
+ createTime: string;
+ }
+
+ /** 鐢ㄦ埛鍒嗛〉鏌ヨ鍙傛暟 */
+ export interface PageParams {
+ pageNo: number;
+ pageSize: number;
+ username?: string;
+ status?: number;
+ deptId?: number;
+ }
+
+ /** 鍒嗛〉缁撴灉 */
+ export interface PageResult {
+ list: User[];
+ total: number;
+ }
+}
+
+/** 鑾峰彇鐢ㄦ埛鍒嗛〉鍒楄〃 */
+export function getUserPage(params: SystemUserApi.PageParams) {
+ return request.get<SystemUserApi.PageResult>('/system/user/page', { params });
+}
+
+/** 鑾峰彇鐢ㄦ埛璇︽儏 */
+export function getUser(id: number) {
+ return request.get<SystemUserApi.User>(`/system/user/get?id=${id}`);
+}
+
+/** 鍒涘缓鐢ㄦ埛 */
+export function createUser(data: SystemUserApi.User) {
+ return request.post('/system/user/create', data);
+}
+
+/** 鏇存柊鐢ㄦ埛 */
+export function updateUser(data: SystemUserApi.User) {
+ return request.put('/system/user/update', data);
+}
+
+/** 鍒犻櫎鐢ㄦ埛 */
+export function deleteUser(id: number) {
+ return request.delete(`/system/user/delete?id=${id}`);
+}
+```
+
+### 1.4 鏍峰紡瑙勮寖
+
+```vue
+<template>
+ <div class="user-list">
+ <!-- 浼樺厛浣跨敤 Tailwind CSS 绫� -->
+ <div class="flex items-center justify-between p-4 mb-2 bg-white rounded-lg">
+ <span class="text-base font-medium text-gray-900">鏍囬</span>
+ </div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+/* 缁勪欢鏍峰紡浣跨敤 scoped */
+.user-list {
+ /* 棰滆壊浣跨敤 CSS 鍙橀噺 */
+ background-color: var(--component-background);
+
+ /* 蹇呰鏃惰鐩栫粍浠跺簱鏍峰紡 */
+ :deep(.ant-table) {
+ border-radius: 8px;
+ }
+}
+</style>
+```
+
+---
+
+## 浜屻�佹牳蹇冪粍浠跺鐢ㄦ寚鍗�
+
+### 2.1 琛ㄦ牸缁勪欢 (useVbenVxeGrid)
+
+椤圭洰浣跨敤 VxeTable 灏佽鐨� `useVbenVxeGrid` 浣滀负鏍稿績琛ㄦ牸缁勪欢銆�
+
+#### 鍩虹鐢ㄦ硶
+
+```vue
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SystemUserApi } from '#/api/system/user';
+
+import { useVbenVxeGrid, TableAction, ACTION_ICON } from '#/adapter/vxe-table';
+import { getUserPage } from '#/api/system/user';
+
+// 琛ㄦ牸鍒楅厤缃�
+const columns: VxeTableGridOptions['columns'] = [
+ { type: 'checkbox', width: 50 },
+ { field: 'username', title: '鐢ㄦ埛鍚�', width: 120 },
+ { field: 'nickname', title: '鏄电О', width: 120 },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: 'common_status' },
+ },
+ },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', width: 180 },
+ { field: 'action', title: '鎿嶄綔', width: 200, slots: { default: 'actions' } },
+];
+
+// 琛ㄦ牸瀹炰緥
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: [
+ {
+ fieldName: 'username',
+ label: '鐢ㄦ埛鍚�',
+ component: 'Input',
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions('common_status'),
+ },
+ },
+ ],
+ },
+ gridOptions: {
+ columns,
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getUserPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ } as VxeTableGridOptions<SystemUserApi.User>,
+});
+
+// 鍒锋柊琛ㄦ牸
+function handleRefresh() {
+ gridApi.query();
+}
+</script>
+
+<template>
+ <Grid table-title="鐢ㄦ埛鍒楄〃">
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '缂栬緫',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['system:user:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['system:user:delete'],
+ popConfirm: {
+ title: `纭鍒犻櫎銆�${row.username}銆戝悧锛焋,
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+</template>
+```
+
+#### 琛ㄦ牸鍒楁覆鏌撳櫒
+
+| 娓叉煋鍣� | 鐢ㄩ�� | 绀轰緥 |
+|--------|------|------|
+| `CellImage` | 鍗曞浘灞曠ず | `cellRender: { name: 'CellImage' }` |
+| `CellImages` | 澶氬浘灞曠ず | `cellRender: { name: 'CellImages' }` |
+| `CellLink` | 閾炬帴鎸夐挳 | `cellRender: { name: 'CellLink', props: { text: '鏌ョ湅' } }` |
+| `CellTag` | 鏍囩灞曠ず | `cellRender: { name: 'CellTag', props: { color: 'blue' } }` |
+| `CellDict` | 瀛楀吀鏍囩 | `cellRender: { name: 'CellDict', props: { type: 'common_status' } }` |
+| `CellSwitch` | 寮�鍏冲垏鎹� | `cellRender: { name: 'CellSwitch', attrs: { beforeChange } }` |
+| `CellOperation` | 鎿嶄綔鎸夐挳 | `cellRender: { name: 'CellOperation', options: ['edit', 'delete'] }` |
+
+### 2.2 琛ㄥ崟寮圭獥 (useVbenModal)
+
+```vue
+<script lang="ts" setup>
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+import Form from './modules/form.vue';
+
+// 寮圭獥瀹炰緥
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+// 鎵撳紑鏂板寮圭獥
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+// 鎵撳紑缂栬緫寮圭獥
+function handleEdit(row: any) {
+ formModalApi.setData(row).open();
+}
+</script>
+
+<template>
+ <FormModal @success="handleRefresh" />
+</template>
+```
+
+琛ㄥ崟缁勪欢绀轰緥锛�
+
+```vue
+<!-- modules/form.vue -->
+<script lang="ts" setup>
+import type { SystemUserApi } from '#/api/system/user';
+
+import { computed, ref } from 'vue';
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+import { useVbenForm } from '#/adapter/form';
+import { createUser, updateUser } from '#/api/system/user';
+
+const emit = defineEmits(['success']);
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const values = await formApi.getValues();
+ if (formData.value?.id) {
+ await updateUser({ ...values, id: formData.value.id });
+ } else {
+ await createUser(values);
+ }
+ emit('success');
+ modalApi.close();
+ },
+});
+
+const formData = ref<SystemUserApi.User>();
+
+const [Form, formApi] = useVbenForm({
+ schema: [
+ {
+ fieldName: 'username',
+ label: '鐢ㄦ埛鍚�',
+ rules: 'required',
+ component: 'Input',
+ },
+ {
+ fieldName: 'nickname',
+ label: '鏄电О',
+ rules: 'required',
+ component: 'Input',
+ },
+ {
+ fieldName: 'email',
+ label: '閭',
+ rules: 'email',
+ component: 'Input',
+ },
+ {
+ fieldName: 'deptId',
+ label: '閮ㄩ棬',
+ component: 'TreeSelect',
+ },
+ ],
+});
+
+// 鐩戝惉寮圭獥鎵撳紑
+modalApi.onOpen((data) => {
+ formData.value = data;
+ if (data) {
+ formApi.setValues(data);
+ }
+});
+</script>
+
+<template>
+ <Modal title="鐢ㄦ埛淇℃伅">
+ <Form />
+ </Modal>
+</template>
+```
+
+### 2.3 瀛楀吀缁勪欢
+
+#### DictTag - 瀛楀吀鏍囩
+
+鐢ㄤ簬灞曠ず鏁版嵁瀛楀吀鍊肩殑鏍囩褰㈠紡銆�
+
+```vue
+<script lang="ts" setup>
+import DictTag from '#/components/dict-tag/dict-tag.vue';
+</script>
+
+<template>
+ <DictTag type="common_status" :value="1" />
+</template>
+```
+
+#### DictSelect - 瀛楀吀閫夋嫨鍣�
+
+鐢ㄤ簬琛ㄥ崟涓粠瀛楀吀閫夋嫨鍊笺��
+
+```vue
+<script lang="ts" setup>
+import DictSelect from '#/components/form-create/components/dict-select.vue';
+</script>
+
+<template>
+ <!-- 涓嬫媺閫夋嫨 -->
+ <DictSelect v-model="form.status" dict-type="common_status" />
+
+ <!-- 鍗曢�夋寜閽� -->
+ <DictSelect
+ v-model="form.status"
+ dict-type="common_status"
+ select-type="radio"
+ />
+
+ <!-- 澶嶉�夋 -->
+ <DictSelect
+ v-model="form.types"
+ dict-type="common_status"
+ select-type="checkbox"
+ />
+</template>
+```
+
+#### 瀛楀吀宸ュ叿鍑芥暟
+
+```typescript
+import { getDictOptions, getDictLabel, getDictObj } from '#/packages/effects/hooks/src';
+
+// 鑾峰彇瀛楀吀閫夐」鍒楄〃锛堢敤浜� Select/Radio 绛夌粍浠讹級
+const options = getDictOptions('common_status', 'number');
+
+// 鑾峰彇瀛楀吀鏍囩鏂囨湰
+const label = getDictLabel('common_status', 1); // "寮�鍚�"
+
+// 鑾峰彇瀛楀吀瀹屾暣瀵硅薄
+const dictObj = getDictObj('common_status', 1);
+// { label: '寮�鍚�', value: 1, colorType: 'success', cssClass: '' }
+```
+
+### 2.4 琛ㄦ牸鎿嶄綔缁勪欢 (TableAction)
+
+鐢ㄤ簬琛ㄦ牸涓殑鎿嶄綔鎸夐挳锛屾敮鎸佹潈闄愭帶鍒跺拰浜屾纭銆�
+
+```vue
+<script lang="ts" setup>
+import { TableAction, ACTION_ICON } from '#/adapter/vxe-table';
+</script>
+
+<template>
+ <TableAction
+ :actions="[
+ {
+ label: '缂栬緫',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['system:user:update'],
+ onClick: handleEdit,
+ },
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['system:user:delete'],
+ popConfirm: {
+ title: '纭鍒犻櫎鍚楋紵',
+ confirm: handleDelete,
+ },
+ },
+ ]"
+ :drop-down-actions="[
+ {
+ label: '鍒嗛厤瑙掕壊',
+ auth: ['system:permission:assign-user-role'],
+ onClick: handleAssignRole,
+ },
+ ]"
+ />
+</template>
+```
+
+### 2.5 鏂囦欢涓婁紶缁勪欢
+
+#### ImageUpload - 鍥剧墖涓婁紶
+
+```vue
+<script lang="ts" setup>
+import ImageUpload from '#/components/upload/image-upload.vue';
+
+const imageUrl = ref('');
+const imageList = ref<string[]>([]);
+</script>
+
+<template>
+ <!-- 鍗曞浘涓婁紶 -->
+ <ImageUpload v-model="imageUrl" :max-number="1" />
+
+ <!-- 澶氬浘涓婁紶 -->
+ <ImageUpload v-model="imageList" :max-number="5" />
+
+ <!-- 闄愬埗澶у皬鍜屾牸寮� -->
+ <ImageUpload
+ v-model="imageUrl"
+ :max-size="5"
+ :accept="['.jpg', '.jpeg', '.png']"
+ />
+</template>
+```
+
+#### FileUpload - 鏂囦欢涓婁紶
+
+```vue
+<script lang="ts" setup>
+import FileUpload from '#/components/upload/file-upload.vue';
+
+const fileList = ref<string[]>([]);
+</script>
+
+<template>
+ <FileUpload
+ v-model="fileList"
+ :max-number="3"
+ :max-size="10"
+ :accept="['.pdf', '.doc', '.docx']"
+ />
+</template>
+```
+
+### 2.6 鎻忚堪鍒楄〃缁勪欢 (Description)
+
+鐢ㄤ簬璇︽儏椤靛睍绀烘暟鎹��
+
+```vue
+<script lang="ts" setup>
+import Description from '#/components/description/description.vue';
+
+const data = {
+ username: 'admin',
+ nickname: '绠$悊鍛�',
+ email: 'admin@example.com',
+ status: 0,
+ createTime: '2024-01-01 12:00:00',
+};
+
+const schema = [
+ { field: 'username', label: '鐢ㄦ埛鍚�' },
+ { field: 'nickname', label: '鏄电О' },
+ { field: 'email', label: '閭' },
+ {
+ field: 'status',
+ label: '鐘舵��',
+ render: (value) => h(DictTag, { type: 'common_status', value }),
+ },
+ { field: 'createTime', label: '鍒涘缓鏃堕棿' },
+];
+</script>
+
+<template>
+ <Description title="鍩烘湰淇℃伅" :data="data" :schema="schema" />
+</template>
+```
+
+---
+
+## 涓夈�佺姸鎬佺鐞�
+
+### 3.1 浣跨敤 Pinia Store
+
+椤圭洰浣跨敤 Pinia 杩涜鐘舵�佺鐞嗭紝Store 瀹氫箟鍦� `src/packages/stores/src` 涓��
+
+#### 甯哥敤 Store
+
+| Store | 鐢ㄩ�� | 瀵煎叆璺緞 |
+|-------|------|----------|
+| `useUserStore` | 鐢ㄦ埛淇℃伅 | `#/packages/stores/src` |
+| `useAccessStore` | 鏉冮檺/Token | `#/packages/stores/src` |
+| `useDictStore` | 鏁版嵁瀛楀吀 | `#/packages/stores/src` |
+
+#### 绀轰緥
+
+```typescript
+import { useUserStore, useAccessStore } from '#/packages/stores/src';
+
+const userStore = useUserStore();
+const accessStore = useAccessStore();
+
+// 鑾峰彇鐢ㄦ埛淇℃伅
+const userInfo = userStore.userInfo;
+
+// 妫�鏌ユ潈闄�
+const { hasAccessByCodes } = useAccess();
+const canEdit = hasAccessByCodes(['system:user:update']);
+```
+
+---
+
+## 鍥涖�佹潈闄愭帶鍒�
+
+### 4.1 鎸夐挳鏉冮檺
+
+```vue
+<script lang="ts" setup>
+import { useAccess } from '#/packages/effects/access/src';
+
+const { hasAccessByCodes } = useAccess();
+</script>
+
+<template>
+ <!-- 鏂瑰紡涓�锛氶�氳繃 TableAction 鐨� auth 灞炴�� -->
+ <TableAction
+ :actions="[
+ {
+ label: '缂栬緫',
+ auth: ['system:user:update'],
+ onClick: handleEdit,
+ },
+ ]"
+ />
+
+ <!-- 鏂瑰紡浜岋細閫氳繃 v-if 鎵嬪姩鍒ゆ柇 -->
+ <Button v-if="hasAccessByCodes(['system:user:create'])" type="primary">
+ 鏂板
+ </Button>
+</template>
+```
+
+### 4.2 璺敱鏉冮檺
+
+鍦ㄨ矾鐢遍厤缃腑閫氳繃 `meta.authorities` 璁剧疆鏉冮檺锛�
+
+```typescript
+{
+ path: 'user',
+ name: 'SystemUser',
+ component: () => import('#/views/system/user/index.vue'),
+ meta: {
+ title: '鐢ㄦ埛绠$悊',
+ authorities: ['system:user:visit'],
+ },
+}
+```
+
+---
+
+## 浜斻�佸浗闄呭寲
+
+### 5.1 浣跨敤鏂瑰紡
+
+```vue
+<script lang="ts" setup>
+import { $t } from '#/locales';
+</script>
+
+<template>
+ <Button>{{ $t('common.save') }}</Button>
+ <span>{{ $t('ui.actionMessage.deleteSuccess', ['鐢ㄦ埛']) }}</span>
+</template>
+```
+
+### 5.2 娣诲姞鍥介檯鍖栨枃鏈�
+
+鍦� `src/locales` 鐩綍涓嬬殑璇█鏂囦欢涓坊鍔狅細
+
+```json
+// src/locales/zh-CN.json
+{
+ "common": {
+ "save": "淇濆瓨",
+ "cancel": "鍙栨秷"
+ },
+ "ui": {
+ "actionMessage": {
+ "deleteSuccess": "鍒犻櫎銆恵0}銆戞垚鍔�"
+ }
+ }
+}
+```
+
+---
+
+## 鍏�佸父鐢� Hooks
+
+### 6.1 usePagination - 鍒嗛〉
+
+```typescript
+import { usePagination } from '#/packages/effects/hooks/src';
+
+const { page, pageSize, total, setPage, setTotal } = usePagination();
+```
+
+### 6.2 useTabs - 鏍囩椤垫搷浣�
+
+```typescript
+import { useTabs } from '#/packages/effects/hooks/src';
+
+const { closeOtherTabs, closeTab, refreshTab } = useTabs();
+
+// 鍏抽棴鍏朵粬鏍囩椤�
+await closeOtherTabs();
+
+// 鍒锋柊褰撳墠椤�
+await refreshTab();
+```
+
+### 6.3 useWatermark - 姘村嵃
+
+```typescript
+import { useWatermark } from '#/packages/effects/hooks/src';
+
+const { updateWatermark, destroyWatermark } = useWatermark();
+
+// 璁剧疆姘村嵃
+await updateWatermark({
+ content: '鐢ㄦ埛鍚�',
+});
+
+// 閿�姣佹按鍗�
+destroyWatermark();
+```
+
+---
+
+## 涓冦�佸伐鍏峰嚱鏁�
+
+### 7.1 甯哥敤宸ュ叿
+
+```typescript
+import {
+ formatDateTime, // 鏃ユ湡鏍煎紡鍖�
+ formatDate, // 鏃ユ湡鏍煎紡鍖栵紙涓嶅惈鏃堕棿锛�
+ downloadFileFromBlobPart, // 鏂囦欢涓嬭浇
+ isEmpty, // 鍒ょ┖
+ isFunction, // 鍒ゆ柇鍑芥暟
+ isString, // 鍒ゆ柇瀛楃涓�
+} from '#/packages/utils/src';
+
+// 鏃ユ湡鏍煎紡鍖�
+formatDateTime('2024-01-01T12:00:00'); // "2024-01-01 12:00:00"
+
+// 鏂囦欢涓嬭浇
+downloadFileFromBlobPart({
+ fileName: '鐢ㄦ埛鍒楄〃.xlsx',
+ source: blobData,
+});
+
+// 鍒ょ┖
+isEmpty([]); // true
+isEmpty({}); // true
+isEmpty(null); // true
+```
+
+### 7.2 鏁板瓧鏍煎紡鍖�
+
+```typescript
+import {
+ erpNumberFormatter, // 鏁板瓧鏍煎紡鍖�
+ fenToYuan, // 鍒嗚浆鍏�
+ formatFileSize, // 鏂囦欢澶у皬鏍煎紡鍖�
+} from '#/packages/utils/src';
+
+// 淇濈暀涓や綅灏忔暟
+erpNumberFormatter(1234.567, 2); // "1,234.57"
+
+// 鍒嗚浆鍏�
+fenToYuan(12345); // 123.45
+
+// 鏂囦欢澶у皬
+formatFileSize(1024 * 1024); // "1.00 MB"
+```
+
+---
+
+## 鍏�佷唬鐮佺ず渚嬶細鏍囧噯 CRUD 椤甸潰
+
+```vue
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SystemUserApi } from '#/api/system/user';
+
+import { ref } from 'vue';
+
+import { confirm, Page, useVbenModal } from '#/packages/effects/common-ui/src';
+import { getDictOptions } from '#/packages/effects/hooks/src';
+import { downloadFileFromBlobPart, isEmpty } from '#/packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenVxeGrid, TableAction, ACTION_ICON } from '#/adapter/vxe-table';
+import {
+ getUserPage,
+ deleteUser,
+ createUser,
+ updateUser,
+ exportUser,
+} from '#/api/system/user';
+import { $t } from '#/locales';
+
+import Form from './modules/form.vue';
+
+// ========== 寮圭獥瀹氫箟 ==========
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+// ========== 琛ㄦ牸瀹氫箟 ==========
+const checkedIds = ref<number[]>([]);
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: [
+ {
+ fieldName: 'username',
+ label: '鐢ㄦ埛鍚�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ敤鎴峰悕' },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions('common_status', 'number'),
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ],
+ },
+ gridOptions: {
+ columns: [
+ { type: 'checkbox', width: 50 },
+ { field: 'username', title: '鐢ㄦ埛鍚�', width: 120 },
+ { field: 'nickname', title: '鏄电О', width: 120 },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 100,
+ cellRender: { name: 'CellDict', props: { type: 'common_status' } },
+ },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', width: 180 },
+ { field: 'action', title: '鎿嶄綔', width: 180, slots: { default: 'actions' } },
+ ],
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getUserPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SystemUserApi.User>,
+ gridEvents: {
+ checkboxAll: ({ records }) => {
+ checkedIds.value = records.map((item) => item.id!);
+ },
+ checkboxChange: ({ records }) => {
+ checkedIds.value = records.map((item) => item.id!);
+ },
+ },
+});
+
+// ========== 鎿嶄綔鏂规硶 ==========
+function handleRefresh() {
+ gridApi.query();
+}
+
+async function handleExport() {
+ const data = await exportUser(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鐢ㄦ埛.xls', source: data });
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SystemUserApi.User) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SystemUserApi.User) {
+ await deleteUser(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.username]));
+ handleRefresh();
+}
+
+async function handleDeleteBatch() {
+ await confirm($t('ui.actionMessage.deleteBatchConfirm'));
+ // ... 鎵归噺鍒犻櫎閫昏緫
+}
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+
+ <Grid table-title="鐢ㄦ埛鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鐢ㄦ埛']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['system:user:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['system:user:export'],
+ onClick: handleExport,
+ },
+ {
+ label: $t('ui.actionTitle.deleteBatch'),
+ type: 'primary',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ disabled: isEmpty(checkedIds),
+ auth: ['system:user:delete'],
+ onClick: handleDeleteBatch,
+ },
+ ]"
+ />
+ </template>
+
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['system:user:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['system:user:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.username]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
+```
+
+---
+
+## 涔濄�佹敞鎰忎簨椤�
+
+1. **璺緞鍒悕**锛歚#/*` 鎸囧悜 `./src/*`锛屽鍏ユ椂浣跨敤 `#/components/xxx` 鑰岄潪鐩稿璺緞
+2. **琛ㄥ崟楠岃瘉**锛氫娇鐢� VeeValidate + Zod锛屽湪 schema 涓�氳繃 `rules` 灞炴�у畾涔�
+3. **HTML 瀹夊叏**锛氱敤鎴疯緭鍏ョ殑 HTML 浣跨敤 `v-dompurify-html` 澶勭悊
+4. **鏁忔劅鏁版嵁**锛氫娇鐢� `secure-ls` 鍔犲瘑瀛樺偍
+5. **浠g爜娉ㄩ噴**锛氬彧鍦� WHY 涓嶆槑鏄炬椂娣诲姞娉ㄩ噴锛岄伩鍏嶆棤鎰忎箟鐨勬敞閲�
+6. **缁勪欢鍛藉悕**锛氬繀椤讳娇鐢� `defineOptions({ name: 'ComponentName' })` 瀹氫箟缁勪欢鍚�
+
+---
+
+## 鍗併�佺浉鍏宠祫婧�
+
+- [Vue 3 鏂囨。](https://vuejs.org/)
+- [Ant Design Vue 鏂囨。](https://antdv.com/)
+- [VxeTable 鏂囨。](https://vxetable.cn/)
+- [Tailwind CSS 鏂囨。](https://tailwindcss.com/)
+- [椤圭洰 CLAUDE.md](./CLAUDE.md)
--
Gitblit v1.9.3