@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.
- package/README.md +15 -0
- package/dist/es/components/i-avatar-group/src/avatar-group.mjs +1 -1
- package/dist/es/components/i-form-item/src/form-item.mjs +1 -1
- package/dist/es/components/i-form-item/src/form-item.vue_vue_type_script_setup_true_lang.mjs +2 -1
- package/dist/es/components/i-image/src/image.vue_vue_type_script_setup_true_lang.mjs +20 -20
- package/dist/es/components/i-tooltip/src/tooltip-content.mjs +1 -1
- package/dist/es/style.css +1 -1
- package/dist/es/utils/use-intersection-observer.mjs +31 -0
- package/dist/lib/components/i-avatar-group/src/avatar-group.cjs +1 -1
- package/dist/lib/components/i-form-item/src/form-item.cjs +1 -1
- package/dist/lib/components/i-form-item/src/form-item.vue_vue_type_script_setup_true_lang.cjs +1 -1
- package/dist/lib/components/i-image/src/image.vue_vue_type_script_setup_true_lang.cjs +1 -1
- package/dist/lib/components/i-tooltip/src/tooltip-content.cjs +1 -1
- package/dist/lib/style.css +1 -1
- package/dist/lib/utils/use-intersection-observer.cjs +1 -0
- package/dist/types/components/i-form/index.d.ts +3 -3
- package/dist/types/components/i-form/index.d.ts.map +1 -1
- package/dist/types/components/i-form/src/form.vue.d.ts +1 -1
- package/dist/types/components/i-form/src/form.vue.d.ts.map +1 -1
- package/dist/types/components/i-form/src/types.d.ts +16 -4
- package/dist/types/components/i-form/src/types.d.ts.map +1 -1
- package/dist/types/components/i-form-item/index.d.ts +3 -3
- package/dist/types/components/i-form-item/index.d.ts.map +1 -1
- package/dist/types/components/i-form-item/src/form-item.vue.d.ts +2 -2
- package/dist/types/components/i-form-item/src/form-item.vue.d.ts.map +1 -1
- package/dist/types/components/i-form-item/src/types.d.ts +2 -2
- package/dist/types/components/i-form-item/src/types.d.ts.map +1 -1
- package/dist/types/components/i-tooltip/index.d.ts +9 -2
- package/dist/types/components/i-tooltip/index.d.ts.map +1 -1
- package/dist/types/components/i-tooltip/src/tooltip-content.vue.d.ts +15 -2
- package/dist/types/components/i-tooltip/src/tooltip-content.vue.d.ts.map +1 -1
- package/dist/types/components/i-tooltip/src/tooltip.vue.d.ts +18 -4
- package/dist/types/components/i-tooltip/src/tooltip.vue.d.ts.map +1 -1
- package/dist/types/utils/use-intersection-observer.d.ts +19 -0
- package/dist/types/utils/use-intersection-observer.d.ts.map +1 -0
- package/package.json +2 -3
- package/skills/icc-web-components/SKILL.md +0 -191
- package/skills/icc-web-components/patterns/crud-list.md +0 -195
- package/skills/icc-web-components/patterns/dialog-form.md +0 -180
- package/skills/icc-web-components/patterns/form-page.md +0 -139
- package/skills/icc-web-components/patterns/tabs-page.md +0 -164
- package/skills/icc-web-components/patterns/tree-manage.md +0 -169
- package/skills/icc-web-components/references/i-avatar.md +0 -29
- package/skills/icc-web-components/references/i-badge.md +0 -19
- package/skills/icc-web-components/references/i-card.md +0 -12
- package/skills/icc-web-components/references/i-color-picker.md +0 -17
- package/skills/icc-web-components/references/i-crud-page.md +0 -204
- package/skills/icc-web-components/references/i-dialog.md +0 -36
- package/skills/icc-web-components/references/i-draggable.md +0 -46
- package/skills/icc-web-components/references/i-dropdown.md +0 -21
- package/skills/icc-web-components/references/i-empty.md +0 -15
- package/skills/icc-web-components/references/i-feedback.md +0 -50
- package/skills/icc-web-components/references/i-form.md +0 -223
- package/skills/icc-web-components/references/i-icon.md +0 -21
- package/skills/icc-web-components/references/i-image.md +0 -34
- package/skills/icc-web-components/references/i-loading.md +0 -25
- package/skills/icc-web-components/references/i-menu.md +0 -22
- package/skills/icc-web-components/references/i-popover.md +0 -51
- package/skills/icc-web-components/references/i-progress.md +0 -14
- package/skills/icc-web-components/references/i-scrollbar.md +0 -20
- package/skills/icc-web-components/references/i-space.md +0 -15
- package/skills/icc-web-components/references/i-steps.md +0 -12
- package/skills/icc-web-components/references/i-tabs.md +0 -16
- package/skills/icc-web-components/references/i-tag.md +0 -35
- package/skills/icc-web-components/references/i-time-picker.md +0 -19
- package/skills/icc-web-components/references/i-transition.md +0 -17
- package/skills/icc-web-components/references/i-tree-select.md +0 -26
- package/skills/icc-web-components/references/i-tree.md +0 -34
- package/skills/icc-web-components/references/i-upload.md +0 -25
- package/skills/icc-web-components/references/i-virtual-list.md +0 -24
- package/skills/icc-web-components/references/i-virtual-table.md +0 -20
- package/skills/icc-web-components/rules/composition.md +0 -28
- package/skills/icc-web-components/rules/forms.md +0 -28
- 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
|
-
```
|