12. 通用组件速查
一句话定位
src/components/ 下 22 个组件目录,覆盖字典、选择器、上传、图表、编辑器和表格辅助件;本篇逐个给出作用、props/事件与调用示例。
代码位置:Web/src/components/
总览表
| 目录 | 组件 | 一句话作用 |
|---|---|---|
sysDict/sysDict.vue | GSysDict(全局注册) | 按字典/常量编码渲染 tag / select / radio / checkbox |
selector/* | userSelectorDialog 等 | 机构/用户/角色/职位选择弹窗 + 高级下拉 |
uploadImage/index.vue | UploadImage | 单图上传,输出 base64 |
scEcharts/index.vue | scEcharts | ECharts 封装,跟随主题 + 自适应 |
iconSelector/index.vue | IconSelector | 图标选择器(ali/ele/awe/remix 四tab) |
importButton/index.vue | ImportButton | 一行按钮完成「选文件 + FormData 上传」 |
file/OnlineFilePreview.vue | OnlineFilePreview | 在线预览 Office/PDF/图片 |
table/* | Table / ModifyRecord / DictLabel … | el-table 封装与表格单元格辅助件 |
auth/* | Auth / Auths / AuthAll | 权限包裹组件 |
avatar / cropper / cameraDialog | - | 头像上传裁剪 / 拍照 |
editor / mdEditor / jsonEditor | - | 富文本 / Markdown / JSON 编辑器 |
dragVerify/* | - | 拖拽滑块验证 |
badgeTabs / noticeBar / numberRange / highLightKeyword / transfer / svgIcon | - | 杂项展示/输入件 |
1. GSysDict(字典组件,全局注册)
main.ts 中 app.component('GSysDict', sysDict),任意页面可直接用 <g-sys-dict>。
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
modelValue(必填) | string | number | boolean | array | null | null | 绑定值 |
code | string | - | 字典编码(code 与 data 不能同时为空) |
data | DictItem[] | [] | 直接传数据源,优先级高于 code |
isConst | boolean | false | true 从常量列表取,否则从字典列表取 |
renderAs | 'tag' | 'select' | 'radio' | 'checkbox' | 'checkbox-button' | 'radio-button' | 'tag' | 渲染方式 |
multiple | boolean | false | 多选(select/checkbox 生效) |
multipleModel | 'array' | 'comma' | 'array' | 多选值形态:['1','2'] 或 '1,2' |
propLabel / propValue | string | 'label' / 'value' | 显示/取值字段 |
onItemFilter | (dict) => boolean | - | 过滤选项 |
onItemFormatter | (dict) => string | - | 自定义显示文案 |
mutexConfigs | {value, excludes}[] | [] | 互斥配置 |
radioCancelable | boolean | true | 单选可再次点击取消 |
emitDataLoadedOnVisibleChange | boolean | false | 每次展开下拉重新抛 data-loaded |
事件:update:modelValue、change(value, dictItems, dictData)、data-loaded(dictData)。
<!-- 查询区:下拉 -->
<g-sys-dict v-model="state.queryParams.status" code="sys_status" render-as="select" clearable placeholder="状态" />
<!-- 表格/详情:标签展示 -->
<g-sys-dict v-model="row.status" code="sys_status" />
<!-- 表单:单选按钮组 -->
<g-sys-dict v-model="state.ruleForm.status" code="sys_status" render-as="radio" />
<!-- 多选,值为逗号串 -->
<g-sys-dict v-model="state.ruleForm.tags" code="sys_tag" render-as="select" multiple multiple-model="comma" />
<!-- 带数量统计的选项(data-loaded + formatter) -->
<g-sys-dict
v-model="state.productionType"
code="ProcedureTypeEnum"
render-as="select"
:emit-data-loaded-on-visible-change="true"
:on-item-formatter="(d) => `${d.label}(${countMap[d.value] ?? 0})`"
@data-loaded="handleDictDataLoaded"
/>数据来源:useUserInfo().dictList(/api/sysDictType/allDictList)与 useUserInfo().constList,在 initBackEndControlRoutes() 时一次性加载。
2. selector 系列
2.1 userSelectorDialog / orgSelectorDialog / roleSelectorDialog / posSelectorDialog
四者接口一致:
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
visible | boolean | false | 用 v-model:visible 控制 |
data | array | [] | 已选中的列表(回显) |
singleChoice | boolean | false | 是否单选(用户选择器支持) |
事件:update:visible、change(选中的列表)。
真实用法(views/system/notice/index.vue):
<template>
<NoticeUser ref="noticeUserRef" v-model:visible="state.noticeUserVisible" :data="state.noticeUsers" @change="closeGrantUser" />
</template>
<script lang="ts" setup>
import NoticeUser from '/@/components/selector/userSelectorDialog.vue';
const noticeUserRef = ref<InstanceType<typeof NoticeUser>>();
const closeGrantUser = (users: any) => { state.noticeUsers = users; state.noticeUserVisible = false; };
</script>| 文件 | 标题 | 数据源 |
|---|---|---|
userSelectorDialog.vue | 选择账号 | 左侧机构树 + 用户分页列表 |
orgSelectorDialog.vue | 选择部门 | 部门树 |
roleSelectorDialog.vue | 选择角色 | 角色分页列表 |
posSelectorDialog.vue | 选择职位 | 职位分页列表 |
2.2 pulldownSelecter(高级下拉)
表格化下拉,支持远程搜索、分页、单选/多选、回显。
| Prop | 默认 | 说明 |
|---|---|---|
modelValue | - | 单选为 id,多选为数组 |
fetchOptions(必填) | - | 形如 (params) => getAPI(SysUserApi).apiSysUserPagePost(params) |
valueProp / labelProp | 'id' / 'name' | 取值/展示字段 |
labelFormat | - | 自定义显示文本,如 (item) => \${item.realName}(${item.account})`` |
keywordProp | 'keyword' | 输入关键字落到哪个查询字段 |
queryParams | {} | 附加查询条件 |
pagination | true | 是否分页 |
autoLoad | true | 展开即查询 |
multiple / disabled / clearable | false | 常规开关 |
defaultOptions | [] | 回显用对象数组 |
dropdownWidth / dropdownHeight | '100%' / '550px' | 下拉尺寸 |
事件:update:modelValue、change(value, row)。
<pulldown-selecter
class="w100"
v-model="state.ruleForm.userId"
:fetch-options="handleSysUserTable"
:queryParams="{ page: 1, pageSize: 10 }"
:pagination="true"
:default-options="state.defaultUsers"
label-prop="realName"
value-prop="id"
placeholder="请选择用户"
:auto-load="true"
clearable
@change="onUserChange"
/>
<script lang="ts" setup>
import pulldownSelecter from '/@/components/selector/pulldownSelecter.vue';
const handleSysUserTable = (params: any) => getAPI(SysUserApi).apiSysUserPagePost(params);
</script>3. uploadImage(单图上传 → base64)
| Prop | 默认 | 说明 |
|---|---|---|
modelValue(defineModel) | - | 双向绑定,值为 base64 字符串(data:image/png;base64,...) |
defaultImage | null | 缺省展示图,设置了就不显示预览大图 |
disabled | false | 禁用 |
accept | '.jpg,.png,.jpeg,.bmp' | 可选类型 |
icon | 'ele-Picture' | 占位图标 |
<UploadImage v-model="state.ruleForm.avatar" />
<UploadImage v-model="state.ruleForm.logo" accept=".png,.jpg" icon="ele-Picture" :disabled="!isAdmin()" />⚠️ 组件内部用 FileReader 转 base64,不调后端接口。若要落库,请把 base64 作为字段提交,或用 SysFileApi.apiSysFileUploadFileFromBase64Post。当前业务页暂无引用,按需引入。
4. scEcharts(图表)
| Prop | 默认 | 说明 |
|---|---|---|
height / width | '100%' | 容器尺寸 |
option | {} | ECharts option,深度监听自动 setOption |
nodata | false | 无数据标记 |
事件:clickData(点击图形时抛出 evt.value)。
<scEcharts ref="chartRef" height="300px" :option="state.chartOption" @click-data="onChartClick" />
<script lang="ts" setup>
const scEcharts = defineAsyncComponent(() => import('/@/components/scEcharts/index.vue'));
</script>内部已做:echarts.registerTheme('T', T)、MutationObserver 跟随 data-theme、ResizeObserver 自适应、beforeUnmount 里 dispose()。详见「图表开发」篇。
5. table 辅助组件
5.1 ModifyRecord(修改记录,列表页标配)
<template #row_record="{ row }"><ModifyRecord :data="row" /></template>
<script lang="ts" setup>
import ModifyRecord from '/@/components/table/modifyRecord.vue';
</script>props.data:任意行对象。hover 弹出 el-descriptions,自动判断并显示 createUserName / createTime / updateUserName / updateTime / createOrgName / tenantId / remark(字段不存在自动隐藏)。
5.2 Table(table/index.vue,el-table 封装)
| Prop | 说明 |
|---|---|
getData(必填) | 取数函数 |
columns | 列配置,附加 isCheck(默认勾选展示)、hideCheck(隐藏勾选/拖拽) |
config | isBorder / isSerialNo / showSelection / isSelection / pageSize / hideExport / exportFileName |
param | 筛选参数 |
defaultSort | { prop, order } |
exportChangeData | 导出时的数据转换函数 |
printName | 打印标题 |
事件:pageChange、selectionChange、sortHeader(列顺序拖拽)。
新列表页统一用
vxe-grid,此组件用于历史页面和简单内嵌表格。
5.3 其它
| 组件 | props / 事件 | 用途 |
|---|---|---|
table/dictLabel.vue | code、value 等 | 单元格内按字典渲染 label |
table/formatter.vue | 行/列数据 | 单元格格式化 |
table/search.vue | columns: TableSearchType[]、modelValue;@search / @reset | 配置化查询表单 |
table/importData.vue | props: ['import','download'],@refresh;defineExpose({ openDialog, closeDialog }) | 导入数据弹窗(含模板下载,走 downloadStreamFile) |
table/tableEditor.vue | value: array、columns;@add / @delete / @update:value | 可编辑子表 |
table/viewRecord.vue | defineExpose(...) | 查看记录 |
6. iconSelector(图标选择器)
| Prop | 默认 |
|---|---|
modelValue | - |
prepend | 'ele-Pointer'(前置图标) |
placeholder | '请输入内容搜索图标或者选择图标' |
size | 'default' |
title | '请选择图标' |
disabled / clearable | false / true |
emptyDescription | '无相关图标' |
事件:update:modelValue、get、clear。内置 ali / ele / awe / remix 四个 tab。
真实用法(views/system/menu/component/editMenu.vue):
<IconSelector v-model="state.ruleForm.icon" :size="getGlobalComponentSize" placeholder="菜单图标" type="all" />
<script lang="ts" setup>
import IconSelector from '/@/components/iconSelector/index.vue';
import other from '/@/utils/other';
const getGlobalComponentSize = computed(() => other.globalComponentSize());
</script>7. importButton(一键导入)
| Prop | 说明 |
|---|---|
url | 上传接口地址 |
param | 随 FormData 一起提交的附加字段(深度监听) |
accept | <input type="file"> 的 accept |
事件:success、error。内部用 utils/request.ts 的 request2 以 multipart/form-data 提交。
<ImportButton url="/api/sysUser/import" :param="{ orgId: state.orgId }" accept=".xlsx,.xls" @success="handleQuery()">
导入用户
</ImportButton>
<script lang="ts" setup>
import ImportButton from '/@/components/importButton/index.vue';
</script>8. OnlineFilePreview(在线预览)
| Prop | 默认 | 说明 |
|---|---|---|
url | '' | 文件地址 |
file | - | File 对象(与 url 二选一) |
loading | false | 加载态 |
imagePreview | false | 图片走 el-image 大图预览 |
底层是 @file-viewer/vue3 的 <file-viewer>,渲染器由 vite.config.ts 的 fileViewerRenderers() 插件注入。
真实用法(views/system/file/index.vue、views/document/components/DocumentPreviewDialog.vue):
<OnlineFilePreview v-if="previewUrl || previewFile" :url="previewUrl" :file="previewFile" :loading="previewLoading" class="preview-body" />
<script lang="ts" setup>
import OnlineFilePreview from '/@/components/file/OnlineFilePreview.vue';
</script>⚠️ 组件根节点固定 data-theme="light",避免暗色模式下 markdown 白底浅字不可读。
9. 其余组件速查
| 组件 | Props / 事件 | 说明 |
|---|---|---|
auth/auth.vue | value: string | <Auth value="sysDemo/add"><el-button/></Auth> 包一层做权限 |
auth/auths.vue / auth/authAll.vue | value: string[] | 满足其一 / 全部满足 |
avatar/index.vue | defineModel() + disabled 等 | 头像上传(内部用 el-upload) |
cropper/index.vue | @uploadCropperImg,defineExpose(...) | 图片裁剪后上传 |
cameraDialog/cameraDialog.vue | @confirm,defineExpose({ openDialog }) | 摄像头拍照 |
dragVerify/dragVerify.vue | isPassing、width、height、text、successText … | 滑块验证;另有 dragVerifyImg / ImgChip / ImgRotate |
editor/index.vue | v-model:getHtml / v-model:getText、disabled、height … | wangeditor 富文本 |
mdEditor/index.vue | 同 editor | md-editor-v3 |
jsonEditor/index.vue | @update:jsonObj | json-editor-vue |
badgeTabs/index.vue | modelValue、items: BadgeTabsItem[];@change | 带角标的标签页 |
noticeBar/index.vue | mode('closeable'/'link')、text、color、background、height、delay、speed、scrollable、leftIcon、rightIcon;@close / @link | 滚动通知栏 |
numberRange/index.vue | modelValue: [min,max]、disabled、min、max、precision …;@update:modelValue / @update:minValue / @update:maxValue / @change | 数值区间输入 |
highLightKeyword/index.vue | text、keyword、color、background | 关键字高亮 |
transfer/index.vue | leftData / rightData、props;@left / @right / @allLeft / @allRight / @update:leftData / @update:rightData | 自定义穿梭框 |
svgIcon/index.vue | name、size、color | SVG 图标(已全局注册为 SvgIcon) |
⚠️ 注意事项 / 常见坑
- GSysDict 的
code与data不能同时为空,否则控制台报[g-sys-dict] code和data不能同时为空且渲染空。 - 字典在登录后一次性加载:新增字典类型后要让前端重新拉
setDictList()(重新登录或刷新路由初始化)。 - selector 弹窗用
v-model:visible,不是v-model。 uploadImage产出 base64,不是 url;大图慎用(base64 会让请求体膨胀约 1/3)。scEcharts容器宽高为 0 时不会 init(源码里明确判断),需保证父容器有实际高度,否则图表不出来。ModifyRecord依赖行对象字段名,后端 DTO 若改名(如createUserName)需同步。- 组件按需
import,只有GSysDict与SvgIcon是全局注册的。 importButton、uploadImage目前业务页暂无引用,属「已封装待用」组件,用前先跑一遍自测。
小结
- 字典渲染一律用
GSysDict,别再手写v-for拼 el-option。 - 选人/选部门/选角色/选职位用
selector/*Dialog,统一v-model:visible+@change。 - 图表用
scEcharts(自带主题与 resize),表格辅助用ModifyRecord。 - 上传分发:
uploadImage(base64 头像)→ImportButton(Excel 导入)→ Tus(uploadDialog.vue大文件),见「上传下载」篇。