diff --git a/src/assets/stylus/root.styl b/src/assets/stylus/root.styl index a5d4bce..f515993 100644 --- a/src/assets/stylus/root.styl +++ b/src/assets/stylus/root.styl @@ -20,8 +20,8 @@ body { .region .el-radio-button__original-radio:checked + .el-radio-button__inner { color: var(--theme-color) !important; } - letter-spacing: 1px; - user-select: none; + letter-spacing: .5px; + //user-select: none; } .el-message-box { diff --git a/src/common/utils/desensitize.ts b/src/common/utils/desensitize.ts new file mode 100644 index 0000000..9077625 --- /dev/null +++ b/src/common/utils/desensitize.ts @@ -0,0 +1,54 @@ +import Strings from '@/common/utils/strings.ts' + +export default { + mobile: (value: string) => { + if (Strings.isBlank(value)) { + return '' + } + + if (!/^1[3-9]\d{9}$/.test(value)) { + return value + } + return value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') + }, + idcard: (value: string) => { + if (Strings.isBlank(value)) { + return '' + } + if (value.length !== 18) { + return value + } + return value.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2') + }, + email: (value: string) => { + if (Strings.isBlank(value)) { + return '' + } + const emailRegex = /^(\w)\w*@(\w+\.\w+)$/ + if (!emailRegex.test(value)) { + return value + } + return value.replace(emailRegex, '$1****@$2') + }, + bankCard: (value: string) => { + if (Strings.isBlank(value)) { + return '' + } + if (value.length < 10) { + return value + } + return value.replace(/(\d{6})\d+(\d{4})$/, '$1****$2') + }, + name: (value: string) => { + if (Strings.isBlank(value)) { + return '' + } + if (value.length === 1) { + return value + } else if (value.length === 2) { + return value.replace(/(.)./, '$1*') + } else { + return value.replace(/(.).*(.)/, '$1*$2') + } + }, +} diff --git a/src/components/a-desensitize/ADesensitize.vue b/src/components/a-desensitize/ADesensitize.vue new file mode 100644 index 0000000..eb8ca97 --- /dev/null +++ b/src/components/a-desensitize/ADesensitize.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/components/a-form-panel/AFormPanel.tsx b/src/components/a-form-panel/AFormPanel.tsx new file mode 100644 index 0000000..4eebfaf --- /dev/null +++ b/src/components/a-form-panel/AFormPanel.tsx @@ -0,0 +1,138 @@ +import type { Arrayable } from 'element-plus/es/utils' +import { + ElButton, + ElForm, + ElLoading, + ElMessage, + type FormInstance, + type FormItemRule, +} from 'element-plus' +import { + defineComponent, + withDirectives, +} from 'vue' +import type { SetupContext } from '@vue/runtime-core' +import ADialog from '@/components/a-dialog/ADialog.vue' +import Utils from '@/common/utils' +import FormUtil from '@/common/utils/formUtil.ts' +import styles from '@/components/a-form-panel/a-form-panel.module.styl' + +export interface FormPanelType { + title: string + detailsLoader: (id?: string) => Promise + doSubmit: (data: T) => Promise + rules?: Partial>> + labelWidth?: string + width?: string + modal?: boolean + appendToBody?: boolean +} + +const component = defineComponent( + (props: FormPanelType, {slots, expose}: SetupContext) => { + const formIns = useTemplateRef('formRef') + const showDialog = ref(false) + const loading = ref(false) + + const formData = Utils.resetAble(reactive({} as T)) + + function dialogCloseHandler() { + formData.$reset() + } + + const submiting = ref(false) + + function submitHandler() { + submiting.value = true + FormUtil.submit(formIns, () => props.doSubmit(formData.$clone() as T)) + .then(res => { + ElMessage.success('提交成功') + if ((res ?? true)) { + showDialog.value = false + } + }) + .finally(() => { + submiting.value = false + }) + } + + const open = (id?: string) => { + loading.value = true + props.detailsLoader(id) + .then(res => { + formData.$reset((res ?? {}) as any) + }) + .finally(() => { + loading.value = false + }) + showDialog.value = true + } + expose({open}) + + return () => ( showDialog.value = val} + closed={dialogCloseHandler} + submit-handler={submitHandler} + title={props.title} + width={props.width} + append-to-body={props.appendToBody} + modal={props.modal} + > + {{ + default: () => withDirectives( + { + slots.default?.(formData as T) + } + , + [ [ ElLoading.directive, loading.value ] ]), + footer: () => ( + <> + showDialog.value = false}>关闭 + 提交 + + ), + }} + ) + }, + { + name: 'AFormPanel', + props: [ 'title', 'detailsLoader', 'doSubmit', 'rules', 'labelWidth', 'width', 'modal', 'appendToBody' ], + }) + +export interface AFormPanelInstance extends InstanceType { + open: (id?: string) => void +} + + +export function buildFormPanelProps(props: DeepPartial>) { + if (props.labelWidth == null) { + props.labelWidth = '80px' + } + if (props.appendToBody == null) { + props.appendToBody = false + } + if (props.width == null) { + props.width = 'fit-content' + } + if (props.modal == null) { + props.modal = false + } + return reactive({ + title: props.title!, + detailsLoader: props.detailsLoader!, + doSubmit: props.doSubmit!, + rules: props.rules, + labelWidth: props.labelWidth!, + width: props.width!, + modal: props.modal!, + appendToBody: props.appendToBody!, + } as FormPanelType) +} + +export default component diff --git a/src/components/a-form-panel/AFormPanel.vue b/src/components/a-form-panel/AFormPanel.vue deleted file mode 100644 index 3508090..0000000 --- a/src/components/a-form-panel/AFormPanel.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - - - diff --git a/src/components/a-form-panel/a-form-panel.module.styl b/src/components/a-form-panel/a-form-panel.module.styl new file mode 100644 index 0000000..b8cf544 --- /dev/null +++ b/src/components/a-form-panel/a-form-panel.module.styl @@ -0,0 +1,5 @@ +.form-panel > div:first-child { + padding 20px + display: grid; + gap: 0 30px; +} diff --git a/src/components/a-form-panel/a-form-panel.ts b/src/components/a-form-panel/a-form-panel.ts new file mode 100644 index 0000000..636be21 --- /dev/null +++ b/src/components/a-form-panel/a-form-panel.ts @@ -0,0 +1,29 @@ +import type { Arrayable } from 'element-plus/es/utils' +import type { FormItemRule } from 'element-plus' + +export interface FormPanelType { + title: string + detailsLoader: (id?: string) => Promise + doSubmit: (data: T) => Promise + rules?: Partial>> + labelWidth: string + width: string + modal: boolean + appendToBody: boolean +} + +export function buildFormPanelProps(props: DeepPartial>) { + if (props.labelWidth == null) { + props.labelWidth = '80px' + } + if (props.appendToBody == null) { + props.appendToBody = false + } + if (props.width == null) { + props.width = 'fit-content' + } + if (props.modal == null) { + props.modal = false + } + return reactive(props as FormPanelType) +} diff --git a/src/components/a-page/a-table-page/ATablePage.tsx b/src/components/a-page/a-table-page/ATablePage.tsx index 57f1b6f..629b480 100644 --- a/src/components/a-page/a-table-page/ATablePage.tsx +++ b/src/components/a-page/a-table-page/ATablePage.tsx @@ -142,10 +142,16 @@ interface FormPropsType { * 页布局配置 */ interface PageLayoutType { + /** + * 查询表单高度,传入数字时,单位:fr,传入字符串时不会而外添加单位,,默认:1fr + */ + searchFormHeight: number | string /** * 数据表格高度,传入数字时,单位:fr,传入字符串时不会而外添加单位,,默认:9fr */ dataListHeight: number | string + showHighForm: boolean + enableHighForm: boolean } interface ATablePageType

{ @@ -187,10 +193,22 @@ function buildSearchForm(searchForm: DeepPartial = {}) { + if (pageLayout.searchFormHeight == null) { + pageLayout.searchFormHeight = 1 + } + if (pageLayout.dataListHeight == null) { pageLayout.dataListHeight = 9 } + if (pageLayout.showHighForm == null) { + pageLayout.showHighForm = false + } + + if (pageLayout.enableHighForm == null) { + pageLayout.enableHighForm = true + } + return pageLayout } @@ -211,13 +229,16 @@ function buildTable

(table: DeepPartial(false) - const showHighForm = ref(false) + const showHighForm = ref(props.pageLayout.showHighForm) const doSearch = () => { loading.value = true @@ -331,8 +352,10 @@ const component = defineComponent( const pageCssParam = computed(() => { const dataListHeight = props.pageLayout.dataListHeight + const searchFormHeight = props.pageLayout.searchFormHeight return { '--data-list-height': Types.isString(dataListHeight) ? dataListHeight : dataListHeight + 'fr', + '--search-form-height': Types.isString(searchFormHeight) ? searchFormHeight : searchFormHeight + 'fr', } }) const actionColumnBtnRender = (scope: ColumnScopeType, tableAction: TableActionType) => { @@ -466,10 +489,14 @@ const component = defineComponent( }} ) } + const sortChangeHandler = ({prop, order}: { prop: string, order: 'ascending' | 'descending' | null }) => { + formData.orders = order == null ? undefined : (prop + ':' + (order == 'ascending' ? 'asc' : 'desc')) + doSearch() + } onMounted(doSearch) return () => (
- + {props.pageLayout.enableHighForm ? ( {/*@ts-ignore*/} { @@ -477,7 +504,7 @@ const component = defineComponent( }
@@ -530,9 +557,11 @@ const component = defineComponent( ) } - - - + { + props.pageLayout.enableHighForm ? ( + + ) : <>}
@@ -548,6 +577,7 @@ const component = defineComponent( cell-class-name="table-cell" header-row-class-name="table-header" class="data-table" + onSort-change={sortChangeHandler} > { slots.columns?.() diff --git a/src/components/a-page/a-table-page/a-table-page.module.styl b/src/components/a-page/a-table-page/a-table-page.module.styl index 78f12d5..91120f6 100644 --- a/src/components/a-page/a-table-page/a-table-page.module.styl +++ b/src/components/a-page/a-table-page/a-table-page.module.styl @@ -1,6 +1,6 @@ .table-page { grid-template-columns 1fr - grid-template-rows: minmax(74px, 1fr) minmax(277px, var(--data-list-height)); + grid-template-rows: minmax(74px, var(--search-form-height)) minmax(277px, var(--data-list-height)); grid-auto-rows: minmax(0, auto); gap 10px @@ -74,11 +74,13 @@ .tool-bar-right { display grid - grid-template-columns: minmax(200px, 1fr) 150px; + grid-template-columns: minmax(200px, 1fr) auto; grid-auto-rows: 32px; gap 10px & > div:first-child { + justify-self: end; + :global(.el-form) { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); @@ -92,7 +94,10 @@ & > div:last-child { display: flex; - justify-content: space-evenly; + gap 10px + justify-content: flex-end; + min-width 65px + max-width 150px :global(.el-button--default) { width 32px @@ -119,7 +124,6 @@ } } - /*:global(.el-table) { width 100% height: 100%; @@ -199,6 +203,7 @@ } } */ + :global(.el-pagination) { justify-content center diff --git a/src/components/a-table-column/ADesensitizeColumn.vue b/src/components/a-table-column/ADesensitizeColumn.vue new file mode 100644 index 0000000..2ed396e --- /dev/null +++ b/src/components/a-table-column/ADesensitizeColumn.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/dts/components.d.ts b/src/dts/components.d.ts index 9c8065c..c718434 100644 --- a/src/dts/components.d.ts +++ b/src/dts/components.d.ts @@ -17,6 +17,7 @@ declare module 'vue' { ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElButton: typeof import('element-plus/es')['ElButton'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] @@ -33,6 +34,7 @@ declare module 'vue' { ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose'] + ElIconPicture: typeof import('@element-plus/icons-vue')['Picture'] ElIconPlus: typeof import('@element-plus/icons-vue')['Plus'] ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] @@ -53,6 +55,8 @@ declare module 'vue' { ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] @@ -70,6 +74,7 @@ declare global { const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] const ElButton: typeof import('element-plus/es')['ElButton'] const ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + const ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] const ElContainer: typeof import('element-plus/es')['ElContainer'] const ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] @@ -86,6 +91,7 @@ declare global { const ElHeader: typeof import('element-plus/es')['ElHeader'] const ElIcon: typeof import('element-plus/es')['ElIcon'] const ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose'] + const ElIconPicture: typeof import('@element-plus/icons-vue')['Picture'] const ElIconPlus: typeof import('@element-plus/icons-vue')['Plus'] const ElImage: typeof import('element-plus/es')['ElImage'] const ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] @@ -106,6 +112,8 @@ declare global { const ElTabs: typeof import('element-plus/es')['ElTabs'] const ElTag: typeof import('element-plus/es')['ElTag'] const ElTooltip: typeof import('element-plus/es')['ElTooltip'] + const ElTransfer: typeof import('element-plus/es')['ElTransfer'] + const ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] const ElUpload: typeof import('element-plus/es')['ElUpload'] const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] diff --git a/src/pages/cst/customer/CustomerForm.vue b/src/pages/cst/customer/CustomerForm.vue index 9138a08..4938a65 100644 --- a/src/pages/cst/customer/CustomerForm.vue +++ b/src/pages/cst/customer/CustomerForm.vue @@ -1,9 +1,9 @@