表格自定义

main
expressgy 3 months ago
parent aab3a5be5e
commit 01571bffd1
  1. 4
      src/api/http.js
  2. 45
      src/components/AntDesignVue/CustomAntDesignVue/TableColumChoose/index.vue
  3. 4
      src/components/AntDesignVue/CustomAntDesignVue/index.js
  4. 4
      src/components/AntDesignVue/Icon/index.js
  5. 10
      src/components/AntDesignVue/index.js
  6. 89
      src/views/Auth/Dict/DictForm.vue
  7. 147
      src/views/Auth/Dict/DictTable.vue
  8. 40
      src/views/Auth/Dict/DictTree.vue
  9. 109
      src/views/Auth/Dict/index.vue

@ -74,7 +74,7 @@ service.interceptors.response.use(
return data.data; return data.data;
} else if (data.statusCode === 403) { } else if (data.statusCode === 403) {
// ! WARN ============================================ // ! WARN ============================================
window.pino.error('经常报Token过期'); window.pino.error('Token过期');
// ! 如果已经过期,返回的响应就正常返回 // ! 如果已经过期,返回的响应就正常返回
if (!isExpired) { if (!isExpired) {
@ -116,8 +116,6 @@ service.interceptors.response.use(
} }
}, },
async (error) => { async (error) => {
console.log('B');
// const { /*response, code,*/ message } = error; // const { /*response, code,*/ message } = error;
Message.error(error.message || 'Error'); Message.error(error.message || 'Error');
return Promise.reject(error); return Promise.reject(error);

@ -0,0 +1,45 @@
<script setup name="TableColumChoose">
import { h, ref, watch } from 'vue';
import { BgColorsOutlined } from '@ant-design/icons-vue';
defineOptions({
name: 'TableColumChoose',
});
const props = defineProps({
columnList: {
type: Array,
required: true,
},
});
const status = ref(false);
const checkedKeys = ref(props.columnList.filter((item) => item.status === true).map((item) => item.key));
const handleClose = () => {
};
watch(checkedKeys, (newVal) => {
props.columnList.forEach((item) => {
if (checkedKeys.value.includes(item.key)) {
item.status = true;
} else {
item.status = false;
}
});
});
const onDrop = (info) => {
const before = info.dragNode.pos.split('-').slice(-1)[0];
const after = info.node.pos.split('-').slice(-1)[0];
const target = props.columnList.splice(before, 1)[0];
props.columnList.splice(after, 0, target);
};
</script>
<template>
<div>
<AButton shape="circle" :icon="h(BgColorsOutlined)" @click="status = true" />
<a-drawer v-model:open="status" title="表格列选择" placement="right" :fieldNames="{ title: 'name' }" @close="handleClose">
<a-tree class="draggable-tree" draggable block-node checkable v-model:checkedKeys="checkedKeys" :tree-data="props.columnList" @drop="onDrop" />
<!-- @dragenter="onDragEnter"-->
</a-drawer>
</div>
</template>
<style scoped></style>

@ -16,6 +16,7 @@ import AckCreateAntdButton from './Button/AckCreateAntdButton.vue';
import AntdModalTemplate from './AntdModalTemplate/index.vue'; import AntdModalTemplate from './AntdModalTemplate/index.vue';
import IconSelect from './IconSelect/index.vue'; import IconSelect from './IconSelect/index.vue';
import CustomIconSelect from './IconSelect/customIconSelect.vue'; import CustomIconSelect from './IconSelect/customIconSelect.vue';
import TableColumChoose from '@/components/AntDesignVue/CustomAntDesignVue/TableColumChoose/index.vue';
// console.log(AntdModalTemplate); // console.log(AntdModalTemplate);
@ -26,7 +27,8 @@ export default function setupCustomAntdComponents(app) {
AckCreateAntdButton, AckCreateAntdButton,
AntdModalTemplate, AntdModalTemplate,
IconSelect, IconSelect,
CustomIconSelect CustomIconSelect,
TableColumChoose
]; ];
for (let component of customComponentList) { for (let component of customComponentList) {
app.component(component.name, component); app.component(component.name, component);

@ -23,6 +23,7 @@ const iconPool2 = import.meta.glob('./icon2/*.svg', {
query: '?raw', query: '?raw',
import: 'default', import: 'default',
}); });
const iconPool = {};
function makeIcon(pool) { function makeIcon(pool) {
const svgContentPool1 = pool; const svgContentPool1 = pool;
@ -49,9 +50,10 @@ function makeIcon(pool) {
}, },
}); });
iconComponentList1.push(icon); iconComponentList1.push(icon);
iconPool[svgName] = icon;
} }
return iconComponentList1; return iconComponentList1;
} }
export const IconPool = iconPool;
export const custonIcon1 = makeIcon(iconPool1); export const custonIcon1 = makeIcon(iconPool1);
export const custonIcon2 = makeIcon(iconPool2); export const custonIcon2 = makeIcon(iconPool2);

@ -28,7 +28,7 @@ import {
Input, Input,
Radio, Radio,
RadioGroup, RadioGroup,
InputNumber, Affix, Anchor, AnchorLink, InputNumber, Affix, Anchor, AnchorLink, Tree, InputSearch, Tooltip, Drawer, Checkbox, CheckboxGroup,
} from 'ant-design-vue'; } from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css'; import 'ant-design-vue/dist/reset.css';
@ -65,7 +65,13 @@ export function setupCustomAntDesignVueComponents(app) {
InputNumber, // ! 数字输入框 InputNumber, // ! 数字输入框
Affix, // ! 固钉 Affix, // ! 固钉
Anchor, // ! 锚点 Anchor, // ! 锚点
AnchorLink, AnchorLink, // ! 锚点项
Tree, // ! 树
InputSearch, // ! 树的查询
Tooltip, // ! 提示器
Drawer, // ! 抽屉
Checkbox, // ! 多选
CheckboxGroup,
]; ];
for (let component of componentList) { for (let component of componentList) {
app.component(component.name, component); app.component(component.name, component);

@ -1,5 +1,5 @@
<script setup name="DictForm"> <script setup name="DictForm">
import { onMounted, h, watch, reactive } from 'vue'; import { onMounted, h, watch, reactive, ref } from 'vue';
import { useBaseDataStore } from '@/stores/baseData.js'; import { useBaseDataStore } from '@/stores/baseData.js';
import { ReloadOutlined, SettingOutlined } from '@ant-design/icons-vue'; import { ReloadOutlined, SettingOutlined } from '@ant-design/icons-vue';
const baseDataStore = useBaseDataStore(); const baseDataStore = useBaseDataStore();
@ -11,9 +11,9 @@ const props = defineProps({
required: true, required: true,
default: () => ({ default: () => ({
status: true, status: true,
form: { formData: {
pid: 0, pid: 0,
// //
dictKey: '', dictKey: '',
// //
dictDesc: '', dictDesc: '',
@ -36,18 +36,37 @@ const props = defineProps({
}), }),
}, },
}); });
// ! ref
const formRef = ref(null);
// ! Form // ! Form
const form = reactive({ const form = reactive({
...props.data.form, ...props.data.formData,
});
// !
const serviceKeyDisabled = ref(false);
// !
const rules = reactive({
pid: [],
serviceKey: [{ required: true, message: '请选择字典所属服务!', trigger: ['change', 'blur'] }],
dictKey: [{ required: true, min: 1, max: 32, message: '请将字典标识长度控制在1到32位!', trigger: ['change', 'blur'] }],
dictDesc: [],
dictName: [{ required: true, message: '请输入字典名称!' }],
dictIcon: [],
dictType: [{ required: true, message: '请选择字典类型!' }],
root: [],
orderNum: [],
}); });
// ! pid // ! pid
watch(form, (newVal, oldVal) => { watch(form, (newVal, oldVal) => {
if (form.pid != '') { if (newVal.pid != '') {
const dictList = baseDataStore.state.dictList.filter((item) => { const dictList = baseDataStore.state.dictList.filter((item) => {
return item.dictId == form.pid; return item.dictId == newVal.pid;
}); });
if (dictList.length > 0) { if (dictList.length > 0) {
form.serviceKey = dictList[0].serviceKey; newVal.serviceKey = dictList[0].serviceKey;
serviceKeyDisabled.value = true;
} else {
serviceKeyDisabled.value = false;
} }
} }
}); });
@ -64,23 +83,54 @@ onMounted(() => {
baseDataStore.getServiceList(); baseDataStore.getServiceList();
} }
}); });
function handleChange(da){
console.log(da); // !
}
const handleAck = () => { const handleAck = () => {
props.data.handleAck(form) formRef.value
} .validate()
.then((value) => {
const data = { ...value };
if (Array.isArray(value.pid) && value.pid.length > 0) {
data.pid = value.pid.slice(-1)[0];
}
props.data.handleAck(data);
clearInput();
})
.catch((err) => {
formRef.value.scrollToField(err.errorFields[0]?.name);
});
};
// !
const handleCancel = () => { const handleCancel = () => {
props.data.handleCancel() props.data.handleCancel();
clearInput();
};
// !
function clearValidate() {
formRef.value?.clearValidate();
}
// !
function clearInput() {
formRef.value.resetFields();
clearValidate();
} }
</script> </script>
<template> <template>
<AntdModalTemplate> <AntdModalTemplate>
<AForm :model="form" name="DictForm" :label-col="{ span: 8 }" layout="vertical"> <AForm ref="formRef" :model="form" :rules="rules" name="DictForm" :label-col="{ span: 8 }" layout="vertical">
<AFormItem label="上级字典" name="pid"> <AFormItem label="上级字典" name="pid">
<AFlex gap="small"> <AFlex gap="small">
<ACascader flex="1" allowClear showSearch v-model:value="form.pid" :options="baseDataStore.state.dictTree" placeholder="请选择上级字典" change-on-select /> <ACascader
flex="1"
allowClear
showSearch
v-model:value="form.pid"
:options="baseDataStore.state.dictTree"
:multiple="false"
placeholder="请选择上级字典"
change-on-select
/>
<AButton flex="0" :icon="h(ReloadOutlined)" @click="baseDataStore.getDictList()" shape="circle" /> <AButton flex="0" :icon="h(ReloadOutlined)" @click="baseDataStore.getDictList()" shape="circle" />
</AFlex> </AFlex>
</AFormItem> </AFormItem>
@ -90,6 +140,7 @@ const handleCancel = () => {
<ASelect <ASelect
placeholder="请选择所属服务" placeholder="请选择所属服务"
ref="serviceKey" ref="serviceKey"
:disabled="serviceKeyDisabled"
v-model:value="form.serviceKey" v-model:value="form.serviceKey"
:options="baseDataStore.state.serviceList" :options="baseDataStore.state.serviceList"
:field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }" :field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }"
@ -98,8 +149,8 @@ const handleCancel = () => {
</AFlex> </AFlex>
</AFormItem> </AFormItem>
<AFormItem label="字典标志" name="dictName"> <AFormItem label="字典标识" name="dictKey">
<AInput style="width: 100%" v-model:value="form.dictKey" placeholder="请输入字典标" :maxlength="32" showCount /> <AInput style="width: 100%" v-model:value="form.dictKey" placeholder="请输入字典标" :maxlength="32" showCount />
</AFormItem> </AFormItem>
<AFormItem label="字典名称" name="dictName"> <AFormItem label="字典名称" name="dictName">
@ -111,18 +162,20 @@ const handleCancel = () => {
</AFormItem> </AFormItem>
<AFormItem label="字典图标" name="dictIcon"> <AFormItem label="字典图标" name="dictIcon">
<CustomIconSelect v-model:value="form.dictIcon"/> <CustomIconSelect v-model:value="form.dictIcon" />
</AFormItem> </AFormItem>
<AFormItem label="字典描述" name="dictDesc"> <AFormItem label="字典描述" name="dictDesc">
<ATextarea style="width: 100%" v-model:value="form.dictDesc" :rows="3" placeholder="请输入字典描述" :maxlength="255" showCount /> <ATextarea style="width: 100%" v-model:value="form.dictDesc" :rows="3" placeholder="请输入字典描述" :maxlength="255" showCount />
</AFormItem> </AFormItem>
<AFormItem label="原始数据Root" name="root"> <AFormItem label="原始数据Root" name="root">
<ARadioGroup v-model:value="form.root" name="radioGroup"> <ARadioGroup v-model:value="form.root" name="radioGroup">
<ARadio :value="true"></ARadio> <ARadio :value="true"></ARadio>
<ARadio :value="false"></ARadio> <ARadio :value="false"></ARadio>
</ARadioGroup> </ARadioGroup>
</AFormItem> </AFormItem>
<AFormItem label="排序" name="orderNum"> <AFormItem label="排序" name="orderNum">
<AInputNumber v-model:value="form.orderNum"> <AInputNumber v-model:value="form.orderNum">
<template #addonAfter><SettingOutlined /></template> <template #addonAfter><SettingOutlined /></template>

@ -0,0 +1,147 @@
<script setup>
import { h, onMounted, ref, watch } from 'vue';
import { useBaseDataStore } from '@/stores/baseData.js';
import { DeleteTwotone, EditTwotone } from '@vicons/antd';
import { IconPool } from '@/components/AntDesignVue/Icon/index.js';
const baseDataStore = useBaseDataStore();
const props = defineProps({
dataSource: {
type: Array,
required: true,
},
methods: {
type: Object,
required: true,
default: () => ({
handleTableChange: () => {},
}),
},
columnList: {
type: Array,
required: true,
default: () => [],
},
});
const tableColumnObject = {
index: {
title: '序号',
key: 'index',
width: 70, //
customRender: ({ text, record, index, column }) => `${index + 1}`, // 使 + 1
},
dictKey: {
title: '字典标识',
dataIndex: 'dictKey',
key: 'dictKey',
resizable: true,
width: 200
},
dictName: {
title: '字典名称',
dataIndex: 'dictName',
key: 'dictName',
resizable: true,
width: 200
},
dictIcon: {
title: '字典图标',
dataIndex: 'dictIcon',
customRender: ({ text, record, index, column }) => (text ? h(IconPool[text], { style: { fontSize: '20px' } }) : null),
key: 'dictIcon',
},
dictType: {
title: '字典类型',
dataIndex: 'dictType',
key: 'dictType',
customRender: ({ text, record, index, column }) => baseDataStore.state.dictTypeList.find((item) => item.value == text)?.label,
},
serviceKey: {
title: '所属服务',
dataIndex: 'serviceKey',
key: 'serviceKey',
},
haveChildren: {
title: '子项',
dataIndex: 'haveChildren',
key: 'haveChildren',
width: '50px',
},
createtime: {
title: '创建时间',
dataIndex: 'createtime',
key: 'createtime',
width: '140px',
},
createName: {
title: '创建人',
dataIndex: 'createName',
key: 'createName',
},
updatetime: {
title: '更新时间',
dataIndex: 'updatetime',
key: 'updatetime',
width: '140px',
},
updateName: {
title: '更新人',
dataIndex: 'updateName',
key: 'updateName',
},
action: {
title: '操作',
dataIndex: 'action',
key: 'action',
width: 100,
align: 'center',
},
};
const tableColumns = ref([]);
watch(props.columnList, (columnList) => {
makeColumn();
});
onMounted(() => {
window.pino.info('DictTable Mounted');
makeColumn();
});
// !
function makeColumn() {
if (props.columnList && props.columnList.length > 0) {
const existColumn = props.columnList.filter((i) => i.status === true);
if (existColumn.length == 0) {
tableColumns.value = Object.keys(tableColumnObject).map((column) => tableColumnObject[column]);
} else {
tableColumns.value = existColumn.map((column) => tableColumnObject[column.key]);
}
} else {
tableColumns.value = Object.keys(tableColumnObject).map((column) => tableColumnObject[column]);
}
}
function handleResizeColumn(w, col) {
col.width = w;
}
</script>
<template>
<ATable
style="height: 100%"
:dataSource="props.dataSource"
:columns="tableColumns"
@change="props.methods.handleTableChange"
:pagination="false"
sticky
@resizeColumn="handleResizeColumn"
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)"
:scroll="{ scrollToFirstRowOnChange: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column?.key === 'action'"></template>
</template>
</ATable>
</template>
<style scoped></style>

@ -0,0 +1,40 @@
<script setup>
import { useBaseDataStore } from '@/stores/baseData.js';
import {DownOutlined, SmileOutlined} from '@vicons/antd';
import { onMounted, reactive, ref, watch } from 'vue';
import { IconPool } from '@/components/AntDesignVue/Icon/index.js';
const baseDataStore = useBaseDataStore();
// !
const fieldNames = reactive({
children:'children', title:'label', key:'id'
});
//
onMounted(() => {
window.pino.info('DictTree Mounted');
// !
if (baseDataStore.state.dictList?.length == 0) {
baseDataStore.getDictList();
}
});
</script>
<template>
<ATree
:treeData="baseDataStore.state.dictTree"
:show-line="true"
show-icon
:fieldNames="fieldNames"
>
<!-- <template #switcherIcon="{ switcherCls, status }"><down-outlined style="width: 16px;color: red" :class="switcherCls" /></template>-->
<template #icon="all">
<!-- <div v-if="all.label == 'test'">{{all}}</div>-->
<span v-if="!all.meta.dictIcon">N</span>
<component v-else :is="IconPool[all.meta.dictIcon]" />
</template>
<!-- <template #switcherIcon="{ switcherCls }"><down-outlined :class="switcherCls" /></template>-->
</ATree>
</template>
<style scoped></style>

@ -1,9 +1,12 @@
<script setup name="DictPage"> <script setup name="DictPage">
import { reactive } from 'vue'; import { onMounted, reactive, h, watch } from 'vue';
import DictForm from './DictForm.vue'; import DictForm from './DictForm.vue';
import { CoreDict } from '@/api/index.js'; import { CoreDict } from '@/api/index.js';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import DictTable from '@/views/Auth/Dict/DictTable.vue';
import DictTree from '@/views/Auth/Dict/DictTree.vue';
import { BgColorsOutlined } from '@ant-design/icons-vue';
import { IconPool } from '@/components/AntDesignVue/Icon/index.js';
const Message = useMessage(); const Message = useMessage();
// ! // !
@ -22,9 +25,9 @@ const formData = reactive({
// //
dictIcon: '', dictIcon: '',
// //
dictType: '', dictType: '1',
// //
serviceKey: '', serviceKey: undefined,
// //
root: false, root: false,
// //
@ -37,23 +40,94 @@ const formData = reactive({
formData.status = false; formData.status = false;
}, },
}); });
// !
const tableData = reactive({
dataSource: [],
methods: {
handleTableChange: () => {},
},
pageInfo: {
pageSize: 10,
pageNumber: 1,
isList: false,
isAsc: false,
dictInfo: undefined,
dictType: undefined,
root: undefined,
serviceKey: undefined,
status: undefined,
},
total: 0,
tableColumnList: [
{ title: '序号', key: 'index', status: true },
{ title: '字典标识', key: 'dictKey', status: true },
{ title: '字典名称', key: 'dictName', status: true },
{ title: '字典图标', key: 'dictIcon', status: true },
{ title: '字典类型', key: 'dictType', status: true },
{ title: '所属服务', key: 'serviceKey', status: true },
{ title: '子项', key: 'haveChildren', status: true },
{ title: '创建时间', key: 'createtime', status: true },
{ title: '创建人', key: 'createName', status: true },
{ title: '更新时间', key: 'updatetime', status: true },
{ title: '更新人', key: 'updateName', status: true },
{ title: '操作', key: 'action', status: true },
]
});
// todo // todo
// todo // todo
// todo // todo
// ! // !
async function handleCreateAck(data){ async function handleCreateAck(data) {
await CoreDict.createDict(data); await CoreDict.createDict(data);
Message.success('添加字典成功!'); Message.success('添加字典成功!');
formData.status = false;
} }
// const cc = SvgComponentList[0] // !
async function getPage() {
const resd = await CoreDict.getDict(tableData.pageInfo);
tableData.dataSource = resd.rowData;
tableData.total = Number(resd.total);
}
// !
const handlePageChange = (page, pageSize) => {
tableData.pageInfo.pageSize = pageSize;
tableData.pageInfo.pageNumber = page;
getPage();
};
onMounted(() => {
getPage();
});
</script> </script>
<template> <template>
<WorkContainer> <WorkContainer>
<template #header> <template #header>
<ASpace>
<CreateAntdButton name="字典" @click="formData.status = true" /> <CreateAntdButton name="字典" @click="formData.status = true" />
<TableColumChoose v-model:columnList="tableData.tableColumnList"/>
</ASpace>
</template> </template>
<template #main> <template #main>
<div class="dickBody">
<div class="tree">
<DictTree/>
</div>
<div class="table">
<DictTable :methods="tableData.methods" :dataSource="tableData.dataSource" :columnList="tableData.tableColumnList"/>
</div>
</div>
</template>
<template #footer>
<APagination
v-model:current="tableData.pageInfo.pageNumber"
:total="tableData.total"
:pageSize="tableData.pageInfo.pageSize"
:pageSizeOptions="[10, 20, 50, 100, 200]"
:showSizeChanger="true"
:showQuickJumper="true"
:showTotal="(total) => `共 ${total} 条`"
@change="handlePageChange"
/>
</template> </template>
<template #modal> <template #modal>
<AModal v-model:open="formData.status" title="添加字典" :centered="true" :maskClosable="false"> <AModal v-model:open="formData.status" title="添加字典" :centered="true" :maskClosable="false">
@ -64,4 +138,25 @@ async function handleCreateAck(data){
</WorkContainer> </WorkContainer>
</template> </template>
<style scoped></style> <style scoped lang="scss">
.dickBody{
position: relative;
height: 100%;
width: 100%;
display: flex;
& > div.tree{
position: relative;
flex-shrink: 0;
max-height: 100%;
width: 300px;
border-right: 1px solid #cdcdcd;
padding-right: 5px;
}
& > div.table{
position: relative;
flex: 1;
max-height: 100%;
padding-left: 5px;
}
}
</style>

Loading…
Cancel
Save