mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +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-router": "4.6.3",
|
||||
"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": {
|
||||
"@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
|
||||
|
||||
@ -193,6 +193,7 @@ h6 {
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
height: 100%;
|
||||
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',
|
||||
personalCenter: 'Personal Center',
|
||||
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: '布局设置',
|
||||
personalCenter: '个人中心',
|
||||
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 {
|
||||
/* 50= navbar 50 */
|
||||
min-height: calc(100vh - 50px);
|
||||
height: calc(100vh - 50px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
@ -68,6 +69,7 @@ function addIframe() {
|
||||
.app-main {
|
||||
/* 84 = navbar + tags-view = 50 + 34 */
|
||||
min-height: calc(100vh - 84px);
|
||||
height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
|
||||
@ -8,6 +8,8 @@ import '@/assets/styles/index.scss';
|
||||
import App from './App.vue';
|
||||
import store from './store';
|
||||
import router from './router';
|
||||
import Layui from '@layui/layui-vue';
|
||||
import '@layui/layui-vue/lib/index.css';
|
||||
|
||||
// 自定义指令
|
||||
import directive from './directive';
|
||||
@ -51,6 +53,7 @@ app.use(HighLight);
|
||||
app.use(ElementIcons);
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.use(Layui);
|
||||
app.use(i18n);
|
||||
app.use(VXETable);
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否为空
|
||||
*/
|
||||
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 vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
import createUnoCss from './unocss';
|
||||
@ -11,8 +12,14 @@ import createSetupExtend from './setup-extend';
|
||||
import path from 'path';
|
||||
|
||||
export default (viteEnv: any, isBuild = false): [] => {
|
||||
const vitePlugins: any = [];
|
||||
vitePlugins.push(vue());
|
||||
const vitePlugins: any = [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true
|
||||
}
|
||||
}),
|
||||
vueJsx()
|
||||
];
|
||||
vitePlugins.push(vueDevTools());
|
||||
vitePlugins.push(createUnoCss());
|
||||
vitePlugins.push(createAutoImport(path));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user