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