diff --git a/apps/web-antd/src/api/bpm/form/index.ts b/apps/web-antd/src/api/bpm/form/index.ts index 27191230a..581178c5d 100644 --- a/apps/web-antd/src/api/bpm/form/index.ts +++ b/apps/web-antd/src/api/bpm/form/index.ts @@ -3,6 +3,7 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace BpmFormApi { + // TODO @siye:注释加一个。。嘿嘿 export interface FormVO { id?: number | undefined; name: string; @@ -11,7 +12,6 @@ export namespace BpmFormApi { status: number; remark: string; createTime: string; - } } diff --git a/apps/web-antd/src/router/routes/modules/bpm.ts b/apps/web-antd/src/router/routes/modules/bpm.ts index cf15a4543..2d2cb5b8a 100644 --- a/apps/web-antd/src/router/routes/modules/bpm.ts +++ b/apps/web-antd/src/router/routes/modules/bpm.ts @@ -27,7 +27,6 @@ const routes: RouteRecordRaw[] = [ }, ], }, - { path: 'process-instance/detail', component: () => import('#/views/bpm/processInstance/detail/index.vue'), @@ -47,8 +46,6 @@ const routes: RouteRecordRaw[] = [ }; }, }, - - /** 编辑流程表单 */ { path: '/bpm/manager/form/edit', name: 'BpmFormEditor', diff --git a/apps/web-antd/src/views/bpm/form/data.ts b/apps/web-antd/src/views/bpm/form/data.ts index 536208854..a416dd269 100644 --- a/apps/web-antd/src/views/bpm/form/data.ts +++ b/apps/web-antd/src/views/bpm/form/data.ts @@ -91,20 +91,17 @@ export function useGridColumns( props: { type: DICT_TYPE.COMMON_STATUS }, }, }, - { field: 'remark', title: '备注', minWidth: 200, }, - { field: 'createTime', title: '创建时间', minWidth: 180, formatter: 'formatDateTime', }, - { field: 'operation', title: '操作', diff --git a/apps/web-antd/src/views/bpm/form/editor.vue b/apps/web-antd/src/views/bpm/form/editor.vue index c4fba895b..1c374357c 100644 --- a/apps/web-antd/src/views/bpm/form/editor.vue +++ b/apps/web-antd/src/views/bpm/form/editor.vue @@ -1,4 +1,5 @@ + + + + diff --git a/apps/web-ele/src/components/cropper/cropper-modal.vue b/apps/web-ele/src/components/cropper/cropper-modal.vue new file mode 100644 index 000000000..7e7008370 --- /dev/null +++ b/apps/web-ele/src/components/cropper/cropper-modal.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/apps/web-ele/src/components/cropper/cropper.vue b/apps/web-ele/src/components/cropper/cropper.vue new file mode 100644 index 000000000..c2d62755e --- /dev/null +++ b/apps/web-ele/src/components/cropper/cropper.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/apps/web-ele/src/components/cropper/index.ts b/apps/web-ele/src/components/cropper/index.ts new file mode 100644 index 000000000..43fd89ff3 --- /dev/null +++ b/apps/web-ele/src/components/cropper/index.ts @@ -0,0 +1,3 @@ +export { default as CropperAvatar } from './cropper-avatar.vue'; +export { default as CropperImage } from './cropper.vue'; +export type { CropperType } from './typing'; diff --git a/apps/web-ele/src/components/cropper/typing.ts b/apps/web-ele/src/components/cropper/typing.ts new file mode 100644 index 000000000..f9ba238fd --- /dev/null +++ b/apps/web-ele/src/components/cropper/typing.ts @@ -0,0 +1,68 @@ +import type Cropper from 'cropperjs'; +import type { ButtonProps } from 'element-plus'; + +import type { CSSProperties } from 'vue'; + +export interface apiFunParams { + file: Blob; + filename: string; + name: string; +} + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export interface CropperProps { + src?: string; + alt?: string; + circled?: boolean; + realTimePreview?: boolean; + height?: number | string; + crossorigin?: '' | 'anonymous' | 'use-credentials' | undefined; + imageStyle?: CSSProperties; + options?: Cropper.Options; +} + +export interface CropperAvatarProps { + width?: number | string; + value?: string; + showBtn?: boolean; + btnProps?: ButtonProps; + btnText?: string; + uploadApi?: (params: apiFunParams) => Promise; + size?: number; +} + +export interface CropperModalProps { + circled?: boolean; + uploadApi?: (params: apiFunParams) => Promise; + src?: string; + size?: number; +} + +export const defaultOptions: Cropper.Options = { + aspectRatio: 1, + zoomable: true, + zoomOnTouch: true, + zoomOnWheel: true, + cropBoxMovable: true, + cropBoxResizable: true, + toggleDragModeOnDblclick: true, + autoCrop: true, + background: true, + highlight: true, + center: true, + responsive: true, + restore: true, + checkCrossOrigin: true, + checkOrientation: true, + scalable: true, + modal: true, + guides: true, + movable: true, + rotatable: true, +}; + +export type { Cropper as CropperType }; diff --git a/apps/web-ele/src/components/form-create/components/dict-select.vue b/apps/web-ele/src/components/form-create/components/dict-select.vue new file mode 100644 index 000000000..b4e65d06e --- /dev/null +++ b/apps/web-ele/src/components/form-create/components/dict-select.vue @@ -0,0 +1,78 @@ + + + + diff --git a/apps/web-ele/src/components/form-create/components/use-api-select.tsx b/apps/web-ele/src/components/form-create/components/use-api-select.tsx new file mode 100644 index 000000000..6eee727da --- /dev/null +++ b/apps/web-ele/src/components/form-create/components/use-api-select.tsx @@ -0,0 +1,288 @@ +import type { ApiSelectProps } from '#/components/form-create/typing'; + +import { defineComponent, onMounted, ref, useAttrs } from 'vue'; + +import { isEmpty } from '@vben/utils'; + +import { + ElCheckbox, + ElCheckboxGroup, + ElOption, + ElRadio, + ElRadioGroup, + ElSelect, +} from 'element-plus'; + +import { requestClient } from '#/api/request'; + +export const useApiSelect = (option: ApiSelectProps) => { + return defineComponent({ + name: option.name, + props: { + // 选项标签 + labelField: { + type: String, + default: () => option.labelField ?? 'label', + }, + // 选项的值 + valueField: { + type: String, + default: () => option.valueField ?? 'value', + }, + // api 接口 + url: { + type: String, + default: () => option.url ?? '', + }, + // 请求类型 + method: { + type: String, + default: 'GET', + }, + // 选项解析函数 + parseFunc: { + type: String, + default: '', + }, + // 请求参数 + data: { + type: String, + default: '', + }, + // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + selectType: { + type: String, + default: 'select', + }, + // 是否多选 + multiple: { + type: Boolean, + default: false, + }, + // 是否远程搜索 + remote: { + type: Boolean, + default: false, + }, + // 远程搜索时携带的参数 + remoteField: { + type: String, + default: 'label', + }, + }, + setup(props) { + const attrs = useAttrs(); + const options = ref([]); // 下拉数据 + const loading = ref(false); // 是否正在从远程获取数据 + const queryParam = ref(); // 当前输入的值 + const getOptions = async () => { + options.value = []; + // 接口选择器 + if (isEmpty(props.url)) { + return; + } + + switch (props.method) { + case 'GET': { + let url: string = props.url; + if (props.remote && queryParam.value !== undefined) { + url = url.includes('?') + ? `${url}&${props.remoteField}=${queryParam.value}` + : `${url}?${props.remoteField}=${queryParam.value}`; + } + parseOptions(await requestClient.get(url)); + break; + } + case 'POST': { + const data: any = JSON.parse(props.data); + if (props.remote) { + data[props.remoteField] = queryParam.value; + } + parseOptions(await requestClient.post(props.url, data)); + break; + } + } + }; + + function parseOptions(data: any) { + // 情况一:如果有自定义解析函数优先使用自定义解析 + if (!isEmpty(props.parseFunc)) { + options.value = parseFunc()?.(data); + return; + } + // 情况二:返回的直接是一个列表 + if (Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况二:返回的是分页数据,尝试读取 list + data = data.list; + if (!!data && Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况三:不是 yudao-vue-pro 标准返回 + console.warn( + `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`, + ); + } + + function parseOptions0(data: any[]) { + if (Array.isArray(data)) { + options.value = data.map((item: any) => ({ + label: parseExpression(item, props.labelField), + value: parseExpression(item, props.valueField), + })); + return; + } + console.warn(`接口[${props.url}] 返回结果不是一个数组`); + } + + function parseFunc() { + let parse: any = null; + if (props.parseFunc) { + // 解析字符串函数 + // eslint-disable-next-line no-new-func + parse = new Function(`return ${props.parseFunc}`)(); + } + return parse; + } + + function parseExpression(data: any, template: string) { + // 检测是否使用了表达式 + if (!template.includes('${')) { + return data[template]; + } + // 正则表达式匹配模板字符串中的 ${...} + const pattern = /\$\{([^}]*)\}/g; + // 使用replace函数配合正则表达式和回调函数来进行替换 + return template.replaceAll(pattern, (_, expr) => { + // expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值 + const result = data[expr.trim()]; // 去除前后空白,以防用户输入带空格的属性名 + if (!result) { + console.warn( + `接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`, + ); + } + return result; + }); + } + + const remoteMethod = async (query: any) => { + if (!query) { + return; + } + loading.value = true; + try { + queryParam.value = query; + await getOptions(); + } finally { + loading.value = false; + } + }; + + onMounted(async () => { + await getOptions(); + }); + + const buildSelect = () => { + if (props.multiple) { + // fix:多写此步是为了解决 multiple 属性问题 + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + ), + )} + + ); + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + ), + )} + + ); + }; + const buildCheckbox = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + const buildRadio = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + return () => ( + <> + {(() => { + switch (props.selectType) { + case 'checkbox': { + return buildCheckbox(); + } + case 'radio': { + return buildRadio(); + } + case 'select': { + return buildSelect(); + } + default: { + return buildSelect(); + } + } + })()} + + ); + }, + }); +}; diff --git a/apps/web-ele/src/components/form-create/components/use-images-upload.tsx b/apps/web-ele/src/components/form-create/components/use-images-upload.tsx new file mode 100644 index 000000000..4e821c6c6 --- /dev/null +++ b/apps/web-ele/src/components/form-create/components/use-images-upload.tsx @@ -0,0 +1,25 @@ +import { defineComponent } from 'vue'; + +import ImageUpload from '#/components/upload/image-upload.vue'; + +export const useImagesUpload = () => { + return defineComponent({ + name: 'ImagesUpload', + props: { + multiple: { + type: Boolean, + default: true, + }, + maxNumber: { + type: Number, + default: 5, + }, + }, + setup() { + // TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递 + return (props: { maxNumber?: number; multiple?: boolean }) => ( + + ); + }, + }); +}; diff --git a/apps/web-ele/src/components/form-create/helpers.ts b/apps/web-ele/src/components/form-create/helpers.ts new file mode 100644 index 000000000..c647711c4 --- /dev/null +++ b/apps/web-ele/src/components/form-create/helpers.ts @@ -0,0 +1,182 @@ +import type { Ref } from 'vue'; + +import type { Menu } from '#/components/form-create/typing'; + +import { nextTick, onMounted } from 'vue'; + +import { apiSelectRule } from '#/components/form-create/rules/data'; + +import { + useDictSelectRule, + useEditorRule, + useSelectRule, + useUploadFileRule, + useUploadImageRule, + useUploadImagesRule, +} from './rules'; + +export function makeRequiredRule() { + return { + type: 'Required', + field: 'formCreate$required', + title: '是否必填', + }; +} + +export const localeProps = ( + t: (msg: string) => any, + prefix: string, + rules: any[], +) => { + return rules.map((rule: { field: string; title: any }) => { + if (rule.field === 'formCreate$required') { + rule.title = t('props.required') || rule.title; + } else if (rule.field && rule.field !== '_optionType') { + rule.title = t(`components.${prefix}.${rule.field}`) || rule.title; + } + return rule; + }); +}; + +/** + * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) + * + * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule + * @param fields 解析后表单组件字段 + * @param parentTitle 如果是子表单,子表单的标题,默认为空 + */ +export const parseFormFields = ( + rule: Record, + fields: Array> = [], + parentTitle: string = '', +) => { + const { type, field, $required, title: tempTitle, children } = rule; + if (field && tempTitle) { + let title = tempTitle; + if (parentTitle) { + title = `${parentTitle}.${tempTitle}`; + } + let required = false; + if ($required) { + required = true; + } + fields.push({ + field, + title, + type, + required, + }); + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFields(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFormFields(rule, fields); + }); + } +}; + +/** + * 表单设计器增强 hook + * 新增 + * - 文件上传 + * - 单图上传 + * - 多图上传 + * - 字典选择器 + * - 用户选择器 + * - 部门选择器 + * - 富文本 + */ +export const useFormCreateDesigner = async (designer: Ref) => { + const editorRule = useEditorRule(); + const uploadFileRule = useUploadFileRule(); + const uploadImageRule = useUploadImageRule(); + const uploadImagesRule = useUploadImagesRule(); + + /** + * 构建表单组件 + */ + const buildFormComponents = () => { + // 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代 + designer.value?.removeMenuItem('upload'); + // 移除自带的富文本组件规则,使用 editorRule 替代 + designer.value?.removeMenuItem('fc-editor'); + const components = [ + editorRule, + uploadFileRule, + uploadImageRule, + uploadImagesRule, + ]; + components.forEach((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `main` 分类下 + designer.value?.appendMenuItem('main', { + icon: component.icon, + name: component.name, + label: component.label, + }); + }); + }; + + const userSelectRule = useSelectRule({ + name: 'UserSelect', + label: '用户选择器', + icon: 'icon-eye', + }); + const deptSelectRule = useSelectRule({ + name: 'DeptSelect', + label: '部门选择器', + icon: 'icon-tree', + }); + const dictSelectRule = useDictSelectRule(); + const apiSelectRule0 = useSelectRule({ + name: 'ApiSelect', + label: '接口选择器', + icon: 'icon-json', + props: [...apiSelectRule], + event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'], + }); + + /** + * 构建系统字段菜单 + */ + const buildSystemMenu = () => { + // 移除自带的下拉选择器组件,使用 currencySelectRule 替代 + // designer.value?.removeMenuItem('select') + // designer.value?.removeMenuItem('radio') + // designer.value?.removeMenuItem('checkbox') + const components = [ + userSelectRule, + deptSelectRule, + dictSelectRule, + apiSelectRule0, + ]; + const menu: Menu = { + name: 'system', + title: '系统字段', + list: components.map((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `system` 分类下 + return { + icon: component.icon, + name: component.name, + label: component.label, + }; + }), + }; + designer.value?.addMenu(menu); + }; + + onMounted(async () => { + await nextTick(); + buildFormComponents(); + buildSystemMenu(); + }); +}; diff --git a/apps/web-ele/src/components/form-create/index.ts b/apps/web-ele/src/components/form-create/index.ts new file mode 100644 index 000000000..b311e79e6 --- /dev/null +++ b/apps/web-ele/src/components/form-create/index.ts @@ -0,0 +1,3 @@ +export { useApiSelect } from './components/use-api-select'; + +export { useFormCreateDesigner } from './helpers'; diff --git a/apps/web-ele/src/components/form-create/rules/data.ts b/apps/web-ele/src/components/form-create/rules/data.ts new file mode 100644 index 000000000..2c6cee2ce --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/data.ts @@ -0,0 +1,182 @@ +/* eslint-disable no-template-curly-in-string */ +const selectRule = [ + { + type: 'select', + field: 'selectType', + title: '选择器类型', + value: 'select', + options: [ + { label: '下拉框', value: 'select' }, + { label: '单选框', value: 'radio' }, + { label: '多选框', value: 'checkbox' }, + ], + // 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性 + control: [ + { + value: 'select', + condition: '==', + method: 'hidden', + rule: [ + 'multiple', + 'clearable', + 'collapseTags', + 'multipleLimit', + 'allowCreate', + 'filterable', + 'noMatchText', + 'remote', + 'remoteMethod', + 'reserveKeyword', + 'defaultFirstOption', + 'automaticDropdown', + ], + }, + ], + }, + { + type: 'switch', + field: 'filterable', + title: '是否可搜索', + }, + { type: 'switch', field: 'multiple', title: '是否多选' }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + }, + { type: 'switch', field: 'clearable', title: '是否可以清空选项' }, + { + type: 'switch', + field: 'collapseTags', + title: '多选时是否将选中值按文字的形式展示', + }, + { + type: 'inputNumber', + field: 'multipleLimit', + title: '多选时用户最多可以选择的项目数,为 0 则不限制', + props: { min: 0 }, + }, + { + type: 'input', + field: 'autocomplete', + title: 'autocomplete 属性', + }, + { type: 'input', field: 'placeholder', title: '占位符' }, + { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' }, + { + type: 'input', + field: 'noMatchText', + title: '搜索条件无匹配时显示的文字', + }, + { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, + { + type: 'switch', + field: 'reserveKeyword', + title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词', + }, + { + type: 'switch', + field: 'defaultFirstOption', + title: '在输入框按下回车,选择第一个匹配项', + }, + { + type: 'switch', + field: 'popperAppendToBody', + title: '是否将弹出框插入至 body 元素', + value: true, + }, + { + type: 'switch', + field: 'automaticDropdown', + title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单', + }, +]; + +const apiSelectRule = [ + { + type: 'input', + field: 'url', + title: 'url 地址', + props: { + placeholder: '/system/user/simple-list', + }, + }, + { + type: 'select', + field: 'method', + title: '请求类型', + value: 'GET', + options: [ + { label: 'GET', value: 'GET' }, + { label: 'POST', value: 'POST' }, + ], + control: [ + { + value: 'GET', + condition: '!=', + method: 'hidden', + rule: [ + { + type: 'input', + field: 'data', + title: '请求参数 JSON 格式', + props: { + autosize: true, + type: 'textarea', + placeholder: '{"type": 1}', + }, + }, + ], + }, + ], + }, + { + type: 'input', + field: 'labelField', + title: 'label 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'nickname', + }, + }, + { + type: 'input', + field: 'valueField', + title: 'value 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'id', + }, + }, + { + type: 'input', + field: 'parseFunc', + title: '选项解析函数', + info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表 + (data: any)=>{ label: string; value: any }[]`, + props: { + autosize: true, + rows: { minRows: 2, maxRows: 6 }, + type: 'textarea', + placeholder: ` + function (data) { + console.log(data) + return data.list.map(item=> ({label: item.nickname,value: item.id})) + }`, + }, + }, + { + type: 'switch', + field: 'remote', + info: '是否可搜索', + title: '其中的选项是否从服务器远程加载', + }, + { + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name', + }, +]; + +export { apiSelectRule, selectRule }; diff --git a/apps/web-ele/src/components/form-create/rules/index.ts b/apps/web-ele/src/components/form-create/rules/index.ts new file mode 100644 index 000000000..db306da35 --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/index.ts @@ -0,0 +1,6 @@ +export { useDictSelectRule } from './use-dict-select'; +export { useEditorRule } from './use-editor-rule'; +export { useSelectRule } from './use-select-rule'; +export { useUploadFileRule } from './use-upload-file-rule'; +export { useUploadImageRule } from './use-upload-image-rule'; +export { useUploadImagesRule } from './use-upload-images-rule'; diff --git a/apps/web-ele/src/components/form-create/rules/use-dict-select.ts b/apps/web-ele/src/components/form-create/rules/use-dict-select.ts new file mode 100644 index 000000000..c9c438e8b --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-dict-select.ts @@ -0,0 +1,69 @@ +import { onMounted, ref } from 'vue'; + +import { buildUUID, cloneDeep } from '@vben/utils'; + +import * as DictDataApi from '#/api/system/dict/type'; +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; +import { selectRule } from '#/components/form-create/rules/data'; + +/** + * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule + */ +export const useDictSelectRule = () => { + const label = '字典选择器'; + const name = 'DictSelect'; + const rules = cloneDeep(selectRule); + const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据 + onMounted(async () => { + const data = await DictDataApi.getSimpleDictTypeList(); + if (!data || data.length === 0) { + return; + } + dictOptions.value = + data?.map((item: DictDataApi.SystemDictTypeApi.DictType) => ({ + label: item.name, + value: item.type, + })) ?? []; + }); + return { + icon: 'icon-descriptions', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'dictType', + title: '字典类型', + value: '', + options: dictOptions.value, + }, + { + type: 'select', + field: 'valueType', + title: '字典值类型', + value: 'str', + options: [ + { label: '数字', value: 'int' }, + { label: '字符串', value: 'str' }, + { label: '布尔值', value: 'bool' }, + ], + }, + ...rules, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/rules/use-editor-rule.ts b/apps/web-ele/src/components/form-create/rules/use-editor-rule.ts new file mode 100644 index 000000000..556baf0a1 --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-editor-rule.ts @@ -0,0 +1,36 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export const useEditorRule = () => { + const label = '富文本'; + const name = 'Tinymce'; + return { + icon: 'icon-editor', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'input', + field: 'height', + title: '高度', + }, + { type: 'switch', field: 'readonly', title: '是否只读' }, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/rules/use-select-rule.ts b/apps/web-ele/src/components/form-create/rules/use-select-rule.ts new file mode 100644 index 000000000..fd4213783 --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-select-rule.ts @@ -0,0 +1,45 @@ +import type { SelectRuleOption } from '#/components/form-create/typing'; + +import { buildUUID, cloneDeep } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; +import { selectRule } from '#/components/form-create/rules/data'; + +/** + * 通用选择器规则 hook + * + * @param option 规则配置 + */ +export const useSelectRule = (option: SelectRuleOption) => { + const label = option.label; + const name = option.name; + const rules = cloneDeep(selectRule); + return { + icon: option.icon, + label, + name, + event: option.event, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + if (!option.props) { + option.props = []; + } + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + ...option.props, + ...rules, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/rules/use-upload-file-rule.ts b/apps/web-ele/src/components/form-create/rules/use-upload-file-rule.ts new file mode 100644 index 000000000..55f5bea33 --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-upload-file-rule.ts @@ -0,0 +1,84 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export const useUploadFileRule = () => { + const label = '文件上传'; + const name = 'FileUpload'; + return { + icon: 'icon-upload', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'fileType', + title: '文件类型', + value: ['doc', 'xls', 'ppt', 'txt', 'pdf'], + options: [ + { label: 'doc', value: 'doc' }, + { label: 'xls', value: 'xls' }, + { label: 'ppt', value: 'ppt' }, + { label: 'txt', value: 'txt' }, + { label: 'pdf', value: 'pdf' }, + ], + props: { + multiple: true, + }, + }, + { + type: 'switch', + field: 'autoUpload', + title: '是否在选取文件后立即进行上传', + value: true, + }, + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'switch', + field: 'isShowTip', + title: '是否显示提示', + value: true, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + value: false, + }, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/rules/use-upload-image-rule.ts b/apps/web-ele/src/components/form-create/rules/use-upload-image-rule.ts new file mode 100644 index 000000000..70760b061 --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-upload-image-rule.ts @@ -0,0 +1,93 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export const useUploadImageRule = () => { + const label = '单图上传'; + const name = 'ImageUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + multiple: false, + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + { + type: 'switch', + field: 'disabled', + title: '是否显示删除按钮', + value: true, + }, + { + type: 'switch', + field: 'showBtnText', + title: '是否显示按钮文字', + value: true, + }, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/rules/use-upload-images-rule.ts b/apps/web-ele/src/components/form-create/rules/use-upload-images-rule.ts new file mode 100644 index 000000000..c18a7b49d --- /dev/null +++ b/apps/web-ele/src/components/form-create/rules/use-upload-images-rule.ts @@ -0,0 +1,89 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export const useUploadImagesRule = () => { + const label = '多图上传'; + const name = 'ImagesUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + multiple: true, + maxNumber: 5, + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + ]); + }, + }; +}; diff --git a/apps/web-ele/src/components/form-create/typing.ts b/apps/web-ele/src/components/form-create/typing.ts new file mode 100644 index 000000000..13f98f973 --- /dev/null +++ b/apps/web-ele/src/components/form-create/typing.ts @@ -0,0 +1,60 @@ +import type { Rule } from '@form-create/element-ui'; // 左侧拖拽按钮 + +/** 数据字典 Select 选择器组件 Props 类型 */ +export interface DictSelectProps { + dictType: string; // 字典类型 + valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @芋艿:'boolean' | 'number' | 'string';需要和 vue3 一起统一! + selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + formCreateInject?: any; +} + +/** 左侧拖拽按钮 */ +export interface MenuItem { + label: string; + name: string; + icon: string; +} + +/** 左侧拖拽按钮分类 */ +export interface Menu { + title: string; + name: string; + list: MenuItem[]; +} + +export type MenuList = Array; + +// TODO @dhb52:MenuList、Menu、MenuItem、DragRule 这几个,是不是没用到呀? +// 拖拽组件的规则 +export interface DragRule { + icon: string; + name: string; + label: string; + children?: string; + inside?: true; + drag?: string | true; + dragBtn?: false; + mask?: false; + + rule(): Rule; + + props(v: any, v1: any): Rule[]; +} + +/** 通用 API 下拉组件 Props 类型 */ +export interface ApiSelectProps { + name: string; // 组件名称 + labelField?: string; // 选项标签 + valueField?: string; // 选项的值 + url?: string; // url 接口 + isDict?: boolean; // 是否字典选择器 +} + +/** 选择组件规则配置类型 */ +export interface SelectRuleOption { + label: string; // label 名称 + name: string; // 组件名称 + icon: string; // 组件图标 + props?: any[]; // 组件规则 + event?: any[]; // 事件配置 +} diff --git a/apps/web-ele/src/components/iframe/iframe.vue b/apps/web-ele/src/components/iframe/iframe.vue new file mode 100644 index 000000000..de70d89a9 --- /dev/null +++ b/apps/web-ele/src/components/iframe/iframe.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/components/iframe/index.ts b/apps/web-ele/src/components/iframe/index.ts new file mode 100644 index 000000000..d437bc0d3 --- /dev/null +++ b/apps/web-ele/src/components/iframe/index.ts @@ -0,0 +1 @@ +export { default as IFrame } from './iframe.vue'; diff --git a/apps/web-ele/src/layouts/basic.vue b/apps/web-ele/src/layouts/basic.vue index 1481dc5a8..2777f4a3a 100644 --- a/apps/web-ele/src/layouts/basic.vue +++ b/apps/web-ele/src/layouts/basic.vue @@ -1,12 +1,17 @@ + diff --git a/apps/web-ele/src/layouts/components/tenant-dropdown.vue b/apps/web-ele/src/layouts/components/tenant-dropdown.vue new file mode 100644 index 000000000..031b3f36c --- /dev/null +++ b/apps/web-ele/src/layouts/components/tenant-dropdown.vue @@ -0,0 +1,67 @@ + + diff --git a/apps/web-ele/src/plugins/form-create/index.ts b/apps/web-ele/src/plugins/form-create/index.ts new file mode 100644 index 000000000..f4578b4f2 --- /dev/null +++ b/apps/web-ele/src/plugins/form-create/index.ts @@ -0,0 +1,103 @@ +import type { App, Component } from 'vue'; + +import FcDesigner from '@form-create/designer'; +import formCreate from '@form-create/element-ui'; +import install from '@form-create/element-ui/auto-import'; +// 👇使用 form-create 需额外全局引入 element plus 组件 +import { + ElAlert, + ElAside, + ElBadge, + ElCard, + ElCollapse, + ElCollapseItem, + ElContainer, + ElDivider, + ElDropdown, + ElDropdownItem, + ElDropdownMenu, + ElFooter, + ElHeader, + ElMain, + ElMenu, + ElMenuItem, + ElMessage, + ElPopconfirm, + ElTable, + ElTableColumn, + ElTabPane, + ElTabs, + ElTag, + ElText, + ElTransfer, +} from 'element-plus'; + +// ======================= 自定义组件 ======================= +import { useApiSelect } from '#/components/form-create'; +import DictSelect from '#/components/form-create/components/dict-select.vue'; +import { useImagesUpload } from '#/components/form-create/components/use-images-upload'; +import { Tinymce } from '#/components/tinymce'; +import { FileUpload, ImageUpload } from '#/components/upload'; + +const UserSelect = useApiSelect({ + name: 'UserSelect', + labelField: 'nickname', + valueField: 'id', + url: '/system/user/simple-list', +}); +const DeptSelect = useApiSelect({ + name: 'DeptSelect', + labelField: 'name', + valueField: 'id', + url: '/system/dept/simple-list', +}); +const ApiSelect = useApiSelect({ + name: 'ApiSelect', +}); +const ImagesUpload = useImagesUpload(); + +const components = [ + ImageUpload, + ImagesUpload, + FileUpload, + Tinymce, + DictSelect, + UserSelect, + DeptSelect, + ApiSelect, + ElAlert, + ElTransfer, + ElAside, + ElContainer, + ElDivider, + ElHeader, + ElMain, + ElPopconfirm, + ElTable, + ElTableColumn, + ElTabPane, + ElTabs, + ElDropdown, + ElDropdownMenu, + ElDropdownItem, + ElBadge, + ElTag, + ElText, + ElMenu, + ElMenuItem, + ElFooter, + ElMessage, + ElCollapse, + ElCollapseItem, + ElCard, +]; + +// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档 +export const setupFormCreate = (app: App) => { + components.forEach((component) => { + app.component(component.name as string, component as Component); + }); + formCreate.use(install); + app.use(formCreate); + app.use(FcDesigner); +}; diff --git a/apps/web-ele/src/router/routes/modules/infra.ts b/apps/web-ele/src/router/routes/modules/infra.ts new file mode 100644 index 000000000..cc6d96def --- /dev/null +++ b/apps/web-ele/src/router/routes/modules/infra.ts @@ -0,0 +1,39 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + path: '/infra/job/job-log', + component: () => import('#/views/infra/job/logger/index.vue'), + name: 'InfraJobLog', + meta: { + title: '调度日志', + icon: 'ant-design:history-outlined', + activePath: '/infra/job', + keepAlive: false, + hideInMenu: true, + }, + }, + { + path: '/codegen', + name: 'CodegenEdit', + meta: { + title: '代码生成', + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + hideInMenu: true, + }, + children: [ + { + path: '/codegen/edit', + name: 'InfraCodegenEdit', + component: () => import('#/views/infra/codegen/edit/index.vue'), + meta: { + title: '修改生成配置', + activeMenu: '/infra/codegen', + }, + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-ele/src/router/routes/modules/system.ts b/apps/web-ele/src/router/routes/modules/system.ts new file mode 100644 index 000000000..47e6b1682 --- /dev/null +++ b/apps/web-ele/src/router/routes/modules/system.ts @@ -0,0 +1,16 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + path: '/system/notify-message', + component: () => import('#/views/system/notify/my/index.vue'), + name: 'MyNotifyMessage', + meta: { + title: '我的站内信', + icon: 'ant-design:message-filled', + hideInMenu: true, + }, + }, +]; + +export default routes; diff --git a/apps/web-ele/src/views/_core/profile/index.vue b/apps/web-ele/src/views/_core/profile/index.vue index 936032a13..98b7b3f36 100644 --- a/apps/web-ele/src/views/_core/profile/index.vue +++ b/apps/web-ele/src/views/_core/profile/index.vue @@ -1,7 +1,65 @@ - + + +
+ + + + - + + + + + + + + + + + + + + + +
+
+ diff --git a/apps/web-ele/src/views/_core/profile/modules/base-info.vue b/apps/web-ele/src/views/_core/profile/modules/base-info.vue new file mode 100644 index 000000000..3aa64cd63 --- /dev/null +++ b/apps/web-ele/src/views/_core/profile/modules/base-info.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/web-ele/src/views/_core/profile/modules/profile-user.vue b/apps/web-ele/src/views/_core/profile/modules/profile-user.vue new file mode 100644 index 000000000..675bb131c --- /dev/null +++ b/apps/web-ele/src/views/_core/profile/modules/profile-user.vue @@ -0,0 +1,145 @@ + + + diff --git a/apps/web-ele/src/views/_core/profile/modules/reset-pwd.vue b/apps/web-ele/src/views/_core/profile/modules/reset-pwd.vue new file mode 100644 index 000000000..e4e43978e --- /dev/null +++ b/apps/web-ele/src/views/_core/profile/modules/reset-pwd.vue @@ -0,0 +1,106 @@ + + + diff --git a/apps/web-ele/src/views/_core/profile/modules/user-social.vue b/apps/web-ele/src/views/_core/profile/modules/user-social.vue new file mode 100644 index 000000000..afedf32ff --- /dev/null +++ b/apps/web-ele/src/views/_core/profile/modules/user-social.vue @@ -0,0 +1,215 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiAccessLog/data.ts b/apps/web-ele/src/views/infra/apiAccessLog/data.ts new file mode 100644 index 000000000..f89f82e2d --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/data.ts @@ -0,0 +1,174 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log'; + +import { useAccess } from '@vben/access'; + +import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; + +const { hasAccessByCodes } = useAccess(); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + allowClear: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'applicationName', + label: '应用名', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入应用名', + }, + }, + { + fieldName: 'beginTime', + label: '请求时间', + component: 'RangePicker', + // TODO @puhui999:时间范围不太对。结束时间不是 23:59:59 这种哈 + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'duration', + label: '执行时长', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入执行时长', + }, + }, + { + fieldName: 'resultCode', + label: '结果码', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入结果码', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'applicationName', + title: '应用名', + minWidth: 150, + }, + { + field: 'requestMethod', + title: '请求方法', + minWidth: 80, + }, + { + field: 'requestUrl', + title: '请求地址', + minWidth: 300, + }, + { + field: 'beginTime', + title: '请求时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'duration', + title: '执行时长', + minWidth: 120, + formatter: ({ row }) => `${row.duration} ms`, + }, + { + field: 'resultCode', + title: '操作结果', + minWidth: 150, + formatter: ({ row }) => { + return row.resultCode === 0 ? '成功' : `失败(${row.resultMsg})`; + }, + }, + { + field: 'operateModule', + title: '操作模块', + minWidth: 150, + }, + { + field: 'operateName', + title: '操作名', + minWidth: 220, + }, + { + field: 'operateType', + title: '操作类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_OPERATE_TYPE }, + }, + }, + { + field: 'operation', + title: '操作', + minWidth: 80, + align: 'center', + fixed: 'right', + cellRender: { + attrs: { + nameField: 'id', + nameTitle: 'API访问日志', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'detail', + text: '详情', + show: hasAccessByCodes(['infra:api-access-log:query']), + }, + ], + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/apiAccessLog/index.vue b/apps/web-ele/src/views/infra/apiAccessLog/index.vue new file mode 100644 index 000000000..37ae6ec45 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/index.vue @@ -0,0 +1,110 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue b/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue new file mode 100644 index 000000000..6b72e52df --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue @@ -0,0 +1,107 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiErrorLog/data.ts b/apps/web-ele/src/views/infra/apiErrorLog/data.ts new file mode 100644 index 000000000..0344c3104 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/data.ts @@ -0,0 +1,175 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log'; + +import { useAccess } from '@vben/access'; + +import { + DICT_TYPE, + getDictOptions, + getRangePickerDefaultProps, + InfraApiErrorLogProcessStatusEnum, +} from '#/utils'; + +const { hasAccessByCodes } = useAccess(); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + allowClear: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'applicationName', + label: '应用名', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入应用名', + }, + }, + { + fieldName: 'exceptionTime', + label: '异常时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'processStatus', + label: '处理状态', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, + 'number', + ), + allowClear: true, + placeholder: '请选择处理状态', + }, + defaultValue: InfraApiErrorLogProcessStatusEnum.INIT, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'applicationName', + title: '应用名', + minWidth: 150, + }, + { + field: 'requestMethod', + title: '请求方法', + minWidth: 80, + }, + { + field: 'requestUrl', + title: '请求地址', + minWidth: 200, + }, + { + field: 'exceptionTime', + title: '异常发生时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'exceptionName', + title: '异常名', + minWidth: 180, + }, + { + field: 'processStatus', + title: '处理状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS }, + }, + }, + { + field: 'operation', + title: '操作', + minWidth: 200, + align: 'center', + fixed: 'right', + cellRender: { + attrs: { + nameField: 'id', + nameTitle: 'API错误日志', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'detail', + text: '详情', + show: hasAccessByCodes(['infra:api-error-log:query']), + }, + { + code: 'done', + text: '已处理', + show: (row: InfraApiErrorLogApi.ApiErrorLog) => { + return ( + row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT && + hasAccessByCodes(['infra:api-error-log:update-status']) + ); + }, + }, + { + code: 'ignore', + text: '已忽略', + show: (row: InfraApiErrorLogApi.ApiErrorLog) => { + return ( + row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT && + hasAccessByCodes(['infra:api-error-log:update-status']) + ); + }, + }, + ], + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/apiErrorLog/index.vue b/apps/web-ele/src/views/infra/apiErrorLog/index.vue new file mode 100644 index 000000000..1da4d9e8e --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/index.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue b/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue new file mode 100644 index 000000000..5ddeb1f39 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-ele/src/views/infra/build/index.vue b/apps/web-ele/src/views/infra/build/index.vue new file mode 100644 index 000000000..94c18f8a8 --- /dev/null +++ b/apps/web-ele/src/views/infra/build/index.vue @@ -0,0 +1,183 @@ + + + + diff --git a/apps/web-ele/src/views/infra/codegen/data.ts b/apps/web-ele/src/views/infra/codegen/data.ts new file mode 100644 index 000000000..b9b354a2a --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/data.ts @@ -0,0 +1,591 @@ +import type { Recordable } from '@vben/types'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraCodegenApi } from '#/api/infra/codegen'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { h } from 'vue'; + +import { useAccess } from '@vben/access'; +import { IconifyIcon } from '@vben/icons'; +import { handleTree } from '@vben/utils'; + +import { getDataSourceConfigList } from '#/api/infra/data-source-config'; +import { getMenuList } from '#/api/system/menu'; +import { $t } from '#/locales'; +import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; + +const { hasAccessByCodes } = useAccess(); + +/** 导入数据库表的表单 */ +export function useImportTableFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'dataSourceConfigId', + label: '数据源', + component: 'ApiSelect', + componentProps: { + api: async () => { + const data = await getDataSourceConfigList(); + return data.map((item) => ({ + label: item.name, + value: item.id, + })); + }, + autoSelect: 'first', + placeholder: '请选择数据源', + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '表名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入表名称', + }, + }, + { + fieldName: 'comment', + label: '表描述', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入表描述', + }, + }, + ]; +} + +/** 导入数据库表表格列定义 */ +export function useImportTableColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { field: 'name', title: '表名称', minWidth: 200 }, + { field: 'comment', title: '表描述', minWidth: 200 }, + ]; +} + +/** 基本信息表单的 schema */ +export function useBasicInfoFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'tableName', + label: '表名称', + component: 'Input', + componentProps: { + placeholder: '请输入仓库名称', + }, + rules: 'required', + }, + { + fieldName: 'tableComment', + label: '表描述', + component: 'Input', + componentProps: { + placeholder: '请输入表描述', + }, + rules: 'required', + }, + { + fieldName: 'className', + label: '实体类名称', + component: 'Input', + componentProps: { + placeholder: '请输入实体类名称', + }, + rules: 'required', + help: '默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。', + }, + { + fieldName: 'author', + label: '作者', + component: 'Input', + componentProps: { + placeholder: '请输入作者', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + rows: 3, + placeholder: '请输入备注', + }, + formItemClass: 'md:col-span-2', + }, + ]; +} + +/** 生成信息表单基础 schema */ +export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Select', + fieldName: 'templateType', + label: '生成模板', + componentProps: { + options: getDictOptions( + DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE, + 'number', + ), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'frontType', + label: '前端类型', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE, 'number'), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'scene', + label: '生成场景', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE, 'number'), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + fieldName: 'parentMenuId', + label: '上级菜单', + help: '分配到指定菜单下,例如 系统管理', + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: async () => { + const data = await getMenuList(); + data.unshift({ + id: 0, + name: '顶级菜单', + } as SystemMenuApi.Menu); + return handleTree(data); + }, + class: 'w-full', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级菜单', + filterTreeNode(input: string, node: Recordable) { + if (!input || input.length === 0) { + return true; + } + const name: string = node.label ?? ''; + if (!name) return false; + return name.includes(input) || $t(name).includes(input); + }, + showSearch: true, + treeDefaultExpandedKeys: [0], + }, + rules: 'selectRequired', + renderComponentContent() { + return { + title({ label, icon }: { icon: string; label: string }) { + const components = []; + if (!label) return ''; + if (icon) { + components.push(h(IconifyIcon, { class: 'size-4', icon })); + } + components.push(h('span', { class: '' }, $t(label || ''))); + return h('div', { class: 'flex items-center gap-1' }, components); + }, + }; + }, + }, + { + component: 'Input', + fieldName: 'moduleName', + label: '模块名', + help: '模块名,即一级目录,例如 system、infra、tool 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'businessName', + label: '业务名', + help: '业务名,即二级目录,例如 user、permission、dict 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'className', + label: '类名称', + help: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'classComment', + label: '类描述', + help: '用作类描述,例如 用户', + rules: 'required', + }, + ]; +} + +/** 树表信息 schema */ +export function useGenerationInfoTreeFormSchema( + columns: InfraCodegenApi.CodegenColumn[] = [], +): VbenFormSchema[] { + return [ + { + component: 'Divider', + fieldName: 'treeDivider', + label: '', + renderComponentContent: () => { + return { + default: () => ['树表信息'], + }; + }, + formItemClass: 'md:col-span-2', + }, + { + component: 'Select', + fieldName: 'treeParentColumnId', + label: '父编号字段', + help: '树显示的父编码字段名,例如 parent_Id', + componentProps: { + class: 'w-full', + allowClear: true, + placeholder: '请选择', + options: columns.map((column) => ({ + label: column.columnName, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'treeNameColumnId', + label: '名称字段', + help: '树节点显示的名称字段,一般是 name', + componentProps: { + class: 'w-full', + allowClear: true, + placeholder: '请选择名称字段', + options: columns.map((column) => ({ + label: column.columnName, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + ]; +} + +/** 主子表信息 schema */ +export function useGenerationInfoSubTableFormSchema( + columns: InfraCodegenApi.CodegenColumn[] = [], + tables: InfraCodegenApi.CodegenTable[] = [], +): VbenFormSchema[] { + return [ + { + component: 'Divider', + fieldName: 'subDivider', + label: '', + renderComponentContent: () => { + return { + default: () => ['主子表信息'], + }; + }, + formItemClass: 'md:col-span-2', + }, + { + component: 'Select', + fieldName: 'masterTableId', + label: '关联的主表', + help: '关联主表(父表)的表名, 如:system_user', + componentProps: { + class: 'w-full', + allowClear: true, + placeholder: '请选择', + options: tables.map((table) => ({ + label: `${table.tableName}:${table.tableComment}`, + value: table.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'subJoinColumnId', + label: '子表关联的字段', + help: '子表关联的字段, 如:user_id', + componentProps: { + class: 'w-full', + allowClear: true, + placeholder: '请选择', + options: columns.map((column) => ({ + label: `${column.columnName}:${column.columnComment}`, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'RadioGroup', + fieldName: 'subJoinMany', + label: '关联关系', + help: '主表与子表的关联关系', + componentProps: { + class: 'w-full', + allowClear: true, + placeholder: '请选择', + options: [ + { + label: '一对多', + value: true, + }, + { + label: '一对一', + value: 'false', + }, + ], + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'tableName', + label: '表名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入表名称', + }, + }, + { + fieldName: 'tableComment', + label: '表描述', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入表描述', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, + getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'dataSourceConfigId', + title: '数据源', + minWidth: 120, + formatter: (row) => getDataSourceConfigName?.(row.cellValue) || '-', + }, + { + field: 'tableName', + title: '表名称', + minWidth: 200, + }, + { + field: 'tableComment', + title: '表描述', + minWidth: 200, + }, + { + field: 'className', + title: '实体', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'operation', + title: '操作', + width: 300, + fixed: 'right', + align: 'center', + cellRender: { + attrs: { + nameField: 'tableName', + nameTitle: '代码生成', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'preview', + text: '预览', + show: hasAccessByCodes(['infra:codegen:preview']), + }, + { + code: 'edit', + show: hasAccessByCodes(['infra:codegen:update']), + }, + { + code: 'delete', + show: hasAccessByCodes(['infra:codegen:delete']), + }, + { + code: 'sync', + text: '同步', + show: hasAccessByCodes(['infra:codegen:update']), + }, + { + code: 'generate', + text: '生成代码', + show: hasAccessByCodes(['infra:codegen:download']), + }, + ], + }, + }, + ]; +} + +/** 代码生成表格列定义 */ +export function useCodegenColumnTableColumns(): VxeTableGridOptions['columns'] { + return [ + { field: 'columnName', title: '字段列名', minWidth: 130 }, + { + field: 'columnComment', + title: '字段描述', + minWidth: 100, + slots: { default: 'columnComment' }, + }, + { field: 'dataType', title: '物理类型', minWidth: 100 }, + { + field: 'javaType', + title: 'Java 类型', + minWidth: 130, + slots: { default: 'javaType' }, + params: { + options: [ + { label: 'Long', value: 'Long' }, + { label: 'String', value: 'String' }, + { label: 'Integer', value: 'Integer' }, + { label: 'Double', value: 'Double' }, + { label: 'BigDecimal', value: 'BigDecimal' }, + { label: 'LocalDateTime', value: 'LocalDateTime' }, + { label: 'Boolean', value: 'Boolean' }, + ], + }, + }, + { + field: 'javaField', + title: 'Java 属性', + minWidth: 100, + slots: { default: 'javaField' }, + }, + { + field: 'createOperation', + title: '插入', + width: 40, + slots: { default: 'createOperation' }, + }, + { + field: 'updateOperation', + title: '编辑', + width: 40, + slots: { default: 'updateOperation' }, + }, + { + field: 'listOperationResult', + title: '列表', + width: 40, + slots: { default: 'listOperationResult' }, + }, + { + field: 'listOperation', + title: '查询', + width: 40, + slots: { default: 'listOperation' }, + }, + { + field: 'listOperationCondition', + title: '查询方式', + minWidth: 100, + slots: { default: 'listOperationCondition' }, + params: { + options: [ + { label: '=', value: '=' }, + { label: '!=', value: '!=' }, + { label: '>', value: '>' }, + { label: '>=', value: '>=' }, + { label: '<', value: '<' }, + { label: '<=', value: '<=' }, + { label: 'LIKE', value: 'LIKE' }, + { label: 'BETWEEN', value: 'BETWEEN' }, + ], + }, + }, + { + field: 'nullable', + title: '允许空', + width: 60, + slots: { default: 'nullable' }, + }, + { + field: 'htmlType', + title: '显示类型', + width: 130, + slots: { default: 'htmlType' }, + params: { + options: [ + { label: '文本框', value: 'input' }, + { label: '文本域', value: 'textarea' }, + { label: '下拉框', value: 'select' }, + { label: '单选框', value: 'radio' }, + { label: '复选框', value: 'checkbox' }, + { label: '日期控件', value: 'datetime' }, + { label: '图片上传', value: 'imageUpload' }, + { label: '文件上传', value: 'fileUpload' }, + { label: '富文本控件', value: 'editor' }, + ], + }, + }, + { + field: 'dictType', + title: '字典类型', + width: 120, + slots: { default: 'dictType' }, + }, + { + field: 'example', + title: '示例', + minWidth: 100, + slots: { default: 'example' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/codegen/edit/index.vue b/apps/web-ele/src/views/infra/codegen/edit/index.vue new file mode 100644 index 000000000..e338334cd --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/edit/index.vue @@ -0,0 +1,169 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/index.vue b/apps/web-ele/src/views/infra/codegen/index.vue new file mode 100644 index 000000000..a9efeaab9 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/index.vue @@ -0,0 +1,228 @@ + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue b/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue new file mode 100644 index 000000000..00c49911b --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue @@ -0,0 +1,45 @@ + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/column-info.vue b/apps/web-ele/src/views/infra/codegen/modules/column-info.vue new file mode 100644 index 000000000..d8d2aed9d --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/column-info.vue @@ -0,0 +1,151 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue b/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue new file mode 100644 index 000000000..da859ef2f --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue @@ -0,0 +1,172 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/import-table.vue b/apps/web-ele/src/views/infra/codegen/modules/import-table.vue new file mode 100644 index 000000000..aa4cd5814 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/import-table.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue b/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue new file mode 100644 index 000000000..e3332428b --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/apps/web-ele/src/views/infra/config/data.ts b/apps/web-ele/src/views/infra/config/data.ts new file mode 100644 index 000000000..1a79bc3b2 --- /dev/null +++ b/apps/web-ele/src/views/infra/config/data.ts @@ -0,0 +1,209 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraConfigApi } from '#/api/infra/config'; + +import { useAccess } from '@vben/access'; + +import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; + +const { hasAccessByCodes } = useAccess(); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'category', + label: '参数分类', + component: 'Input', + componentProps: { + placeholder: '请输入参数分类', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '参数名称', + component: 'Input', + componentProps: { + placeholder: '请输入参数名称', + }, + rules: 'required', + }, + { + fieldName: 'key', + label: '参数键名', + component: 'Input', + componentProps: { + placeholder: '请输入参数键名', + }, + rules: 'required', + }, + { + fieldName: 'value', + label: '参数键值', + component: 'Input', + componentProps: { + placeholder: '请输入参数键值', + }, + rules: 'required', + }, + { + fieldName: 'visible', + label: '是否可见', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + defaultValue: true, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '参数名称', + component: 'Input', + componentProps: { + placeholder: '请输入参数名称', + clearable: true, + }, + }, + { + fieldName: 'key', + label: '参数键名', + component: 'Input', + componentProps: { + placeholder: '请输入参数键名', + clearable: true, + }, + }, + { + fieldName: 'type', + label: '系统内置', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE, 'number'), + placeholder: '请选择系统内置', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '参数主键', + minWidth: 100, + }, + { + field: 'category', + title: '参数分类', + minWidth: 120, + }, + { + field: 'name', + title: '参数名称', + minWidth: 200, + }, + { + field: 'key', + title: '参数键名', + minWidth: 200, + }, + { + field: 'value', + title: '参数键值', + minWidth: 150, + }, + { + field: 'visible', + title: '是否可见', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'type', + title: '系统内置', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_CONFIG_TYPE }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'operation', + title: '操作', + minWidth: 130, + align: 'center', + fixed: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: '参数', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'edit', + show: hasAccessByCodes(['infra:config:update']), + }, + { + code: 'delete', + show: hasAccessByCodes(['infra:config:delete']), + }, + ], + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/config/index.vue b/apps/web-ele/src/views/infra/config/index.vue new file mode 100644 index 000000000..0c9d66772 --- /dev/null +++ b/apps/web-ele/src/views/infra/config/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/apps/web-ele/src/views/infra/config/modules/form.vue b/apps/web-ele/src/views/infra/config/modules/form.vue new file mode 100644 index 000000000..d775a1a5c --- /dev/null +++ b/apps/web-ele/src/views/infra/config/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/data.ts b/apps/web-ele/src/views/infra/dataSourceConfig/data.ts new file mode 100644 index 000000000..c22a368ad --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/data.ts @@ -0,0 +1,119 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config'; + +import { useAccess } from '@vben/access'; + +const { hasAccessByCodes } = useAccess(); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '数据源名称', + component: 'Input', + componentProps: { + placeholder: '请输入数据源名称', + }, + rules: 'required', + }, + { + fieldName: 'url', + label: '数据源连接', + component: 'Input', + componentProps: { + placeholder: '请输入数据源连接', + }, + rules: 'required', + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + }, + rules: 'required', + }, + { + fieldName: 'password', + label: '密码', + component: 'Input', + componentProps: { + placeholder: '请输入密码', + type: 'password', + }, + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '主键编号', + minWidth: 100, + }, + { + field: 'name', + title: '数据源名称', + minWidth: 150, + }, + { + field: 'url', + title: '数据源连接', + minWidth: 300, + }, + { + field: 'username', + title: '用户名', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'operation', + title: '操作', + minWidth: 130, + align: 'center', + fixed: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: '数据源', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'edit', + show: hasAccessByCodes(['infra:data-source-config:update']), + disabled: (row: any) => row.id === 0, + }, + { + code: 'delete', + show: hasAccessByCodes(['infra:data-source-config:delete']), + disabled: (row: any) => row.id === 0, + }, + ], + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/index.vue b/apps/web-ele/src/views/infra/dataSourceConfig/index.vue new file mode 100644 index 000000000..4a2173902 --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue b/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue new file mode 100644 index 000000000..2fc053458 --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/infra/druid/index.vue b/apps/web-ele/src/views/infra/druid/index.vue new file mode 100644 index 000000000..eb91f8d47 --- /dev/null +++ b/apps/web-ele/src/views/infra/druid/index.vue @@ -0,0 +1,38 @@ + + +