mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-16 00:38:19 +08:00
Pre Merge pull request !253 from 尹琦/新增表格通用组件、搜索通用组件
This commit is contained in:
commit
9a3ab4cb98
@ -44,7 +44,9 @@
|
|||||||
"vue-json-pretty": "2.6.0",
|
"vue-json-pretty": "2.6.0",
|
||||||
"vue-router": "4.6.3",
|
"vue-router": "4.6.3",
|
||||||
"vue-types": "6.0.0",
|
"vue-types": "6.0.0",
|
||||||
"vxe-table": "4.17.7"
|
"vxe-table": "4.17.7",
|
||||||
|
"@layui/layui-vue": "^2.12.0",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/json": "^2.2.403",
|
"@iconify/json": "^2.2.403",
|
||||||
|
|||||||
@ -15,6 +15,25 @@ export const listDemo = (query?: DemoQuery): AxiosPromise<DemoVO[]> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格查询测试单列表
|
||||||
|
* @param current 当前页
|
||||||
|
* @param size 每页数量
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export const listTableDemo = (current: number, size: number, params: any): AxiosPromise<any> => {
|
||||||
|
return request({
|
||||||
|
url: '/demo/demo/list',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
pageNum:current,
|
||||||
|
pageSize: size,
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询测试单详细
|
* 查询测试单详细
|
||||||
* @param id
|
* @param id
|
||||||
|
|||||||
@ -193,6 +193,7 @@ h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-card__body {
|
.el-card__body {
|
||||||
|
height: 100%;
|
||||||
padding: 15px 20px 20px 20px !important;
|
padding: 15px 20px 20px 20px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
src/components/CloseBox/main.vue
Normal file
29
src/components/CloseBox/main.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<el-button size="small" type="danger" icon="Close" :disabled="disabled" class="el-button-close" @click="close"></el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'el-closebox',
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-button.el-button--small.el-button-close {
|
||||||
|
--el-button-size: 15px;
|
||||||
|
width: var(--el-button-size);
|
||||||
|
height: var(--el-button-size);
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
src/components/FormTable/exSlot.tsx
Normal file
34
src/components/FormTable/exSlot.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ex-slot',
|
||||||
|
props: {
|
||||||
|
value: [String, Number],
|
||||||
|
row: Object,
|
||||||
|
render: Function,
|
||||||
|
rowIndex: Number,
|
||||||
|
column: Object,
|
||||||
|
columnIndex: Number,
|
||||||
|
class: [String, Array, Object],
|
||||||
|
style: [String, Array, Object],
|
||||||
|
dict: [Array]
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const value = this.render({
|
||||||
|
value: this.value,
|
||||||
|
row: this.row,
|
||||||
|
column: this.column,
|
||||||
|
rowIndex: this.rowIndex,
|
||||||
|
columnIndex: this.columnIndex,
|
||||||
|
dict: this.dict
|
||||||
|
});
|
||||||
|
if (this.style || this.class) {
|
||||||
|
return (
|
||||||
|
<div style={this.style} class={this.class}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
507
src/components/FormTable/index.vue
Normal file
507
src/components/FormTable/index.vue
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
<template>
|
||||||
|
<lay-table
|
||||||
|
ref="table"
|
||||||
|
:id="id"
|
||||||
|
:page="page"
|
||||||
|
:size="size"
|
||||||
|
:height="height"
|
||||||
|
:max-height="maxHeight"
|
||||||
|
:loading="loading"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:default-toolbar="defaultToolbar"
|
||||||
|
:resize="resize"
|
||||||
|
:children-column-name="childrenColumnName"
|
||||||
|
:indent-size="indentSize"
|
||||||
|
:even="even"
|
||||||
|
:cell-style="cellStyle"
|
||||||
|
:row-style="rowStyle"
|
||||||
|
:cell-class-name="cellClassName"
|
||||||
|
:row-class-name="rowClassName"
|
||||||
|
:skin="skin"
|
||||||
|
:expand-index="expandIndex"
|
||||||
|
:expand-change="expandChange"
|
||||||
|
:default-expand-all="defaultExpandAll"
|
||||||
|
:span-method="arraySpanMethod"
|
||||||
|
:empty-description="emptyDescription"
|
||||||
|
v-model:expand-keys="expands"
|
||||||
|
v-model:selected-keys="selecteds"
|
||||||
|
v-model:selectedKey="selected"
|
||||||
|
>
|
||||||
|
<template v-if="$slots.toolbar" #toolbar>
|
||||||
|
<slot name="toolbar" />
|
||||||
|
</template>
|
||||||
|
<template v-if="$slots.footer" #footer>
|
||||||
|
<slot name="footer" />
|
||||||
|
</template>
|
||||||
|
<template v-if="$slots.empty" #empty>
|
||||||
|
<slot name="empty" />
|
||||||
|
</template>
|
||||||
|
<template v-if="$slots.expand" #expand="{ data }">
|
||||||
|
<slot name="expand" :data="data" />
|
||||||
|
</template>
|
||||||
|
<template #closeAll>
|
||||||
|
<close-box :disabled="disCloseAll" @close="closeAll" />
|
||||||
|
</template>
|
||||||
|
<template #close="{ row, rowIndex, column, columnIndex }">
|
||||||
|
<close-box :disabled="disClose" @close="close(row, rowIndex, column, columnIndex)" />
|
||||||
|
</template>
|
||||||
|
<template v-for="item in headColumns" #[item.titleSlot]>
|
||||||
|
<slot :name="item.titleSlot" :column="item">
|
||||||
|
<ex-slot v-if="item.renderHead" :render="item.renderHead" :column="item" :style="item.headStyle" :class="item.headClass" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template v-for="item in customColumns" #[item.customSlot]="{ row, rowIndex, column, columnIndex }">
|
||||||
|
<slot :name="item.customSlot" :row="row" :row-index="rowIndex" :column="column" :column-index="columnIndex" :value="getValue(column, row)">
|
||||||
|
<ex-slot
|
||||||
|
v-if="item.render"
|
||||||
|
:style="isFunc(item.style, { row, rowIndex, column, columnIndex })"
|
||||||
|
:class="isFunc(item.class, { row, rowIndex, column, columnIndex })"
|
||||||
|
:render="item.render"
|
||||||
|
:row="row"
|
||||||
|
:row-index="rowIndex"
|
||||||
|
:dict="dictStore.getDict(column.dict)"
|
||||||
|
:column="item"
|
||||||
|
:column-index="columnIndex"
|
||||||
|
:value="getValue(column, row)"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<el-tooltip
|
||||||
|
popper-style="z-index: 9999 !important;"
|
||||||
|
v-if="item.showOverflowTooltip"
|
||||||
|
:content="getValue(column, row)"
|
||||||
|
placement="top"
|
||||||
|
:effect="isFunc(item.tooltipEffect, { row, rowIndex, column, columnIndex }) || 'dark'"
|
||||||
|
:disabled="!row.showTooltip"
|
||||||
|
>
|
||||||
|
<el-text
|
||||||
|
:type="isFunc(item.color, { row, rowIndex, column, columnIndex })"
|
||||||
|
:size="isFunc(item.size, { row, rowIndex, column, columnIndex }) || elSize"
|
||||||
|
:class="isFunc(item.class, { row, rowIndex, column, columnIndex })"
|
||||||
|
:style="isFunc(item.style, { row, rowIndex, column, columnIndex })"
|
||||||
|
:line-clamp="isFunc(item.lines, { row, rowIndex, column, columnIndex }) || 1"
|
||||||
|
@mouseenter="showTips($event, row, column)"
|
||||||
|
@click="typeof item.click == 'function' && item.click({ row, rowIndex, column, columnIndex })"
|
||||||
|
>{{ getValue(column, row) }}</el-text
|
||||||
|
>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-text
|
||||||
|
v-else
|
||||||
|
:type="isFunc(item.color, { row, rowIndex, column, columnIndex })"
|
||||||
|
:size="isFunc(item.size, { row, rowIndex, column, columnIndex }) || elSize"
|
||||||
|
:class="isFunc(item.class, { row, rowIndex, column, columnIndex })"
|
||||||
|
:style="isFunc(item.style, { row, rowIndex, column, columnIndex })"
|
||||||
|
@click="typeof item.click == 'function' && item.click({ row, rowIndex, column, columnIndex })"
|
||||||
|
>{{ getValue(column, row) }}</el-text
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</lay-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createApp, isVNode } from 'vue';
|
||||||
|
import { renderToString } from '@vue/server-renderer';
|
||||||
|
import { getProperty, getDataFromTreeByKey } from '@/utils/common';
|
||||||
|
import CloseBox from '@/components/CloseBox/main.vue';
|
||||||
|
import exSlot from './exSlot';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
|
import { getDicts } from '@/api/system/dict/data';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'form-table',
|
||||||
|
components: {
|
||||||
|
'ex-slot': exSlot,
|
||||||
|
'close-box': CloseBox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
//数据主键
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
//分页配置
|
||||||
|
page: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
//树表-指定树形结构的列名
|
||||||
|
childrenColumnName: {
|
||||||
|
type: String,
|
||||||
|
default: 'children'
|
||||||
|
},
|
||||||
|
//表格高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '300px'
|
||||||
|
},
|
||||||
|
//表格最高高度
|
||||||
|
maxHeight: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//加载状态
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//表头
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//表格数据
|
||||||
|
dataSource: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//是否启用默认工具栏
|
||||||
|
defaultToolbar: {
|
||||||
|
type: [Boolean, Array],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//开启列宽拉伸
|
||||||
|
resize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//树表-指定树形结构的缩进距离
|
||||||
|
indentSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 30
|
||||||
|
},
|
||||||
|
//开启斑马条纹
|
||||||
|
even: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//单元格样式
|
||||||
|
cellStyle: Function,
|
||||||
|
//表格行样式
|
||||||
|
rowStyle: Function,
|
||||||
|
//单元格类名
|
||||||
|
cellClassName: Function,
|
||||||
|
//表格行类名
|
||||||
|
rowClassName: Function,
|
||||||
|
//指定风格 line、row、nob
|
||||||
|
skin: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//指定展开操作所在列的索引
|
||||||
|
expandIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//树表-展开行变化时触发
|
||||||
|
expandChange: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
//树表-是否默认展开所有
|
||||||
|
defaultExpandAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//自定义合并单元格函数
|
||||||
|
spanMethods: Function,
|
||||||
|
//无数据文字展示
|
||||||
|
emptyDescription: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//展开列key
|
||||||
|
expandKeys: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//多选选中key
|
||||||
|
selectedKeys: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//单选选中key
|
||||||
|
selectedKey: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//禁止全删
|
||||||
|
disCloseAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 禁止删除
|
||||||
|
disClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//合计值
|
||||||
|
totalRowMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expands: [],
|
||||||
|
selecteds: [],
|
||||||
|
selected: '',
|
||||||
|
tableColumns: [],
|
||||||
|
headColumns: [],
|
||||||
|
customColumns: [],
|
||||||
|
dicts: [],
|
||||||
|
dict: {},
|
||||||
|
appStore: useAppStore(), // 引入 appStore
|
||||||
|
size: 'sm',
|
||||||
|
dictStore: useDictStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
//layui大小转el大小
|
||||||
|
// eslint-disable-next-line vue/return-in-computed-property
|
||||||
|
elSize() {
|
||||||
|
if (this.size == 'sm') {
|
||||||
|
return 'small';
|
||||||
|
}
|
||||||
|
if (this.size == 'md') {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
if (this.size == 'lg') {
|
||||||
|
return 'large';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
expands(val) {
|
||||||
|
this.expandKeys != val && this.$emit('update:expandKeys', val);
|
||||||
|
},
|
||||||
|
selecteds(val) {
|
||||||
|
this.selectedKeys != val && this.$emit('update:selectedKeys', val);
|
||||||
|
},
|
||||||
|
selected(val) {
|
||||||
|
this.selectedKey != val && this.$emit('update:selectedKey', val);
|
||||||
|
},
|
||||||
|
expandKeys(val) {
|
||||||
|
if (this.expands != val || this.expands.length != val.length) {
|
||||||
|
this.expands = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedKeys(val) {
|
||||||
|
if (this.selecteds != val || this.selecteds.length != val.length) {
|
||||||
|
this.selecteds = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedKey(val) {
|
||||||
|
if (this.selected != val) {
|
||||||
|
this.selected = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataSource() {
|
||||||
|
this.totalRow();
|
||||||
|
},
|
||||||
|
'appStore.size'(newSize, oldSize) {
|
||||||
|
// 在这里处理 size 变化后的逻辑
|
||||||
|
if (newSize == 'small') {
|
||||||
|
this.size = 'sm';
|
||||||
|
} else if (newSize == 'default') {
|
||||||
|
this.size = 'md';
|
||||||
|
} else if (newSize == 'large') {
|
||||||
|
this.size = 'lg';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
this.expands = this.expandKeys;
|
||||||
|
this.selecteds = this.selectedKeys;
|
||||||
|
this.selected = this.selectedKey;
|
||||||
|
this.tableColumns = this.initColumns();
|
||||||
|
// 获取字典数据
|
||||||
|
this.renderDict(this.dicts);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
renderDict(dicts) {
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
const dictTypes = [].concat(dicts);
|
||||||
|
Promise.all(
|
||||||
|
dictTypes.map(
|
||||||
|
(type) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
if (dictStore.getDict(type)) {
|
||||||
|
resolve(dictStore.getDict(type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDicts(type)
|
||||||
|
.then((res) => {
|
||||||
|
const dictData = res.data.map((item) => ({
|
||||||
|
label: item.dictLabel,
|
||||||
|
value: item.dictValue
|
||||||
|
}));
|
||||||
|
dictStore.setDict(type, dictData);
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err) => reject(err));
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.dict = dictStore.dict;
|
||||||
|
},
|
||||||
|
//初始化表头
|
||||||
|
initColumns(columns = this.columns) {
|
||||||
|
return columns.map((item) => {
|
||||||
|
if (item.children) {
|
||||||
|
item.children = this.initColumns(item.children);
|
||||||
|
}
|
||||||
|
if (item.type) {
|
||||||
|
if (item.type == 'close') {
|
||||||
|
const closeColumn = {
|
||||||
|
key: 'close',
|
||||||
|
customSlot: 'close',
|
||||||
|
titleSlot: 'closeAll',
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
item.noCloseAll && delete closeColumn.titleSlot;
|
||||||
|
delete closeColumn.type;
|
||||||
|
return closeColumn;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
item.customSlot = item.customSlot || item.key;
|
||||||
|
if (item.renderHead || item.titleSlot) {
|
||||||
|
item.titleSlot = item.titleSlot || `${item.key}Title`;
|
||||||
|
this.headColumns.push(item);
|
||||||
|
}
|
||||||
|
item.dict && this.dicts.push(item.dict);
|
||||||
|
this.customColumns.push(item);
|
||||||
|
return JSON.parse(JSON.stringify(item));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//全关事件
|
||||||
|
closeAll() {
|
||||||
|
this.$emit('closeAll');
|
||||||
|
},
|
||||||
|
//关闭事件
|
||||||
|
close(row, rowIndex, column, columnIndex) {
|
||||||
|
this.$emit('close', { row, rowIndex, column, columnIndex });
|
||||||
|
},
|
||||||
|
//合并单元格
|
||||||
|
arraySpanMethod(row, column, rowIndex, columnIndex) {
|
||||||
|
//包含自定义合并,走自定义合并
|
||||||
|
if (this.spanMethod) {
|
||||||
|
const value = this.spanMethod(row, column, rowIndex, columnIndex);
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
//按表头merge字段进行合并
|
||||||
|
let rowspan = 1,
|
||||||
|
colspan = 1;
|
||||||
|
if (column.merge) {
|
||||||
|
//merge为true,默认根据当前列的key值为合并键值
|
||||||
|
const merge = typeof column.merge == 'boolean' ? column.key : column.merge;
|
||||||
|
//兼容单字段合并和多字段合并
|
||||||
|
const mergeKeys = [].concat(merge);
|
||||||
|
//判断是否满足合并条件
|
||||||
|
function isMerge(data) {
|
||||||
|
let isMerge = true;
|
||||||
|
mergeKeys.forEach((key) => (isMerge = isMerge && getProperty(row, key) == getProperty(data, key)));
|
||||||
|
return isMerge;
|
||||||
|
}
|
||||||
|
//如果前一条数据与本条数据满足合并条件,直接返回[0, 0]
|
||||||
|
if (isMerge(this.dataSource[rowIndex - 1])) {
|
||||||
|
rowspan = 0;
|
||||||
|
colspan = 0;
|
||||||
|
} else {
|
||||||
|
//遍历本条数据之后的表格数据
|
||||||
|
for (let index = rowIndex + 1; index < this.dataSource.length; index++) {
|
||||||
|
const data = this.dataSource[index];
|
||||||
|
if (isMerge(data)) {
|
||||||
|
//满足合并条件,该条数据跨行数加1
|
||||||
|
rowspan += 1;
|
||||||
|
} else {
|
||||||
|
//不满足合并条件,打断循环
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//返回row数据的跨行数,跨列数
|
||||||
|
return [rowspan, colspan];
|
||||||
|
},
|
||||||
|
//获取选中数据
|
||||||
|
getCheckData() {
|
||||||
|
return this.$refs.table.getCheckData();
|
||||||
|
},
|
||||||
|
//加载结果
|
||||||
|
getValue(column, row) {
|
||||||
|
const value = getProperty(row, column.key);
|
||||||
|
if (column.dict) {
|
||||||
|
// return this.typeFormat(this.dict[column.dict], value) || value;
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
const dictData = dictStore.getDict(column.dict);
|
||||||
|
if (dictData) {
|
||||||
|
const dictItem = dictData.find((item) => item.value == value);
|
||||||
|
return dictItem ? dictItem.label : value || '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
//tooltips显示
|
||||||
|
showTips(obj, row, column) {
|
||||||
|
const lines = column.lines || 1;
|
||||||
|
let cellHeight = this.size === 'lg' ? 25 : 20;
|
||||||
|
cellHeight *= lines;
|
||||||
|
const scrollHeight = obj.target.scrollHeight;
|
||||||
|
row.showTooltip = scrollHeight > cellHeight;
|
||||||
|
},
|
||||||
|
//合计
|
||||||
|
totalRow(columns = this.columns) {
|
||||||
|
columns.forEach(async (item) => {
|
||||||
|
if (item.children) {
|
||||||
|
this.totalRow(item.children);
|
||||||
|
}
|
||||||
|
if (item.totalRow) {
|
||||||
|
if (typeof item.totalRow === 'string') return;
|
||||||
|
const column = getDataFromTreeByKey(this.tableColumns, item.key, 'key');
|
||||||
|
let pageTotal = 0;
|
||||||
|
this.dataSource.forEach((data) => {
|
||||||
|
const value = getProperty(data, column.key);
|
||||||
|
if (!isNaN(value)) pageTotal += value * 1;
|
||||||
|
});
|
||||||
|
const totalData = getProperty(this.totalRowMap, item.key) || pageTotal;
|
||||||
|
if (typeof item.totalRow === 'boolean') {
|
||||||
|
column.totalRow = totalData || pageTotal;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof item.totalRow === 'function') {
|
||||||
|
const value = item.totalRow({ value: totalData, pageTotal, column });
|
||||||
|
if (isVNode(value)) {
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
column.totalRow = await renderToString(app);
|
||||||
|
} else {
|
||||||
|
column.totalRow = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//是否是函数
|
||||||
|
isFunc(result, data) {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
const value = this.getValue(data.column, data.row);
|
||||||
|
return result({ value, ...data }) || null;
|
||||||
|
}
|
||||||
|
return result || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.layui-table-cell-content) {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
213
src/components/SearchForm/formElem.vue
Normal file
213
src/components/SearchForm/formElem.vue
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<el-input
|
||||||
|
v-if="['text', 'number', 'textarea'].includes(config.type)"
|
||||||
|
v-model="value"
|
||||||
|
:type="config.type"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:placeholder="config.placeholder || '请输入'"
|
||||||
|
:readonly="config.readonly"
|
||||||
|
:suffix-icon="typeof config.suffixIcon == 'string' ? config.suffixIcon : ''"
|
||||||
|
:prefix-icon="typeof config.prefixIcon == 'string' ? config.prefixIcon : ''"
|
||||||
|
:disabled="config.disabled"
|
||||||
|
:rows="config.rows || 2"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
@click="($event) => typeof config.click == 'function' && config.click(value, $event)"
|
||||||
|
@mousewheel.prevent
|
||||||
|
@keydown.up.prevent
|
||||||
|
@keydown.down.prevent
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<ex-slot v-if="typeof config.suffixIcon == 'function'" :render="config.suffixIcon" :value="value" />
|
||||||
|
</template>
|
||||||
|
<template #prefix>
|
||||||
|
<ex-slot v-if="typeof config.prefixIcon == 'function'" :render="config.prefixIcon" :value="value" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-select
|
||||||
|
ref="select"
|
||||||
|
v-else-if="config.type == 'select'"
|
||||||
|
v-model="value"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:placeholder="config.placeholder || '请选择'"
|
||||||
|
:multiple="config.multiple"
|
||||||
|
:filterable="config.filterable"
|
||||||
|
:disabled="config.disabled"
|
||||||
|
:loading="config.loading"
|
||||||
|
:loading-text="config.loadingText"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
:teleported="false"
|
||||||
|
:fit-input-width="config.fitInputWidth || false"
|
||||||
|
:collapse-tags="config.collapseTags"
|
||||||
|
:collapse-tags-tooltip="config.collapseTagsTooltip"
|
||||||
|
:max-collapse-tags="config.maxCollapseTags"
|
||||||
|
@change="($event) => typeof config.change == 'function' && config.change($event, $refs.select)"
|
||||||
|
@visible-change="($event) => typeof config.visibleChange == 'function' && config.visibleChange($event, $refs.select)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in options"
|
||||||
|
:key="index"
|
||||||
|
:value="config.props?.value ? item[config.props?.value] : item"
|
||||||
|
:label="config.props?.label ? item[config.props?.label] : item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-tree-select
|
||||||
|
v-else-if="config.type == 'treeselect'"
|
||||||
|
v-model="value"
|
||||||
|
:node-key="config.nodeKey || 'id'"
|
||||||
|
:data="config.options"
|
||||||
|
:props="config.props"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:placeholder="config.placeholder || '请选择'"
|
||||||
|
:multiple="config.multiple"
|
||||||
|
:filterable="config.filterable"
|
||||||
|
:disabled="config.disabled"
|
||||||
|
:check-strictly="config.checkStrictly"
|
||||||
|
:show-checkbox="config.showCheckbox"
|
||||||
|
:check-on-click-node="config.checkOnClickNode"
|
||||||
|
:loading="config.loading"
|
||||||
|
:loading-text="config.loadingText"
|
||||||
|
:highlight-current="config.highlightCurrent"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
@change="($event) => typeof config.change == 'function' && config.change($event)"
|
||||||
|
/>
|
||||||
|
<el-date-picker
|
||||||
|
v-else-if="['year', 'month', 'date', 'dates', 'datetime', 'week', 'datetimerange', 'daterange', 'monthrange'].includes(config.type)"
|
||||||
|
v-model="value"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:editable="config.editable"
|
||||||
|
:type="config.type"
|
||||||
|
:placeholder="config.placeholder || '请选择'"
|
||||||
|
:range-separator="config.rangeSeparator"
|
||||||
|
:start-placeholder="config.startPlaceholder || '开始时间'"
|
||||||
|
:end-placeholder="config.endPlaceholder || '结束时间'"
|
||||||
|
:default-time="config.defaultTime"
|
||||||
|
:default-value="config.defaultValue"
|
||||||
|
:value-format="config.valueFormat"
|
||||||
|
:disabled="config.disabled"
|
||||||
|
:shortcuts="config.shortcuts"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
@change="($event) => typeof config.change == 'function' && config.change($event)"
|
||||||
|
/>
|
||||||
|
<el-time-select
|
||||||
|
v-else-if="config.type == 'timeselect'"
|
||||||
|
v-model="value"
|
||||||
|
:type="config.type"
|
||||||
|
:start="config.start || '00:00'"
|
||||||
|
:step="config.step || '00:01'"
|
||||||
|
:end="config.start || '23:59'"
|
||||||
|
:placeholder="config.placeholder || '请选择'"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
/>
|
||||||
|
<el-time-picker
|
||||||
|
v-else-if="config.type == 'timepicker'"
|
||||||
|
v-model="value"
|
||||||
|
:editable="config.editable"
|
||||||
|
:is-range="config.isRange"
|
||||||
|
:placeholder="config.placeholder || '请选择'"
|
||||||
|
:start-placeholder="config.startPlaceholder || '开始时间'"
|
||||||
|
:end-placeholder="config.endPlaceholder || '结束时间'"
|
||||||
|
:range-separator="config.rangeSeparator"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:class="config.class"
|
||||||
|
:style="config.style"
|
||||||
|
/>
|
||||||
|
<el-autocomplete
|
||||||
|
v-else-if="config.type == 'autocomplete'"
|
||||||
|
v-model="value"
|
||||||
|
:disabled="config.disabled"
|
||||||
|
:value-key="config.valueKey || 'value'"
|
||||||
|
:debounce="config.debounce || 300"
|
||||||
|
:placement="config.placement || 'bottom-start'"
|
||||||
|
:fetch-suggestions="config.fetchSuggestions"
|
||||||
|
:clearable="config.clearable"
|
||||||
|
:placeholder="config.placeholder || '请输入'"
|
||||||
|
@select="($event) => typeof config.select == 'function' && config.select($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import exSlot from '@/components/FormTable/exSlot';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
|
export default {
|
||||||
|
name: 'form-elem',
|
||||||
|
components: {
|
||||||
|
exSlot
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: Object,
|
||||||
|
start: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dictOptions: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
options() {
|
||||||
|
return this.config.options || this.dictOptions;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.requestApi();
|
||||||
|
this.loadDict();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
requestApi() {
|
||||||
|
if (this.config.request) {
|
||||||
|
request({
|
||||||
|
url: this.config.request.url,
|
||||||
|
method: this.config.request.method,
|
||||||
|
params: this.config.request.params
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data.success) {
|
||||||
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
|
this.config.options = res.data.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadDict() {
|
||||||
|
if (this.config.dict) {
|
||||||
|
const dictStore = useDictStore(); // 获取字典存储实例
|
||||||
|
let dictData = dictStore.getDict(this.config.dict); // 从缓存中获取字典数据
|
||||||
|
if (!dictData) {
|
||||||
|
// 如果缓存中没有字典数据,则请求后存储
|
||||||
|
const response = await request({
|
||||||
|
url: `/system/dict/data/type/${this.config.dict}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
dictData = response.data.map((item) => ({
|
||||||
|
label: item.dictLabel,
|
||||||
|
value: item.dictValue
|
||||||
|
}));
|
||||||
|
dictStore.setDict(this.config.dict, dictData); // 存储到字典缓存
|
||||||
|
}
|
||||||
|
this.dictOptions = dictData; // 更新组件的字典选项
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(input::-webkit-outer-spin-button),
|
||||||
|
:deep(input::-webkit-inner-spin-button) {
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
}
|
||||||
|
:deep(input[type='number']) {
|
||||||
|
-moz-appearance: textfield !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
185
src/components/SearchForm/index.vue
Normal file
185
src/components/SearchForm/index.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-100" ref="wrap">
|
||||||
|
<el-form :model="model" @submit.prevent @keyup.enter="search" inline>
|
||||||
|
<el-form-item
|
||||||
|
v-for="(item, index) in config"
|
||||||
|
:key="index"
|
||||||
|
:ref="item.prop || `${item.start}_${item.end}`"
|
||||||
|
:label="`${item.label}:`"
|
||||||
|
label-width="auto"
|
||||||
|
>
|
||||||
|
<form-elem v-model="model[item.prop]" :config="item" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item ref="search" style="margin-right: 0px">
|
||||||
|
<template v-if="isSearch">
|
||||||
|
<el-button type="primary" icon="Search" @click="search">搜索</el-button>
|
||||||
|
<el-button type="warning" icon="Refresh" v-if="isReset" @click="reset">重置</el-button>
|
||||||
|
</template>
|
||||||
|
<slot />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template v-if="suffixConfig.length > 0">
|
||||||
|
<el-form v-if="visible" :model="model" @submit.prevent @keyup.enter="search" inline>
|
||||||
|
<el-form-item
|
||||||
|
v-for="(item, index) in suffixConfig"
|
||||||
|
:key="index"
|
||||||
|
:ref="item.prop || `${item.start}_${item.end}`"
|
||||||
|
:label="`${item.label}:`"
|
||||||
|
label-width="auto"
|
||||||
|
>
|
||||||
|
<form-elem v-model="model[item.prop]" :config="item" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="cursor-pointer" @click="showMore">
|
||||||
|
<el-divider>
|
||||||
|
{{ visible ? '折叠' : '更多' }}
|
||||||
|
<el-icon>
|
||||||
|
<ArrowUp v-if="visible" />
|
||||||
|
<ArrowDown v-else />
|
||||||
|
</el-icon>
|
||||||
|
</el-divider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { validatenull } from '@/utils/validate';
|
||||||
|
import formElem from './formElem.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'search-form',
|
||||||
|
components: {
|
||||||
|
'form-elem': formElem
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
//表单值
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
//表单显示配置
|
||||||
|
configs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//是否显示重置按钮
|
||||||
|
isReset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否显示搜索按钮
|
||||||
|
isSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
prefixConfig: [],
|
||||||
|
suffixConfig: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
config() {
|
||||||
|
return this.prefixConfig.length > 0 ? this.prefixConfig : this.configs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
configs: 'resize'
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
const dicts = [];
|
||||||
|
this.configs.forEach((item) => item.dict && !dicts.includes(item.dict) && dicts.push(item.dict));
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMore() {
|
||||||
|
this.visible = !this.visible;
|
||||||
|
this.$emit('toggle', this.visible);
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
this.prefixConfig = [];
|
||||||
|
this.suffixConfig = [];
|
||||||
|
const wrapWidth = this.$refs.wrap.offsetWidth;
|
||||||
|
const btnWidth = this.$refs.search?.$el.offsetWidth || 0;
|
||||||
|
const formWidth = wrapWidth - btnWidth - 1;
|
||||||
|
let width = 0;
|
||||||
|
for (let index = 0; index < this.configs.length; index++) {
|
||||||
|
const item = this.configs[index];
|
||||||
|
const prop = item.prop || `${item.start}_${item.end}`;
|
||||||
|
this.initialize(item);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (validatenull(item.width) && this.$refs[prop]) {
|
||||||
|
const elem = this.$refs[prop][0].$el;
|
||||||
|
item.width = elem.offsetWidth + 32;
|
||||||
|
}
|
||||||
|
width += item.width;
|
||||||
|
if (index == 0 && item.width > formWidth) {
|
||||||
|
this.prefixConfig.push(item);
|
||||||
|
} else {
|
||||||
|
width > formWidth ? this.suffixConfig.push(item) : this.prefixConfig.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.$nextTick(() => this.$emit('resize'));
|
||||||
|
},
|
||||||
|
initialize(item) {
|
||||||
|
if (!('type' in item)) item.type = 'text';
|
||||||
|
if (!('clearable' in item)) item.clearable = true;
|
||||||
|
if (!('props' in item) && item.dict) {
|
||||||
|
item.props = {
|
||||||
|
value: 'value',
|
||||||
|
label: 'label'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!('placeholder' in item)) {
|
||||||
|
if (['text', 'number', 'autocomplete'].includes(item.type)) {
|
||||||
|
item.placeholder = `请输入${item.label}`;
|
||||||
|
} else if (['select', 'treeselect', 'year', 'month', 'week', 'date', 'datetime'].includes(item.type)) {
|
||||||
|
item.placeholder = `请选择${item.label}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (['select', 'treeselect'].includes(item.type)) {
|
||||||
|
if (item.loading && !('loadingText' in item)) item.loadingText = '加载中...';
|
||||||
|
if (item.type == 'treeselect') {
|
||||||
|
if (!('checkOnClickNode' in item)) item.checkOnClickNode = true;
|
||||||
|
if (!('checkStrictly' in item)) item.checkStrictly = true;
|
||||||
|
if (item.multiple && !('showCheckbox' in item)) item.showCheckbox = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (['datetimerange', 'daterange', 'monthrange'].includes(item.type)) {
|
||||||
|
if (!('startPlaceholder' in item)) {
|
||||||
|
item.startPlaceholder = '开始时间';
|
||||||
|
}
|
||||||
|
if (!('endPlaceholder' in item)) {
|
||||||
|
item.endPlaceholder = '结束时间';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search() {
|
||||||
|
this.$emit('search');
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.$emit('reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-form-item--small) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.el-divider--horizontal {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
95
src/components/SearchPanel/index.vue
Normal file
95
src/components/SearchPanel/index.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="isCard" style="height: 100%">
|
||||||
|
<search-form
|
||||||
|
ref="search"
|
||||||
|
:configs="configs"
|
||||||
|
:model="model"
|
||||||
|
:is-reset="isReset"
|
||||||
|
:is-search="isSearch"
|
||||||
|
@search="search"
|
||||||
|
@reset="reset"
|
||||||
|
@toggle="toggle"
|
||||||
|
@resize="toggle"
|
||||||
|
>
|
||||||
|
<slot name="button" />
|
||||||
|
</search-form>
|
||||||
|
<div ref="wrap">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else style="height: 100%">
|
||||||
|
<search-form
|
||||||
|
ref="search"
|
||||||
|
:configs="configs"
|
||||||
|
:model="model"
|
||||||
|
:is-reset="isReset"
|
||||||
|
:is-search="isSearch"
|
||||||
|
@search="search"
|
||||||
|
@reset="reset"
|
||||||
|
@toggle="toggle"
|
||||||
|
@resize="toggle"
|
||||||
|
/>
|
||||||
|
<div ref="wrap">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SearchForm from '@/components/SearchForm/index.vue';
|
||||||
|
export default {
|
||||||
|
name: 'search-panel',
|
||||||
|
components: {
|
||||||
|
'search-form': SearchForm
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
//表单值
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
//表单显示配置
|
||||||
|
configs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
//是否显示重置按钮
|
||||||
|
isReset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否显示搜索按钮
|
||||||
|
isSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否卡片展示
|
||||||
|
isCard: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.toggle();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search() {
|
||||||
|
this.$emit('search');
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.$emit('reset');
|
||||||
|
},
|
||||||
|
toggle() {
|
||||||
|
if (this.$refs.search) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const elem = this.$refs.search.$el;
|
||||||
|
const offsetHeight = elem.offsetHeight;
|
||||||
|
this.$refs.wrap.style.height = `calc(100% - ${offsetHeight}px)`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
90
src/components/ToolbarButton/index.vue
Normal file
90
src/components/ToolbarButton/index.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<lay-button-container>
|
||||||
|
<slot name="prefix"></slot>
|
||||||
|
<el-button v-if="showAdd" v-hasPermi="[`${permission}:${permissionKey}:add`]" type="primary" :disabled="disAdd" icon="Plus" @click="add" plain>{{
|
||||||
|
addName
|
||||||
|
}}</el-button>
|
||||||
|
<slot name="middle"></slot>
|
||||||
|
<el-button v-if="showEdit" v-hasPermi="[`${permission}:${permissionKey}:edit`]" type="success" :disabled="disEdit" icon="Edit" @click="edit" plain>{{
|
||||||
|
editName
|
||||||
|
}}</el-button>
|
||||||
|
<slot name="center"></slot>
|
||||||
|
<el-button v-if="showDel" v-hasPermi="[`${permission}:${permissionKey}:del`]" type="danger" :disabled="disRemove" icon="Delete" @click="remove" plain>{{
|
||||||
|
delName
|
||||||
|
}}</el-button>
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</lay-button-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 定义 props
|
||||||
|
defineProps({
|
||||||
|
// 权限key
|
||||||
|
permission: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
permissionKey: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
disAdd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disRemove: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showAdd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showDel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
addName: {
|
||||||
|
type: String,
|
||||||
|
default: '新增'
|
||||||
|
},
|
||||||
|
editName: {
|
||||||
|
type: String,
|
||||||
|
default: '修改'
|
||||||
|
},
|
||||||
|
delName: {
|
||||||
|
type: String,
|
||||||
|
default: '删除'
|
||||||
|
},
|
||||||
|
permissionList: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义 emits
|
||||||
|
const emit = defineEmits(['add', 'edit', 'remove']);
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const add = () => {
|
||||||
|
emit('add');
|
||||||
|
};
|
||||||
|
|
||||||
|
const edit = () => {
|
||||||
|
emit('edit');
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
emit('remove');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@ -81,5 +81,74 @@ export default {
|
|||||||
layoutSetting: 'Layout Setting',
|
layoutSetting: 'Layout Setting',
|
||||||
personalCenter: 'Personal Center',
|
personalCenter: 'Personal Center',
|
||||||
logout: 'Logout'
|
logout: 'Logout'
|
||||||
|
},
|
||||||
|
// layui-vue国际化
|
||||||
|
input: {
|
||||||
|
placeholder: 'please input'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
previous: 'previous',
|
||||||
|
next: 'next',
|
||||||
|
goTo: 'Go to',
|
||||||
|
confirm: 'confirm',
|
||||||
|
page: 'page',
|
||||||
|
item: 'item',
|
||||||
|
total: 'total'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
filter: 'filter',
|
||||||
|
export: 'export',
|
||||||
|
print: 'print'
|
||||||
|
},
|
||||||
|
datePicker: {
|
||||||
|
year: '',
|
||||||
|
month: 'month',
|
||||||
|
sunday: 'SU',
|
||||||
|
monday: 'MO',
|
||||||
|
tuesday: 'TU',
|
||||||
|
wednesday: 'WE',
|
||||||
|
thursday: 'TH',
|
||||||
|
friday: 'FR',
|
||||||
|
saturday: 'SA',
|
||||||
|
january: 'January',
|
||||||
|
february: 'February',
|
||||||
|
march: 'March',
|
||||||
|
april: 'April',
|
||||||
|
may: 'May',
|
||||||
|
june: 'June',
|
||||||
|
july: 'July',
|
||||||
|
august: 'August',
|
||||||
|
september: 'September',
|
||||||
|
october: 'October',
|
||||||
|
november: 'November',
|
||||||
|
december: 'December',
|
||||||
|
selectDate: 'select date',
|
||||||
|
selectTime: 'select time',
|
||||||
|
selectYear: 'select year',
|
||||||
|
selectMonth: 'select month',
|
||||||
|
confirm: 'confirm',
|
||||||
|
cancel: 'cancel',
|
||||||
|
now: 'now',
|
||||||
|
startTime: 'start time',
|
||||||
|
endTime: 'end time'
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
description: 'No data'
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
text: 'Upload files',
|
||||||
|
dragText: 'Click Upload or drag the file here',
|
||||||
|
defaultErrorMsg: 'Upload failed',
|
||||||
|
urlErrorMsg: 'The upload address format is illegal',
|
||||||
|
numberErrorMsg: 'The number of files uploaded exceeds the specified number',
|
||||||
|
cutInitErrorMsg: 'Clipping plug-in initialization failed',
|
||||||
|
uploadSuccess: 'Upload succeeded',
|
||||||
|
cannotSupportCutMsg:
|
||||||
|
'The current version does not support single multiple file clipping. Try to set multiple to false, and get the returned file object through @ done',
|
||||||
|
occurFileSizeErrorMsg: 'File size warning,The maximum file size cannot exceed target KB',
|
||||||
|
startUploadMsg: 'Upload Start',
|
||||||
|
confirmBtn: 'confirm',
|
||||||
|
cancelBtn: 'cancel',
|
||||||
|
title: 'title'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -81,5 +81,73 @@ export default {
|
|||||||
layoutSetting: '布局设置',
|
layoutSetting: '布局设置',
|
||||||
personalCenter: '个人中心',
|
personalCenter: '个人中心',
|
||||||
logout: '退出登录'
|
logout: '退出登录'
|
||||||
|
},
|
||||||
|
// layui-vue国际化
|
||||||
|
input: {
|
||||||
|
placeholder: '请输入'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
previous: '上一页',
|
||||||
|
next: '下一页',
|
||||||
|
goTo: '到第',
|
||||||
|
confirm: '确认',
|
||||||
|
page: '页',
|
||||||
|
item: '条',
|
||||||
|
total: '共'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
filter: '筛选',
|
||||||
|
export: '导出',
|
||||||
|
print: '打印'
|
||||||
|
},
|
||||||
|
datePicker: {
|
||||||
|
year: '年',
|
||||||
|
month: '月',
|
||||||
|
sunday: '日',
|
||||||
|
monday: '一',
|
||||||
|
tuesday: '二',
|
||||||
|
wednesday: '三',
|
||||||
|
thursday: '四',
|
||||||
|
friday: '五',
|
||||||
|
saturday: '六',
|
||||||
|
january: '1月',
|
||||||
|
february: '2月',
|
||||||
|
march: '3月',
|
||||||
|
april: '4月',
|
||||||
|
may: '5月',
|
||||||
|
june: '6月',
|
||||||
|
july: '7月',
|
||||||
|
august: '8月',
|
||||||
|
september: '9月',
|
||||||
|
october: '10月',
|
||||||
|
november: '11月',
|
||||||
|
december: '12月',
|
||||||
|
selectDate: '选择日期',
|
||||||
|
selectTime: '选择时间',
|
||||||
|
selectYear: '选择年份',
|
||||||
|
selectMonth: '选择月份',
|
||||||
|
confirm: '确认',
|
||||||
|
cancel: '取消',
|
||||||
|
now: '现在',
|
||||||
|
startTime: '开始时间',
|
||||||
|
endTime: '结束时间'
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
description: '无数据'
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
text: '上传文件',
|
||||||
|
dragText: '点击上传,或将文件拖拽到此处',
|
||||||
|
defaultErrorMsg: '上传失败',
|
||||||
|
urlErrorMsg: '上传地址格式不合法',
|
||||||
|
numberErrorMsg: '文件上传超过规定的个数',
|
||||||
|
cutInitErrorMsg: '剪裁插件初始化失败',
|
||||||
|
uploadSuccess: '上传成功',
|
||||||
|
cannotSupportCutMsg: '当前版本暂不支持单次多文件剪裁,尝试设置 multiple 为 false, 通过 @done 获取返回文件对象',
|
||||||
|
occurFileSizeErrorMsg: '文件大小超过限制,文件最大不可超过传入的指定size属性的KB数',
|
||||||
|
startUploadMsg: '开始上传',
|
||||||
|
confirmBtn: '确认',
|
||||||
|
cancelBtn: '取消',
|
||||||
|
title: '标题'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -55,9 +55,10 @@ function addIframe() {
|
|||||||
.app-main {
|
.app-main {
|
||||||
/* 50= navbar 50 */
|
/* 50= navbar 50 */
|
||||||
min-height: calc(100vh - 50px);
|
min-height: calc(100vh - 50px);
|
||||||
|
height: calc(100vh - 50px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
.fixed-header + .app-main {
|
||||||
@ -68,6 +69,7 @@ function addIframe() {
|
|||||||
.app-main {
|
.app-main {
|
||||||
/* 84 = navbar + tags-view = 50 + 34 */
|
/* 84 = navbar + tags-view = 50 + 34 */
|
||||||
min-height: calc(100vh - 84px);
|
min-height: calc(100vh - 84px);
|
||||||
|
height: calc(100vh - 84px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
.fixed-header + .app-main {
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import '@/assets/styles/index.scss';
|
|||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
import Layui from '@layui/layui-vue';
|
||||||
|
import '@layui/layui-vue/lib/index.css';
|
||||||
|
|
||||||
// 自定义指令
|
// 自定义指令
|
||||||
import directive from './directive';
|
import directive from './directive';
|
||||||
@ -51,6 +53,7 @@ app.use(HighLight);
|
|||||||
app.use(ElementIcons);
|
app.use(ElementIcons);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(store);
|
app.use(store);
|
||||||
|
app.use(Layui);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
app.use(VXETable);
|
app.use(VXETable);
|
||||||
app.use(plugins);
|
app.use(plugins);
|
||||||
|
|||||||
413
src/utils/common.ts
Normal file
413
src/utils/common.ts
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
import { validatenull } from './validate';
|
||||||
|
|
||||||
|
/**树形数组根据key值查找对应data
|
||||||
|
* Arr 数组
|
||||||
|
* value 结果
|
||||||
|
* key value对应的key(可为数组)
|
||||||
|
* condition 多条件判断类型(或:||,且:&&)
|
||||||
|
**/
|
||||||
|
export function getDataFromTreeByKey(Arr, value, key = 'href', condition = '||') {
|
||||||
|
let Deep, T, F;
|
||||||
|
if (Arr.length > 0) {
|
||||||
|
for (F = Arr.length; F; ) {
|
||||||
|
T = Arr[--F];
|
||||||
|
if (typeof key == 'string' && T[key] == value) return T;
|
||||||
|
if (Array.isArray(key) && key.length > 0) {
|
||||||
|
const cond = key.map((k) => `T['${k}']==value`);
|
||||||
|
if (eval(cond.join(condition))) return T;
|
||||||
|
}
|
||||||
|
if (T.hasChildren || T.children) {
|
||||||
|
Deep = getDataFromTreeByKey(T.children, value, key);
|
||||||
|
if (Deep) return Deep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//数组转树型数据
|
||||||
|
export function toTree(data, parentKey = 'parentId') {
|
||||||
|
const result = [];
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const map = {};
|
||||||
|
data.forEach((item) => {
|
||||||
|
delete item.children;
|
||||||
|
delete item.hasChildren;
|
||||||
|
map[item.id] = item;
|
||||||
|
});
|
||||||
|
data.forEach((item) => {
|
||||||
|
const parent = map[item[parentKey]];
|
||||||
|
if (parent && parent.id != '#') {
|
||||||
|
(parent.children || (parent.children = [])).push(item);
|
||||||
|
} else {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
//对象数组去重
|
||||||
|
/*
|
||||||
|
arr: 数组数据
|
||||||
|
uniId: 对象内key
|
||||||
|
*/
|
||||||
|
export function uniqueFunc(arr, uniId) {
|
||||||
|
const res = new Map();
|
||||||
|
return arr.filter((item) => !res.has(getProperty(item, uniId)) && res.set(getProperty(item, uniId), 1));
|
||||||
|
}
|
||||||
|
//去除空的children
|
||||||
|
export function hasChildren(tree) {
|
||||||
|
return tree.map((item) => {
|
||||||
|
if (!item.hasChildren) {
|
||||||
|
delete item.children;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
hasChildren(item.children);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
补零函数
|
||||||
|
e: 需补零数字
|
||||||
|
t: 数字固定长度(默认长度2)
|
||||||
|
*/
|
||||||
|
export function digit(e, t = 2) {
|
||||||
|
let i = '';
|
||||||
|
e = String(e);
|
||||||
|
for (let a = e.length; a < t; a++) i += '0';
|
||||||
|
return e < Math.pow(10, t) ? i + (0 | e) : e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
判断两个对象是否相等(内部key一样,key值一样)
|
||||||
|
a:对象1
|
||||||
|
b:对象2
|
||||||
|
*/
|
||||||
|
export function isObjValEqual(a, b) {
|
||||||
|
//取对象a和b的属性名
|
||||||
|
const aProps = Object.getOwnPropertyNames(a);
|
||||||
|
const bProps = Object.getOwnPropertyNames(b);
|
||||||
|
//判断属性名的length是否一致
|
||||||
|
if (aProps.length != bProps.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//循环取出属性名,再判断属性值是否一致
|
||||||
|
for (let i = 0; i < aProps.length; i++) {
|
||||||
|
const propName = aProps[i];
|
||||||
|
if (a[propName] !== b[propName]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 文件下载
|
||||||
|
* file: 文件对象(blob)
|
||||||
|
* filename: 后台传入文件名
|
||||||
|
*/
|
||||||
|
export function downloadFile({ file, filename }) {
|
||||||
|
const blob = new Blob([file], {
|
||||||
|
type: 'application/vnd.ms-excel'
|
||||||
|
});
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
navigator.msSaveBlob(blob, filename);
|
||||||
|
} else {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formVal(form, data = {}) {
|
||||||
|
Object.keys(form).forEach((key) => {
|
||||||
|
if (data[key] !== undefined && data[key] !== null) {
|
||||||
|
form[key] = data[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 对象转url参数
|
||||||
|
* @param {object} data,对象
|
||||||
|
* @param {Boolean} isPrefix,是否自动加上"?"
|
||||||
|
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
|
||||||
|
*/
|
||||||
|
export function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
|
||||||
|
const prefix = isPrefix ? '?' : '';
|
||||||
|
const _result = [];
|
||||||
|
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets';
|
||||||
|
for (const key in data) {
|
||||||
|
const value = data[key];
|
||||||
|
// 去掉为空的参数
|
||||||
|
if (['', undefined, null].indexOf(value) >= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 如果值为数组,另行处理
|
||||||
|
if (value.constructor === Array) {
|
||||||
|
// e.g. {ids: [1, 2, 3]}
|
||||||
|
switch (arrayFormat) {
|
||||||
|
case 'indices':
|
||||||
|
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
_result.push(`${key}[${i}]=${value[i]}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'brackets':
|
||||||
|
// 结果: ids[]=1&ids[]=2&ids[]=3
|
||||||
|
value.forEach((_value) => {
|
||||||
|
_result.push(`${key}[]=${_value}`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'repeat':
|
||||||
|
// 结果: ids=1&ids=2&ids=3
|
||||||
|
value.forEach((_value) => {
|
||||||
|
_result.push(`${key}=${_value}`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'comma':
|
||||||
|
// 结果: ids=1,2,3
|
||||||
|
let commaStr = '';
|
||||||
|
value.forEach((_value) => {
|
||||||
|
commaStr += (commaStr ? ',' : '') + _value;
|
||||||
|
});
|
||||||
|
_result.push(`${key}=${commaStr}`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value.forEach((_value) => {
|
||||||
|
_result.push(`${key}[]=${_value}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_result.push(`${key}=${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _result.length ? prefix + _result.join('&') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepClone(obj) {
|
||||||
|
const newObj = Array.isArray(obj) ? [] : {};
|
||||||
|
if (obj && typeof obj === 'object') {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
newObj[key] = obj && typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
|
||||||
|
* @param {object} obj 对象
|
||||||
|
* @param {string} key 需要获取的属性字段
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export function getProperty(obj, key) {
|
||||||
|
if (!obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof key !== 'string' || key === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (key.indexOf('.') !== -1) {
|
||||||
|
const keys = key.split('.');
|
||||||
|
let firstObj = obj[keys[0]] || {};
|
||||||
|
|
||||||
|
for (let i = 1; i < keys.length; i++) {
|
||||||
|
if (firstObj) {
|
||||||
|
firstObj = firstObj[keys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstObj;
|
||||||
|
}
|
||||||
|
return obj[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findLastIndex(arr, callback, thisArg) {
|
||||||
|
for (let index = arr.length - 1; index >= 0; index--) {
|
||||||
|
const value = arr[index];
|
||||||
|
if (callback.call(thisArg, value, index, arr)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startToEnd(startTime, endTime = new Date(), equation = true) {
|
||||||
|
if (equation) {
|
||||||
|
return Number(new Date(startTime)) <= Number(new Date(endTime));
|
||||||
|
}
|
||||||
|
return Number(new Date(startTime)) < Number(new Date(endTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四舍五入,保留小数
|
||||||
|
* @param {需要保留小数的值} val
|
||||||
|
* @param {保留几位小数} decimal
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function toDecimal(val, decimal = 2) {
|
||||||
|
const power = Math.pow(10, decimal);
|
||||||
|
let f = parseFloat(val);
|
||||||
|
if (isNaN(f)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
f = Math.round(val * power) / power;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据年月获取月份天数
|
||||||
|
* @param {*} month
|
||||||
|
* @param {*} year
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getEndDate(month = new Date().getMonth() + 1, year = new Date().getFullYear()) {
|
||||||
|
const a = new Date();
|
||||||
|
return a.setFullYear(year, month, 1), new Date(a.getTime() - 864e5).getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字转中文
|
||||||
|
* @param {数字} value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function numberToChanie(value) {
|
||||||
|
const chnNumChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
||||||
|
return chnNumChar[value * 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频时长
|
||||||
|
* @param {视频文件对象} file
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getDuration(file) {
|
||||||
|
const fileName = file.name || '';
|
||||||
|
const ext = fileName.split('.')[fileName.split('.').length - 1];
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (ext != 'mp4') {
|
||||||
|
reject('请上传MP4文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
const audioElement = new Audio(url);
|
||||||
|
audioElement.addEventListener('loadedmetadata', (_event) => {
|
||||||
|
resolve(audioElement.duration);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒钟数转小时,分钟,秒数
|
||||||
|
* @param {秒钟数} seconds
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getTimes(seconds) {
|
||||||
|
const h = parseInt((seconds / 60 / 60) % 24);
|
||||||
|
const m = parseInt((seconds / 60) % 60);
|
||||||
|
const s = parseInt(seconds % 60);
|
||||||
|
let val = '';
|
||||||
|
if (h > 0) {
|
||||||
|
val += `${h}小时`;
|
||||||
|
}
|
||||||
|
if (m > 0) {
|
||||||
|
val += `${m}分钟`;
|
||||||
|
}
|
||||||
|
if (!isNaN(s)) {
|
||||||
|
val += `${s}秒`;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File文件对象转Bolb对象
|
||||||
|
* @param {文件对象} file
|
||||||
|
*/
|
||||||
|
export function FileToBolb(file) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
const arr = e.target.result.split(',');
|
||||||
|
const data = window.atob(arr[1]);
|
||||||
|
const mime = arr[0].match(/:(.*?);/)[1];
|
||||||
|
const ia = new Uint8Array(data.length);
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
ia[i] = data.charCodeAt(i);
|
||||||
|
}
|
||||||
|
resolve(new Blob([ia], { type: mime }));
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* blob文件对象转File文件对象
|
||||||
|
* @param {文件blob对象} blob
|
||||||
|
* @param {文件名} fileName
|
||||||
|
* @param {文件类型} fileType
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function BlobToFile(blob, fileName, fileType) {
|
||||||
|
return new window.File([blob], fileName, { type: fileType });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据对象转FormData
|
||||||
|
* @param {数据对象} obj
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function ObjToForm(obj) {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (obj && Object.keys(obj).length > 0) {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const val = obj[key];
|
||||||
|
formData.append(key, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码强度校验
|
||||||
|
* @param {密码} pwd
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function checkPwd(pwd) {
|
||||||
|
let level = 0;
|
||||||
|
if (pwd.length < 8) {
|
||||||
|
level = 1;
|
||||||
|
} else {
|
||||||
|
level = 0;
|
||||||
|
if (pwd.match(/\d/g)) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
if (pwd.match(/[a-z]/gi)) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
if (pwd.match(/[A-Z]/gi)) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
if (pwd.match("[`~!@#$^&*()=|{}':;',\\[\\].<>《》./~!@#¥……&*()——|{}【】‘;:”“'。,、? ]")) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额格式化
|
||||||
|
* @param {金额} value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function toLocaleString(value, min = 2, max = 7) {
|
||||||
|
const imumFractionDigits = { minimumFractionDigits: min, maximumFractionDigits: max };
|
||||||
|
if (validatenull(value) || isNaN(value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value.toLocaleString('en-US', imumFractionDigits);
|
||||||
|
}
|
||||||
@ -106,3 +106,26 @@ export const isArray = (arg: string | string[]) => {
|
|||||||
}
|
}
|
||||||
return Array.isArray(arg);
|
return Array.isArray(arg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为空
|
||||||
|
*/
|
||||||
|
export function validatenull(val) {
|
||||||
|
if (typeof val == 'boolean') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof val == 'number') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (val instanceof Array) {
|
||||||
|
if (val.length == 0) return true;
|
||||||
|
} else if (val instanceof Object) {
|
||||||
|
if (JSON.stringify(val) === '{}') return true;
|
||||||
|
} else {
|
||||||
|
if (val == 'null' || val == null || val == 'undefined' || val == undefined || val == ''){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
256
src/views/demo/tableDemo/index.vue
Normal file
256
src/views/demo/tableDemo/index.vue
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2 h-100">
|
||||||
|
<el-row :gutter="20" style="height: 100%">
|
||||||
|
<el-col :lg="24" :xs="24" style="height: 100%">
|
||||||
|
<el-card shadow="hover" class="h-100">
|
||||||
|
<search-panel ref="searchRef" :configs="search.configs" :model="search.data" @search="onLoad" @reset="reset">
|
||||||
|
<form-table
|
||||||
|
ref="tableRef"
|
||||||
|
:page="table.page"
|
||||||
|
height="100%"
|
||||||
|
:loading="table.loading"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="table.data"
|
||||||
|
:row-class-name="rowClassName"
|
||||||
|
v-model:selected-keys="table.selectedKeys"
|
||||||
|
@change="onLoad"
|
||||||
|
>
|
||||||
|
<template #toolbar>
|
||||||
|
<!-- permission为权限模块,permissionKey为权限key,根据实际情况进行修改 -->
|
||||||
|
<toolbar-button
|
||||||
|
permission="permission"
|
||||||
|
:permissionKey="'permissionKey'"
|
||||||
|
:disRemove="table.selectedData.length == 0"
|
||||||
|
:disEdit="table.selectedData.length != 1"
|
||||||
|
@add="handleAdd"
|
||||||
|
@edit="handleEdit(table.selectedData[0])"
|
||||||
|
@remove="remove"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-button type="warning" plain>前置其他按钮</el-button>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<el-button type="warning" plain>后置其他按钮</el-button>
|
||||||
|
</template>
|
||||||
|
</toolbar-button>
|
||||||
|
</template>
|
||||||
|
<template #operate="{ row }">
|
||||||
|
<!-- 可根据实际情况进行修改或封装 -->
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="primary" @click="view(row)">查看</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</template>
|
||||||
|
</form-table>
|
||||||
|
</search-panel>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { listTableDemo } from '@/api/demo/demo/index';
|
||||||
|
const search = reactive({
|
||||||
|
data: {},
|
||||||
|
configs: [
|
||||||
|
/**
|
||||||
|
* 搜索内容,如果超过一行则只显示一行,多余搜索可折叠显示/隐藏
|
||||||
|
* 支持input、select、tree-select、date-picker、time-select、time-picker、autocomplete类型
|
||||||
|
* 例:输入框、下拉选择框、日期选择框、时间选择框、输入提示框
|
||||||
|
* 如有其他类型搜索,可根据实际情况对组件进行完善
|
||||||
|
* 具体使用请参照SearchForm组件
|
||||||
|
*/
|
||||||
|
{ label: '输入框', prop: 'input' },
|
||||||
|
// 下拉选择框,可以显示字典值或自定义选项options
|
||||||
|
{
|
||||||
|
label: '下拉选择框',
|
||||||
|
prop: 'select',
|
||||||
|
type: 'select',
|
||||||
|
dict: 'sys_user_sex',
|
||||||
|
change: ($event) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
visibleChange: ($event) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '日期选择框',
|
||||||
|
prop: 'date',
|
||||||
|
type: 'date',
|
||||||
|
valueFormat: 'YYYY-MM-DD',
|
||||||
|
change: ($event) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '时间选择框',
|
||||||
|
prop: 'time',
|
||||||
|
type: 'daterange',
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')],
|
||||||
|
change: ($event) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '输入提示框',
|
||||||
|
prop: 'autocomplete',
|
||||||
|
type: 'autocomplete',
|
||||||
|
fetchSuggestions: (queryString, cb) => {
|
||||||
|
// 具体参照el-autocomplete组件
|
||||||
|
},
|
||||||
|
select: (item) => {
|
||||||
|
// 处理选中项
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
|
const activatedFlag = ref(false);
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
{ title: '选择', type: 'checkbox', width: '55px', align: 'center' },
|
||||||
|
{ title: '序号', type: 'number', align: 'center', width: '55px' },
|
||||||
|
{ title: '简单显示', key: 'testKey', showOverflowTooltip: true },
|
||||||
|
/**
|
||||||
|
* 可以在render中使用tsx语法,实现自定义显示任意内容
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
title: 'render显示',
|
||||||
|
key: 'xxx',
|
||||||
|
render: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<span style="color: #67C23A; cursor: pointer; font-size: 18px" onClick={() => handleChange(row)}>{row.orderNum}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 字典显示 dict:'字典名称'
|
||||||
|
{ title: '字典显示', key: 'gender', dict: 'sys_user_sex' },
|
||||||
|
{ title: '操作', customSlot: 'operate', align: 'center', width: '200px', fixed: 'right' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const table = reactive({
|
||||||
|
loading: false,
|
||||||
|
page: {
|
||||||
|
limit: 30,
|
||||||
|
limits: [10, 20, 30, 40, 50, 100, 200, 500],
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
layout: ['count', 'prev', 'page', 'next', 'limits', 'skip'],
|
||||||
|
theme: 'blue'
|
||||||
|
},
|
||||||
|
title: '',
|
||||||
|
selectedData: [],
|
||||||
|
selectedKeys: [],
|
||||||
|
disDelIndex: [],
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLoad = async () => {
|
||||||
|
table.loading = true;
|
||||||
|
const { current, limit } = table.page;
|
||||||
|
try {
|
||||||
|
const res = await listTableDemo(current, limit, search.data);
|
||||||
|
table.page.total = res.total;
|
||||||
|
table.data = res.rows;
|
||||||
|
table.selectedData = [];
|
||||||
|
} finally {
|
||||||
|
table.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
search.data = {};
|
||||||
|
onLoad();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格行样式,提示选中行不能删除或其他操作
|
||||||
|
const rowClassName = (row, rowIndex) => {
|
||||||
|
if(table.disDelIndex.includes(rowIndex)) return 'bg-warning-8';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听表格选中数据
|
||||||
|
watch(
|
||||||
|
() => [...table.selectedKeys],
|
||||||
|
(val) => {
|
||||||
|
initSelect();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const initSelect = () => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
table.selectedData = tableRef.value.getCheckData();
|
||||||
|
table.disDelIndex.length > 0 && initDelRow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initDelRow = () => {
|
||||||
|
table.disDelIndex = [];
|
||||||
|
table.selectedData.forEach(item => {
|
||||||
|
// 项目状态为1时,不能删除, 可根据自己的业务进行调整
|
||||||
|
if(item?.id == '1') {
|
||||||
|
let rowIndex = table.data.findIndex(data => item.id == data.id);
|
||||||
|
table.disDelIndex.push(rowIndex);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const view = (row: any) => {
|
||||||
|
// 查看详情
|
||||||
|
ElMessage.success('查看操作');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
// 新增
|
||||||
|
ElMessage.success('新增');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (row: any) => {
|
||||||
|
// 编辑
|
||||||
|
ElMessage.success('编辑' + row.testKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
// 删除
|
||||||
|
await initDelRow();
|
||||||
|
if(table.disDelIndex.length > 0){
|
||||||
|
ElMessage.warning('当前选中数据有不能删除的数据!');
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击事件
|
||||||
|
const handleChange = (row: any) => {
|
||||||
|
ElMessage.success('点击' + row.orderNum);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
onLoad();
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
activatedFlag.value && onLoad();
|
||||||
|
activatedFlag.value = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.h-100 {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.bg-warning-8) {
|
||||||
|
background-color: #f8e3c5;
|
||||||
|
.layui-table-fixed-left{
|
||||||
|
background-color: #f8e3c5;
|
||||||
|
}
|
||||||
|
.layui-table-fixed-right{
|
||||||
|
background-color: #f8e3c5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||||
|
|
||||||
import createUnoCss from './unocss';
|
import createUnoCss from './unocss';
|
||||||
@ -11,8 +12,14 @@ import createSetupExtend from './setup-extend';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default (viteEnv: any, isBuild = false): [] => {
|
export default (viteEnv: any, isBuild = false): [] => {
|
||||||
const vitePlugins: any = [];
|
const vitePlugins: any = [
|
||||||
vitePlugins.push(vue());
|
vue({
|
||||||
|
script: {
|
||||||
|
defineModel: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
vueJsx()
|
||||||
|
];
|
||||||
vitePlugins.push(vueDevTools());
|
vitePlugins.push(vueDevTools());
|
||||||
vitePlugins.push(createUnoCss());
|
vitePlugins.push(createUnoCss());
|
||||||
vitePlugins.push(createAutoImport(path));
|
vitePlugins.push(createAutoImport(path));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user