@icc-grow/web-components 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +15 -0
  2. package/dist/es/components/i-avatar-group/src/avatar-group.mjs +1 -1
  3. package/dist/es/components/i-form-item/src/form-item.mjs +1 -1
  4. package/dist/es/components/i-form-item/src/form-item.vue_vue_type_script_setup_true_lang.mjs +2 -1
  5. package/dist/es/components/i-image/src/image.vue_vue_type_script_setup_true_lang.mjs +20 -20
  6. package/dist/es/components/i-tooltip/src/tooltip-content.mjs +1 -1
  7. package/dist/es/style.css +1 -1
  8. package/dist/es/utils/use-intersection-observer.mjs +31 -0
  9. package/dist/lib/components/i-avatar-group/src/avatar-group.cjs +1 -1
  10. package/dist/lib/components/i-form-item/src/form-item.cjs +1 -1
  11. package/dist/lib/components/i-form-item/src/form-item.vue_vue_type_script_setup_true_lang.cjs +1 -1
  12. package/dist/lib/components/i-image/src/image.vue_vue_type_script_setup_true_lang.cjs +1 -1
  13. package/dist/lib/components/i-tooltip/src/tooltip-content.cjs +1 -1
  14. package/dist/lib/style.css +1 -1
  15. package/dist/lib/utils/use-intersection-observer.cjs +1 -0
  16. package/dist/types/components/i-form/index.d.ts +3 -3
  17. package/dist/types/components/i-form/index.d.ts.map +1 -1
  18. package/dist/types/components/i-form/src/form.vue.d.ts +1 -1
  19. package/dist/types/components/i-form/src/form.vue.d.ts.map +1 -1
  20. package/dist/types/components/i-form/src/types.d.ts +16 -4
  21. package/dist/types/components/i-form/src/types.d.ts.map +1 -1
  22. package/dist/types/components/i-form-item/index.d.ts +3 -3
  23. package/dist/types/components/i-form-item/index.d.ts.map +1 -1
  24. package/dist/types/components/i-form-item/src/form-item.vue.d.ts +2 -2
  25. package/dist/types/components/i-form-item/src/form-item.vue.d.ts.map +1 -1
  26. package/dist/types/components/i-form-item/src/types.d.ts +2 -2
  27. package/dist/types/components/i-form-item/src/types.d.ts.map +1 -1
  28. package/dist/types/components/i-tooltip/index.d.ts +9 -2
  29. package/dist/types/components/i-tooltip/index.d.ts.map +1 -1
  30. package/dist/types/components/i-tooltip/src/tooltip-content.vue.d.ts +15 -2
  31. package/dist/types/components/i-tooltip/src/tooltip-content.vue.d.ts.map +1 -1
  32. package/dist/types/components/i-tooltip/src/tooltip.vue.d.ts +18 -4
  33. package/dist/types/components/i-tooltip/src/tooltip.vue.d.ts.map +1 -1
  34. package/dist/types/utils/use-intersection-observer.d.ts +19 -0
  35. package/dist/types/utils/use-intersection-observer.d.ts.map +1 -0
  36. package/package.json +2 -3
  37. package/skills/icc-web-components/SKILL.md +0 -191
  38. package/skills/icc-web-components/patterns/crud-list.md +0 -195
  39. package/skills/icc-web-components/patterns/dialog-form.md +0 -180
  40. package/skills/icc-web-components/patterns/form-page.md +0 -139
  41. package/skills/icc-web-components/patterns/tabs-page.md +0 -164
  42. package/skills/icc-web-components/patterns/tree-manage.md +0 -169
  43. package/skills/icc-web-components/references/i-avatar.md +0 -29
  44. package/skills/icc-web-components/references/i-badge.md +0 -19
  45. package/skills/icc-web-components/references/i-card.md +0 -12
  46. package/skills/icc-web-components/references/i-color-picker.md +0 -17
  47. package/skills/icc-web-components/references/i-crud-page.md +0 -204
  48. package/skills/icc-web-components/references/i-dialog.md +0 -36
  49. package/skills/icc-web-components/references/i-draggable.md +0 -46
  50. package/skills/icc-web-components/references/i-dropdown.md +0 -21
  51. package/skills/icc-web-components/references/i-empty.md +0 -15
  52. package/skills/icc-web-components/references/i-feedback.md +0 -50
  53. package/skills/icc-web-components/references/i-form.md +0 -223
  54. package/skills/icc-web-components/references/i-icon.md +0 -21
  55. package/skills/icc-web-components/references/i-image.md +0 -34
  56. package/skills/icc-web-components/references/i-loading.md +0 -25
  57. package/skills/icc-web-components/references/i-menu.md +0 -22
  58. package/skills/icc-web-components/references/i-popover.md +0 -51
  59. package/skills/icc-web-components/references/i-progress.md +0 -14
  60. package/skills/icc-web-components/references/i-scrollbar.md +0 -20
  61. package/skills/icc-web-components/references/i-space.md +0 -15
  62. package/skills/icc-web-components/references/i-steps.md +0 -12
  63. package/skills/icc-web-components/references/i-tabs.md +0 -16
  64. package/skills/icc-web-components/references/i-tag.md +0 -35
  65. package/skills/icc-web-components/references/i-time-picker.md +0 -19
  66. package/skills/icc-web-components/references/i-transition.md +0 -17
  67. package/skills/icc-web-components/references/i-tree-select.md +0 -26
  68. package/skills/icc-web-components/references/i-tree.md +0 -34
  69. package/skills/icc-web-components/references/i-upload.md +0 -25
  70. package/skills/icc-web-components/references/i-virtual-list.md +0 -24
  71. package/skills/icc-web-components/references/i-virtual-table.md +0 -20
  72. package/skills/icc-web-components/rules/composition.md +0 -28
  73. package/skills/icc-web-components/rules/forms.md +0 -28
  74. package/skills/icc-web-components/rules/styling.md +0 -45
@@ -1,180 +0,0 @@
1
- # Pattern: 弹窗表单
2
-
3
- > 弹窗/抽屉中嵌入表单
4
-
5
- ## IDialog + IForm 骨架
6
-
7
- ```vue
8
- <!-- UserFormDialog.vue — 独立子组件 -->
9
- <template>
10
- <IDialog
11
- v-model:visible="visible"
12
- :title="isEdit ? '编辑用户' : '新增用户'"
13
- width="560px"
14
- @closed="handleClosed"
15
- @open="handleOpen"
16
- >
17
- <IForm :model="form" :rules="rules" ref="formRef">
18
- <IFormItem label="姓名" prop="name" >
19
- <IInput v-model="form.name" placeholder="请输入姓名" />
20
- </IFormItem>
21
-
22
- <IFormItem label="邮箱" prop="email" >
23
- <IInput v-model="form.email" placeholder="请输入邮箱" />
24
- </IFormItem>
25
-
26
- <IFormItem label="角色" prop="role" >
27
- <ISelect
28
- v-model="form.role"
29
- :options="roleOptions"
30
- placeholder="请选择角色"
31
- />
32
- </IFormItem>
33
- </IForm>
34
-
35
- <template #footer>
36
- <ISpace :size="12">
37
- <IButton type="black" plain @click="visible = false">取消</IButton>
38
- <IButton :loading="loading" @click="handleSubmit">确定</IButton>
39
- </ISpace>
40
- </template>
41
- </IDialog>
42
- </template>
43
-
44
- <script setup lang="ts">
45
- import { ref, reactive, nextTick } from 'vue'
46
- import {
47
- IDialog, IForm, IFormItem, IInput, ISelect,
48
- IButton, ISpace, IMessage,
49
- } from '@icc-grow/web-components'
50
- import type { IFormInstance } from '@icc-grow/web-components'
51
- import type { Rules } from 'async-validator'
52
-
53
- interface FormItem {
54
- id?:string
55
- name:string
56
- email:string
57
- role:number
58
- }
59
-
60
-
61
- interface Props {
62
- data:FormItem,
63
- visible:boolean
64
- }
65
-
66
- interface Emits {
67
- success:()=>void
68
- 'update:visible':(value:boolean)=>void
69
- }
70
-
71
- // ---- Props / Emits ----
72
- const props = withDefaults(defineProps<Props>(), {});
73
-
74
-
75
- const emit = defineEmits<Emits>()
76
-
77
- // ---- 状态 ----
78
- const visible = ref(false)
79
- const isEdit = ref(false)
80
- const loading = ref(false)
81
- const formRef = ref<IFormInstance>()
82
-
83
-
84
- const form = reactive<FormItem>({
85
- id:null,
86
- name: '',
87
- email: '',
88
- role: 0,
89
- })
90
-
91
- const rules: Rules = {
92
- name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
93
- email: [
94
- { required: true, message: '请输入邮箱', trigger: 'blur' },
95
- { type: 'email', message: '邮箱格式不正确', trigger: 'blur' },
96
- ],
97
- role: [{ required: true, message: '请选择角色', trigger: 'change' }],
98
- }
99
- // ---- 方法 ----
100
- function handleOpen() {
101
- isEdit.value = !!props.data?.id
102
- Object.assign(form, props.data)
103
- }
104
-
105
-
106
- async function handleSubmit() {
107
- try {
108
- const {valid, firstMessage} = await formRef.value?.validate()
109
- if(!valid){
110
- IMessage.error(firstMessage)
111
- return;
112
- }
113
- loading.value = true
114
- if (isEdit.value) {
115
- await api.updateUser(editId.value!, form)
116
- } else {
117
- await api.createUser(form)
118
- }
119
- IMessage.success(isEdit.value ? '编辑成功' : '新增成功')
120
- visible.value = false
121
- emit('success')
122
- } catch {
123
- // 校验失败
124
- } finally {
125
- loading.value = false
126
- }
127
- }
128
-
129
- function handleClosed() {
130
- formRef.value?.resetFields()
131
- emit('update:visible',false);
132
- }
133
-
134
- </script>
135
- ```
136
-
137
- ## 父组件调用
138
-
139
- ```vue
140
- <script setup lang="ts">
141
- const formDialogRef = ref()
142
-
143
- function handleAdd() {
144
- formDialogRef.value?.openAdd()
145
- }
146
-
147
- function handleEdit(row) {
148
- formDialogRef.value?.openEdit(row)
149
- }
150
- </script>
151
-
152
- <template>
153
- <!-- 页面内容... -->
154
- <UserFormDialog ref="formDialogRef" :role-options="roleOptions" @success="fetchList" />
155
- </template>
156
- ```
157
-
158
- ## IDrawer 变体
159
-
160
- 将 `IDialog` 替换为 `IDrawer` 即可,API 几乎一致:
161
-
162
- ```vue
163
- <IDrawer
164
- v-model:visible="visible"
165
- :title="isEdit ? '编辑用户' : '新增用户'"
166
- placement="right"
167
- size="560px"
168
- @closed="handleClosed"
169
- >
170
- <!-- 内容和 footer 同上 -->
171
- </IDrawer>
172
- ```
173
-
174
- ## 关键要点
175
-
176
- 1. **弹窗表单必须抽离为独立子组件**,不要混在列表页中
177
- 2. 通过 `defineExpose` 暴露 `openAdd` / `openEdit` 方法给父组件
178
- 3. `@closed` 事件中调用 `resetFields()` 重置表单
179
- 4. 弹窗内表单控件用 `class="w-full"` 撑满
180
- 5. `#footer` 用 `ISpace` 排列取消和确定按钮
@@ -1,139 +0,0 @@
1
- # Pattern: 表单页
2
-
3
- > 整页表单 + 提交/返回按钮
4
-
5
- ## 完整骨架
6
-
7
- ```vue
8
- <template>
9
- <IPageLayout title="新增用户">
10
- <template #header-action>
11
- <ISpace :size="12">
12
- <IButton @click="handleBack">返回</IButton>
13
- <IButton type="primary" :loading="submitting" @click="handleSubmit">保存</IButton>
14
- </ISpace>
15
- </template>
16
-
17
- <ICard title="基本信息">
18
- <IForm :model="form" :rules="rules" ref="formRef">
19
- <IFormItem label="姓名" prop="name" required>
20
- <IInput v-model="form.name" placeholder="请输入姓名" class="w-[400px]" />
21
- </IFormItem>
22
-
23
- <IFormItem label="手机号" prop="phone" required>
24
- <IInput v-model="form.phone" placeholder="请输入手机号" class="w-[400px]" />
25
- </IFormItem>
26
-
27
- <IFormItem label="邮箱" prop="email">
28
- <IInput v-model="form.email" placeholder="请输入邮箱" class="w-[400px]" />
29
- </IFormItem>
30
-
31
- <IFormItem label="部门" prop="department" required>
32
- <ITreeSelect
33
- v-model="form.department"
34
- :data="departmentTree"
35
- placeholder="请选择部门"
36
- class="w-[400px]"
37
- />
38
- </IFormItem>
39
-
40
- <IFormItem label="角色" prop="roles" required>
41
- <ISelect
42
- v-model="form.roles"
43
- :options="roleOptions"
44
- multiple
45
- placeholder="请选择角色"
46
- class="w-[400px]"
47
- />
48
- </IFormItem>
49
-
50
- <IFormItem label="状态" prop="status">
51
- <ISwitch v-model="form.status" :true-value="1" :false-value="0" />
52
- </IFormItem>
53
-
54
- <IFormItem label="备注" prop="remark">
55
- <ITextarea
56
- v-model="form.remark"
57
- placeholder="请输入备注"
58
- :autosize="{ minRows: 3, maxRows: 6 }"
59
- :maxlength="200"
60
- show-word-limit
61
- class="w-[400px]"
62
- />
63
- </IFormItem>
64
- </IForm>
65
- </ICard>
66
- </IPageLayout>
67
- </template>
68
-
69
- <script setup lang="ts">
70
- import { ref, reactive } from 'vue'
71
- import {
72
- IPageLayout, ICard, IForm, IFormItem,
73
- IInput, ITextarea, ISelect, ITreeSelect,
74
- ISwitch, IButton, ISpace, IMessage,
75
- } from '@icc-grow/web-components'
76
- import type { FormExpose } from '@icc-grow/web-components'
77
- import type { Rules } from 'async-validator'
78
-
79
- // ---- 表单数据 ----
80
- const formRef = ref<FormExpose>()
81
- const submitting = ref(false)
82
-
83
- const form = reactive({
84
- name: '',
85
- phone: '',
86
- email: '',
87
- department: undefined as string | undefined,
88
- roles: [] as number[],
89
- status: 1,
90
- remark: '',
91
- })
92
-
93
- // ---- 校验规则 ----
94
- const rules: Rules = {
95
- name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
96
- phone: [
97
- { required: true, message: '请输入手机号', trigger: 'blur' },
98
- { pattern: /^1\d{10}$/, message: '手机号格式不正确', trigger: 'blur' },
99
- ],
100
- email: [
101
- { type: 'email', message: '邮箱格式不正确', trigger: 'blur' },
102
- ],
103
- department: [{ required: true, message: '请选择部门', trigger: 'change' }],
104
- roles: [{ required: true, message: '请选择角色', trigger: 'change' }],
105
- }
106
-
107
- // ---- 选项数据 ----
108
- const departmentTree = ref([]) // 从接口获取
109
- const roleOptions = ref([]) // 从接口获取
110
-
111
- // ---- 方法 ----
112
- async function handleSubmit() {
113
- try {
114
- await formRef.value?.validate()
115
- submitting.value = true
116
- await api.saveUser(form)
117
- IMessage.success('保存成功')
118
- handleBack()
119
- } catch {
120
- // 校验失败,自动显示错误
121
- } finally {
122
- submitting.value = false
123
- }
124
- }
125
-
126
- function handleBack() {
127
- router.back()
128
- }
129
- </script>
130
- ```
131
-
132
- ## 关键要点
133
-
134
- 1. 标题栏用 `#header-action` 放返回和保存按钮
135
- 2. 表单用 `ICard` 包裹,视觉分区
136
- 3. 所有输入组件限制宽度 `class="w-[400px]"`
137
- 4. 校验规则统一在 `rules` 对象中定义
138
- 5. 提交前调用 `formRef.value?.validate()`
139
- 6. **编辑场景**:onMounted 中根据路由 ID 加载数据,回填 form
@@ -1,164 +0,0 @@
1
- # Pattern: 标签页切换
2
-
3
- > 顶部 Tabs 切换多个子页面/子模块
4
-
5
- ## 完整骨架
6
-
7
- ```vue
8
- <template>
9
- <IPageLayout title="系统设置">
10
- <!-- Tabs 放在搜索区位置 -->
11
- <template #search>
12
- <ITabs v-model="activeTab" :options="tabOptions" @change="handleTabChange" />
13
- </template>
14
-
15
- <!-- 根据 activeTab 条件渲染不同内容 -->
16
- <template v-if="activeTab === 'basic'">
17
- <BasicSettings />
18
- </template>
19
-
20
- <template v-else-if="activeTab === 'security'">
21
- <SecuritySettings />
22
- </template>
23
-
24
- <template v-else-if="activeTab === 'notification'">
25
- <NotificationSettings />
26
- </template>
27
- </IPageLayout>
28
- </template>
29
-
30
- <script setup lang="ts">
31
- import { ref } from 'vue'
32
- import { IPageLayout, ITabs } from '@icc-grow/web-components'
33
- import type { TabItem } from '@icc-grow/web-components'
34
- import BasicSettings from './components/BasicSettings.vue'
35
- import SecuritySettings from './components/SecuritySettings.vue'
36
- import NotificationSettings from './components/NotificationSettings.vue'
37
-
38
- const activeTab = ref('basic')
39
-
40
- const tabOptions: TabItem[] = [
41
- { label: '基本设置', value: 'basic' },
42
- { label: '安全设置', value: 'security' },
43
- { label: '通知设置', value: 'notification' },
44
- ]
45
-
46
- function handleTabChange(value: string | number) {
47
- // 可选:路由同步
48
- // router.replace({ query: { tab: value } })
49
- }
50
- </script>
51
- ```
52
-
53
- ## 变体:Tabs 内嵌 CRUD 列表
54
-
55
- 当每个 Tab 页面是独立的 CRUD 列表时:
56
-
57
- ```vue
58
- <template>
59
- <IPageLayout title="订单管理">
60
- <template #search>
61
- <ITabs v-model="activeTab" :options="tabOptions" @change="handleTabChange" />
62
- </template>
63
-
64
- <!-- 搜索区跟随 Tab 切换 -->
65
- <!-- 注意:搜索区需要放在 default 插槽内,因为 #search 已被 Tabs 占用 -->
66
- <div class="mb-16">
67
- <IPageSearch>
68
- <IInput v-model="query.keyword" placeholder="订单号" clearable />
69
- <template #action>
70
- <IButton type="primary" @click="handleSearch">查询</IButton>
71
- </template>
72
- </IPageSearch>
73
- </div>
74
-
75
- <ITable v-loading="loading" :data="tableData" :columns="currentColumns" />
76
-
77
- <template #footer>
78
- <IPagination
79
- v-model:currentPage="pagination.currentPage"
80
- v-model:pageSize="pagination.pageSize"
81
- :total="pagination.total"
82
- @change="fetchList"
83
- />
84
- </template>
85
- </IPageLayout>
86
- </template>
87
-
88
- <script setup lang="ts">
89
- import { ref, reactive, computed, watch } from 'vue'
90
- import {
91
- IPageLayout, ITabs, IPageSearch, ITable, IPagination,
92
- IInput, IButton,
93
- } from '@icc-grow/web-components'
94
- import type { TabItem, TableColumn } from '@icc-grow/web-components'
95
-
96
- const activeTab = ref('pending')
97
-
98
- const tabOptions: TabItem[] = [
99
- { label: '待处理', value: 'pending', count: 12 },
100
- { label: '进行中', value: 'processing', count: 5 },
101
- { label: '已完成', value: 'completed' },
102
- { label: '已取消', value: 'cancelled' },
103
- ]
104
-
105
- const query = reactive({ keyword: '' })
106
- const loading = ref(false)
107
- const tableData = ref([])
108
- const pagination = reactive({ currentPage: 1, pageSize: 20, total: 0 })
109
-
110
- // 不同 Tab 可能需要不同的列配置
111
- const pendingColumns: TableColumn[] = [
112
- { label: '订单号', prop: 'orderNo' },
113
- { label: '金额', prop: 'amount' },
114
- { label: '操作', prop: 'action', width: 120 },
115
- ]
116
-
117
- const completedColumns: TableColumn[] = [
118
- { label: '订单号', prop: 'orderNo' },
119
- { label: '金额', prop: 'amount' },
120
- { label: '完成时间', prop: 'completedAt', width: 180 },
121
- ]
122
-
123
- const currentColumns = computed(() => {
124
- return activeTab.value === 'completed' ? completedColumns : pendingColumns
125
- })
126
-
127
- function handleTabChange() {
128
- pagination.currentPage = 1
129
- fetchList()
130
- }
131
-
132
- function handleSearch() {
133
- pagination.currentPage = 1
134
- fetchList()
135
- }
136
-
137
- async function fetchList() {
138
- loading.value = true
139
- try {
140
- const res = await api.getOrders({
141
- status: activeTab.value,
142
- ...query,
143
- page: pagination.currentPage,
144
- size: pagination.pageSize,
145
- })
146
- tableData.value = res.data.list
147
- pagination.total = res.data.total
148
- } finally {
149
- loading.value = false
150
- }
151
- }
152
-
153
- watch(activeTab, handleTabChange, { immediate: true })
154
- </script>
155
- ```
156
-
157
- ## 关键要点
158
-
159
- 1. `ITabs` 通常放在 `#search` 插槽位置
160
- 2. 每个 Tab 的内容建议抽离为独立子组件
161
- 3. Tab 切换时需要重置分页 `currentPage = 1`
162
- 4. `TabItem.count` 可以在标签上显示角标数字
163
- 5. 支持权限过滤:`authList` + `authCode`
164
- 6. 如果 Tabs 和搜索区要共存,搜索区放在 default 插槽内
@@ -1,169 +0,0 @@
1
- # Pattern: 树形管理
2
-
3
- > 左侧树形结构 + 右侧内容区
4
-
5
- ## 完整骨架
6
-
7
- ```vue
8
- <template>
9
- <IPageLayout title="部门管理">
10
- <div class="tree-manage">
11
- <!-- 左侧树形 -->
12
- <div class="tree-manage__aside">
13
- <ICard>
14
- <template #header>
15
- <div class="flex items-center justify-between">
16
- <span class="text-black-70 font-medium">组织架构</span>
17
- <IButton text type="primary" icon="icon-add" @click="handleAddDept">新增</IButton>
18
- </div>
19
- </template>
20
-
21
- <IInput
22
- v-model="treeKeyword"
23
- placeholder="搜索部门"
24
- clearable
25
- class="mb-12"
26
- />
27
-
28
- <ITree
29
- ref="treeRef"
30
- :data="treeData"
31
- :props="{ children: 'children', label: 'name' }"
32
- row-key="id"
33
- selectable
34
- default-expand-all
35
- height="calc(100vh - 320px)"
36
- @node-click="handleNodeClick"
37
- />
38
- </ICard>
39
- </div>
40
-
41
- <!-- 右侧内容区 -->
42
- <div class="tree-manage__content">
43
- <ICard title="部门详情" v-if="currentDept">
44
- <template #header>
45
- <div class="flex items-center justify-between">
46
- <span class="text-black-70 font-medium">{{ currentDept.name }}</span>
47
- <ISpace :size="8">
48
- <IButton text type="primary" @click="handleEditDept">编辑</IButton>
49
- <IButton text type="danger" @click="handleDeleteDept">删除</IButton>
50
- </ISpace>
51
- </div>
52
- </template>
53
-
54
- <!-- 部门信息 / 人员列表等 -->
55
- <ITable
56
- v-loading="loading"
57
- :data="memberList"
58
- :columns="memberColumns"
59
- >
60
- <template #action="{ row }">
61
- <IButton text type="primary" @click="handleEditMember(row)">编辑</IButton>
62
- </template>
63
- </ITable>
64
- </ICard>
65
-
66
- <IEmpty v-else desc="请从左侧选择部门" />
67
- </div>
68
- </div>
69
- </IPageLayout>
70
- </template>
71
-
72
- <script setup lang="ts">
73
- import { ref, watch } from 'vue'
74
- import {
75
- IPageLayout, ICard, ITree, ITable,
76
- IInput, IButton, ISpace, IEmpty, IConfirm, IMessage,
77
- } from '@icc-grow/web-components'
78
- import type { TableColumn, TreeExpose, TreeRawData } from '@icc-grow/web-components'
79
-
80
- // ---- 类型定义 ----
81
- interface DeptItem {
82
- id: number
83
- name: string
84
- children?: DeptItem[]
85
- }
86
-
87
- interface MemberItem {
88
- id: number
89
- name: string
90
- position: string
91
- }
92
-
93
- // ---- 树形数据 ----
94
- const treeRef = ref<TreeExpose>()
95
- const treeData = ref<DeptItem[]>([])
96
- const treeKeyword = ref('')
97
- const currentDept = ref<DeptItem>()
98
-
99
- // ---- 人员列表 ----
100
- const loading = ref(false)
101
- const memberList = ref<MemberItem[]>([])
102
-
103
- const memberColumns: TableColumn<MemberItem>[] = [
104
- { label: '姓名', prop: 'name', minWidth: 120 },
105
- { label: '职位', prop: 'position', minWidth: 120 },
106
- { label: '操作', prop: 'action', width: 100, fixed: 'right' },
107
- ]
108
-
109
- // ---- 方法 ----
110
- function handleNodeClick(data: TreeRawData) {
111
- currentDept.value = data as unknown as DeptItem
112
- fetchMembers(data.id as number)
113
- }
114
-
115
- async function fetchMembers(deptId: number) {
116
- loading.value = true
117
- try {
118
- const res = await api.getDeptMembers(deptId)
119
- memberList.value = res.data
120
- } finally {
121
- loading.value = false
122
- }
123
- }
124
-
125
- function handleAddDept() { /* 打开新增部门弹窗 */ }
126
- function handleEditDept() { /* 打开编辑部门弹窗 */ }
127
- function handleEditMember(row: MemberItem) { /* 打开编辑成员弹窗 */ }
128
-
129
- function handleDeleteDept() {
130
- if (!currentDept.value) return
131
- IConfirm({
132
- type: 'warning',
133
- title: '确认删除',
134
- content: `确定删除部门「${currentDept.value.name}」吗?`,
135
- }).then(async () => {
136
- await api.deleteDept(currentDept.value!.id)
137
- IMessage.success('删除成功')
138
- currentDept.value = undefined
139
- // 重新加载树
140
- })
141
- }
142
- </script>
143
-
144
- <style scoped>
145
- .tree-manage {
146
- display: flex;
147
- gap: 16px;
148
- height: 100%;
149
- }
150
-
151
- .tree-manage__aside {
152
- width: 280px;
153
- flex-shrink: 0;
154
- }
155
-
156
- .tree-manage__content {
157
- flex: 1;
158
- min-width: 0;
159
- }
160
- </style>
161
- ```
162
-
163
- ## 关键要点
164
-
165
- 1. 左右布局用 `flex` + 固定左侧宽度
166
- 2. 左侧搜索用 `IInput` + 树形数据过滤
167
- 3. 右侧未选中时显示 `IEmpty` 占位
168
- 4. 树节点点击后加载右侧数据
169
- 5. 如果右侧内容是列表,直接复用 `ITable`;如果是表单详情,用 `IForm`
@@ -1,29 +0,0 @@
1
- # IAvatar / IAvatarGroup
2
-
3
- ## IAvatar
4
-
5
- 图片失败时以 `name` 首字符兜底。
6
-
7
- **Props**
8
-
9
- | Prop | Type | Default | Description |
10
- |------|------|---------|-------------|
11
- | src | `string` | — | 头像图片 URL |
12
- | name | `string` | — | 用户姓名(兜底 + alt + 哈希背景色) |
13
- | size | `number \| string` | `26` | 尺寸(px) |
14
- | shape | `'circle' \| 'square'` | `'circle'` | 形状 |
15
-
16
- ---
17
-
18
- ## IAvatarGroup
19
-
20
- 重叠排列,超出显示 +N。
21
-
22
- **Props**
23
-
24
- | Prop | Type | Default | Description |
25
- |------|------|---------|-------------|
26
- | size | `number \| string` | — | 统一尺寸 |
27
- | limit | `number \| string` | — | 最大展示数 |
28
-
29
- **Slots**: `default`(放置多个 `IAvatar`)
@@ -1,19 +0,0 @@
1
- # IBadge
2
-
3
- 角标,右上角覆盖在子元素上。
4
-
5
- **Props**
6
-
7
- | Prop | Type | Default | Description |
8
- |------|------|---------|-------------|
9
- | count | `number` | — | 显示数字 |
10
- | max | `number` | — | 最大值,超出显示 `max+` |
11
- | showZero | `boolean` | `false` | count=0 时是否显示 |
12
- | dot | `boolean` | `false` | 红点模式 |
13
-
14
- **Slots**: `default`
15
-
16
- ```vue
17
- <IBadge :count="5"><IIcon name="icon-bell" /></IBadge>
18
- <IBadge dot><IIcon name="icon-notification" /></IBadge>
19
- ```