You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

937 lines
26 KiB

<template>
<div class="maboxhi" id="bodys">
<el-table
:data="tableData"
style="width: 100%"
:show-summary="shownm"
:summary-method="getSummaries"
:span-method="arraySpanMethod"
v-loading="loading"
@selection-change="handleSelectionChange"
@select-all="handleUserSelcet"
@header-dragend="handleWidthChange"
border
height="100%"
:row-class-name="tableRowClassName"
ref="selecttable"
>
<el-table-column
:type="column.type == 0 ? 'selection' : ''"
:min-width="column.width"
v-for="(column, index) in newcolumnList"
:key="column.label + column.prop"
:prop="column.prop"
:label="column.label"
:fixed="column.fixed"
:sortable="column.sortable"
:selectable="isselectfun"
v-show="column.head"
>
<!-- <template #header>
<el-text class="mx-1">{{ column.label }}</el-text>
</template> -->
<el-table-column
:type="column.type == 12 ? 'index' : ''"
:min-width="column.width"
:prop="column.prop"
:label="column.label"
v-if="column.type != 0"
flexible
show-overflow-tooltip
class="el_boxsa"
>
<template #header>
<el-text class="el_placeholder" v-if="column.type == 1">&nbsp</el-text>
<el-text
v-if="column.type == 1 || column.type == 0 || column.type == 12 || column.type == 6"
class="mx-1"
></el-text>
<el-icon v-if="column.type == 12 && props.isShowRefresh === true" @click="handleRefresh"
><RefreshRight
/></el-icon>
<el-input
v-if="column.type == 2 || column.type == 13"
v-model.trim="column.values"
clearable
:placeholder="`请输入${column.label}`"
@change="inputchange($event, column)"
@keyup.enter="handleInput"
/>
<el-select
v-else-if="column.type == 3 || column.type == 14"
v-model="column.values"
filterable
class="m-2 selectbr"
clearable
:placeholder="`请选择${column.label}`"
@change="selectchange($event, column)"
>
<el-option
v-for="item in column.checkarr"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-date-picker
v-model="column.values"
v-else-if="column.type == 4"
type="date"
clearable
style="width: auto; height: 23px !important"
:placeholder="`请选择${column.label}`"
@change="timechange($event, column)"
/>
<el-date-picker
v-model="column.values"
v-else-if="column.type == 5"
type="datetime"
clearable
style="width: auto"
size="default"
:placeholder="`请选择${column.label}`"
format="YYYY/MM/DD HH:mm:ss"
@change="timechange($event, column)"
/>
</template>
<template #default="scope">
<el-text
@click.stop @dblclick.stop="Copytxt(scope.row[column.prop])"
class="mx-2 tabculconte"
:class="
scope.row[column.prop] == props.searchInput && scope.row[column.prop]
? 'static-class'
: ''
"
:id="scope.row[column.prop]"
v-if="Number(column.type) < 6 && Number(column.type) > 0"
>{{ scope.row[column.prop] }}</el-text
>
<el-switch
@change="makeCargo(scope.row, column)"
v-else-if="column.type == 11"
v-model="scope.row[column.prop]"
/>
<!-- <el-text
class="mx-2 tabculconte"
v-for="item in column.checkarr"
v-if="Number(column.type) < 6 && Number(column.type) > 0"
>
<span v-if="item.value==scope.row[column.prop]">{{ item.label }}</span>
</el-text
> -->
<el-text
style=""
class="mx-2 tabculconte colors"
v-else-if="column.type == 9"
@click="godowns(scope.row[column.downprop])"
>{{ scope.row[column.prop] }}</el-text
>
<el-input
@blur="handleNumberRange(column, scope.row)"
v-else-if="column.type == 10"
v-model.trim="scope.row[column.prop]"
type="number"
:min="0"
:max="scope.row.residueNumber"
></el-input>
<div
class="slotbuts"
v-else-if="column.type == 6 || column.type == 13 || column.type == 14"
>
<slot :scope="scope"> </slot>
</div>
<!-- <el-image
preview-teleported
v-else-if="column.type == 7"
style="width: 60px; height: 60px; margin-left: 10px"
:initial-index="index"
:z-index="100"
v-for="(item, index) in scope.row[column.prop].split(',')"
:src="item"
:preview-src-list="scope.row[column.prop].split(',')"
/> -->
<el-image
preview-teleported
v-else-if="column.type == 7"
style="width: 60px; height: 60px; margin-left: 10px"
:initial-index="index"
:z-index="100"
v-for="(item, index) in scope.row[column.prop]?.split(',')"
:src="item"
:preview-src-list="scope.row[column.prop]?.split(',')"
/>
</template>
</el-table-column>
<template #header>
<div style="display: inline-block">
<div class="tilbox">
<el-text class="mx-1 tabtitles" @click.stop @dblclick.stop="copyalls(column)">{{
column.label
}}</el-text>
<!-- <el-icon v-if="column.type == 1 || column.type == 2 || column.type == 3"
><DocumentCopy
/></el-icon> -->
</div>
</div>
</template>
</el-table-column>
</el-table>
<!-- 勾选合计行 -->
<div class="selection-total-row" v-show="shownm && isHaveSelection">
<span>勾选合计:</span>
<span v-for="item in selectCount" :key="item.label">
<template v-if="!item.head">
<span style="margin: 0 5px">{{ item.label + ':' }}</span>
<el-text type="success">{{ item.value }}</el-text>
</template>
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import {
computed,
ref,
watchEffect,
getCurrentInstance,
onMounted,
defineExpose,
inject,
watch,
} from 'vue';
import type { PropType } from 'vue';
import { computeNumber } from '@/utils/util.js';
import { useRoute } from 'vue-router';
const $route = useRoute();
const functions = inject('functions') as any;
const { proxy } = getCurrentInstance() as any;
const selecttable = ref(null);
/**
* 对应通知事件
* inputTxt:输入框输入的确认事件
* timeCheck:时间选择器选择事件
* selectCheck:下拉框选中事件
* selection:勾选框事件
*/
onMounted(() => {
function getWinHight() {
var windowHight = 0;
if (document.body.clientHeight && document.documentElement.clientHeight) {
windowHight =
document.body.clientHeight < document.documentElement.clientHeight
? document.body.clientHeight
: document.documentElement.clientHeight;
} else {
// windowHight=(document.body.clientHeight>document.documentElement.clientHeight)?document.body.clientHeight:document.documentElement.clientHeight;
}
// return windowHight;
return document.documentElement.clientHeight;
}
var bodyContent = document.querySelectorAll('.maboxhi');
const _height = getWinHight();
// 设置列表的高度
bodyContent.forEach(val => {
val.style.height = _height - val.getBoundingClientRect().top - 65 + 'px';
// if (val.style.height === '890px') val.style.height = '600px';
});
});
interface TableColumnType {
/** 表格列的key */
prop: string;
/** 表格列的名字 */
label: string;
/**
* 对应列表头的类型
* 0:直接显示为单选,prop,label,全部传空
* 1:普通表格,正常显示
* 2:带输入框表格
* 3:带下拉框表格
* 4:带日期选择器列
* 5:带日期时间选择器
* 6:操作栏,prop,label,全部传空
* 7:图片
* 8:带背景的文字
* 9:下载附件
* 10.输入框的表格内容
* 11.开关类型
* 12.序号
* 13.带搜索的自定义列表
* 14.带下拉的自定义列表
*/
type: number | string;
/** 用于接受表头的值 */
values: number | string;
/** 表头宽度 number string undefined */
width: number | string | undefined;
/** 下拉框可选项 */
checkarr?: {
value: string | number;
label: string | number;
}[];
/** 是否固定表头
* true false
* left right
*/
fixed: boolean | string;
/** 是否统计
* true false 或者直接不写该参数
*/
isshowSummary?: boolean;
/** 是否排序
* true false 或者直接不写该参数
*/
sortable?: boolean;
/** 是否显示列
* true false 或者直接不写该参数
*/
head?: boolean;
/** 是否文字超出隐藏
* 默认超出隐藏
* true是不隐藏
* false 隐藏
*/
isextrahide?: boolean;
/** 下载文件的url的key
*/
downprop?: string;
/** 输入框内容需要小于的对应字段
*/
lessThanNum?: string;
}
interface TableDataType {
[key: string]: any;
}
let props = defineProps({
columnList: {
type: Array as PropType<TableColumnType[]>,
required: true,
},
searchInput: {
type: String,
required: false,
},
tableData: {
type: Array as PropType<TableDataType[]>,
required: true,
},
loading: {
type: Boolean as PropType<boolean>,
required: false,
default: false,
},
//回显勾选的数据列表
checkselect: {
type: Array as PropType<TableDataType[]>,
required: false,
},
//是否可选择的函数
isselectfun: {
type: Function as PropType<(row: TableDataType) => boolean>,
required: false,
default: null,
},
/** 设置列表行类名 */
tableRowClassName: {
type: Function as PropType<(row: TableDataType, rowIndex: number) => string>,
required: false,
default: null,
},
/**
* 设置行合并和列合并
* @return [行合并, 列合并]
* */
arraySpanMethod: {
type: Function as PropType<
(row: {
row: TableDataType;
column: TableColumnType;
rowIndex: number;
columnIndex: number;
}) => number[]
>,
required: false,
default: null,
},
isShowRefresh: {
type: Boolean as PropType<boolean>,
required: false,
default: false,
},
initFn: {
type: (Boolean as PropType<boolean>) || (Function as PropType<Function>),
required: false,
default: false,
},
/** 从本地获取的list名称 */
columnListName: {
type: String as PropType<string>,
required: true,
default: 'columnList',
},
minWidth: {
type: Number as PropType<number>,
required: true,
default: 55,
},
});
/** 勾选数据统计 */
const selectCount = ref([]);
/** 是否有复选框 */
const isHaveSelection = ref(false);
// row, column, rowIndex, columnIndex
let selectarr = ref([]);
/** 设置每行颜色 */
const tableRowClassName = ({ row, rowIndex }: { row: TableDataType; rowIndex: number }) => {
for (let i = 0; i < selectarr.value.length; i++) {
if (selectarr.value[i] === row) return 'table-SelectedRow-bgcolor';
}
if (props.tableRowClassName) return props.tableRowClassName(row, rowIndex);
return '';
};
let emit = defineEmits(['inputTxt', 'timeCheck', 'selectCheck', 'selection', 'makeCargo']);
let newcolumnList = ref<TableColumnType[]>([]);
watchEffect(() => {
const _arr = [];
props.columnList.forEach(item => {
if (!item.head) {
_arr.push(item);
}
});
newcolumnList.value = _arr;
});
onMounted(() => {
/* if (props.checkselect) {
props.checkselect.map(item => {
if (props.isselectfun) {
if (props.isselectfun(item)) {
selecttable.value.toggleRowSelection(item, true);
}
} else {
selecttable.value.toggleRowSelection(item, true);
}
});
} */
let _checkSelect = props.checkselect;
console.log('_checkSelect :>> ', _checkSelect);
if (!_checkSelect) {
_checkSelect = [];
}
handleCheckSelect(_checkSelect);
});
/** 勾选 */
function handleCheckSelect(select: any[]) {
console.log('select :>> ', select);
console.log(props.isselectfun, 'props.isselectfun');
if (select.length !== 0) {
for (const item of select) {
if (props.isselectfun) {
if (props.isselectfun(item)) {
selecttable.value.toggleRowSelection(item, true);
} else {
selecttable.value.toggleRowSelection(item, true);
}
} else {
selecttable.value.toggleRowSelection(item, true);
}
}
} else {
selecttable.value.clearSelection();
}
}
/** 清空勾选 */
const handleClearSelect = () => {
selecttable.value.clearSelection();
};
function godowns(url) {
let a = document.createElement('a'); //创建一个a标签用来跳转
a.href = url; //myurl 是后端返回的下载地址
// a.target = '_blank';
a.download = 'xixi.zip'; //设置下载文件的文件名和文件格式
document.body.appendChild(a); //将标签DOM,放置页面
a.click();
window.URL.revokeObjectURL(url); //释放 url 对象内存
document.body.removeChild(a);
}
let shownm = ref(false);
function copyalls(column: TableColumnType) {
console.log(column,'column');
const _isReturn = column.type == 1 || column.type == 2 || column.type == 3;
if (!_isReturn) return;
let text = '';
props.tableData.map((item, index) => {
if (index < props.tableData.length - 1) {
if (item[column.prop]) {
text += `${item[column.prop]}\n`;
}
} else {
if (item[column.prop]) {
text += `${item[column.prop]}`;
} else {
}
}
});
copyContent(text);
}
function handleNumberRange(column: TableColumnType, row: TableDataType) {
row[column.prop] = row[column.prop].replace(/[^\.\d]/g, ''); //替换掉与数字无关的字符(中英文与符号都会被替换掉,只留下数字,此时值类型为字符串)
if (row[column.prop]) {
//如果替换后还有值
row[column.prop] = parseInt(row[column.prop]); //把字符型转成整数型
}
// if (column.lessThanNum) {
// if (row[column.prop] > row[column.lessThanNum]) {
// row[column.prop] = row[column.lessThanNum];
// proxy.$message({
// type: 'error',
// message: '数量不能大于可用数量',
// });
// }
// }
// 上架数量判断
if (row.residueNumber) {
if (row.enterNum > row.residueNumber) {
proxy.$message({
type: 'error',
message: '数量不能大于可上架总数,已调整为最大数量!',
});
row.enterNum = row.residueNumber;
}
}
// 下架数量判断
if (row.num) {
if (row.enterNum > row.num) {
proxy.$message({
type: 'error',
message: '数量不能大于可下架总数,已调整为最大数量!',
});
row.enterNum = row.num;
}
}
}
function handleEdit(index: number, row: TableDataType) {}
function makeCargo(value, column: TableColumnType) {
emit('makeCargo', value, column);
}
function inputchange(value, column: TableColumnType) {
console.log('value :>> ', value);
console.log('column :>> ', column);
if (typeof value == 'string') {
emit('inputTxt', value, column);
}
}
function selectchange(value, column: TableColumnType) {
emit('selectCheck', value, column);
}
function timechange(value, column: TableColumnType) {
emit('timeCheck', value, column);
}
function timechangeKey(value, column: TableColumnType) {
emit('timeCheck', column.values, column);
}
let timer = null;
const handleSelectionChange = (param: TableDataType[]) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
selectarr.value = param;
try {
// 重置数据
for (let i = 0; i < selectCount.value.length; i++) {
selectCount.value[i].value = 0;
}
if (selectarr.value.length === 0) return;
for (let index = 0; index < selectarr.value.length; index++) {
const val = selectarr.value[index];
for (let j = 0; j < selectCount.value.length; j++) {
const item = selectCount.value[j];
item.value = computeNumber(item.value, '+', Number(val[item.prop] || 0)).result;
}
}
} catch (error) {
console.log('error :>> ', error);
} finally {
emit('selection', param);
}
}, 10);
};
// 计算是否清空表格复选框值
const handleUserSelcet = list => {
if (list.length === 0) return;
let _flag = false;
for (const iterator of list) {
if (_flag) return;
for (const item of props.tableData) {
if (item === iterator) _flag = true;
if (_flag) break;
}
}
if (_flag) return;
handleClearSelect();
};
function inputclear(value, column: TableColumnType) {
emit('inputTxt', '', column);
}
function selectclear(value, column: TableColumnType) {
emit('selectCheck', '', column);
}
function timeclear(value, column: TableColumnType) {
emit('timeCheck', '', column);
}
function handleDelete(index: number, row: TableDataType) {}
const getSummaries = (param: any) => {
const _arr = [];
const { columns, data } = param;
let newarr = [];
let tji = 0;
let _flag1 = false;
let _flag2 = false;
props.columnList.forEach((item, index) => {
if (item.type === 0) _flag1 = true;
if (index == 0) {
newarr[0] = '总计';
return;
}
tji = 0;
if (item?.isshowSummary && !item.head) {
for (let ite of data) {
tji = computeNumber(tji, '+', Number(ite[item?.prop] || 0)).result;
}
newarr.push(tji);
_arr.push({
label: item?.label,
value: 0,
prop: item?.prop,
head: item.head,
});
_flag2 = true;
} else if (!item.head) {
newarr.push(null);
}
});
selectCount.value = _arr;
isHaveSelection.value = _flag1;
shownm.value = _flag2;
return newarr;
};
/** 设置合并行和列 */
const arraySpanMethod = row => {
if (props.arraySpanMethod) return props.arraySpanMethod(row);
};
watchEffect(() => {
for (const iterator of props.columnList) {
if (iterator.isshowSummary) {
shownm.value = true;
break;
}
}
});
// 复制文本内容
function copyContent(content: string) {
// 复制结果
let copyResult = true;
// 设置想要复制的文本内容
const text = content || '暂无内容';
// 判断是否支持clipboard方式
if (!!window.navigator.clipboard) {
// 利用clipboard将文本写入剪贴板(这是一个异步promise)
window.navigator.clipboard
.writeText(text)
.then(res => {
proxy.$message({
type: 'success',
message: '复制成功',
});
// 返回复制操作的最终结果
return copyResult;
})
.catch(err => {
copyResult = false;
console.log('复制失败', err);
proxy.$message({
type: 'error',
message: '复制失败',
});
// 返回复制操作的最终结果
return copyResult;
});
} else {
// 不支持clipboard方式 则采用document.execCommand()方式
// 创建一个input元素
let inputDom = document.createElement('textarea');
// 设置为只读 防止移动端手机上弹出软键盘
inputDom.setAttribute('readonly', 'readonly');
// 给input元素赋值
inputDom.value = text;
// 将创建的input添加到body
document.body.appendChild(inputDom);
// 选中input元素的内容
inputDom.select();
// 执行浏览器复制命令
// 复制命令会将当前选中的内容复制到剪切板中(这里就是创建的input标签中的内容)
// Input要在正常的编辑状态下原生复制方法才会生效
const result = document.execCommand('copy');
// 判断是否复制成功
if (result) {
proxy.$message({
type: 'success',
message: '复制成功',
});
} else {
console.log('复制失败');
proxy.$message({
type: 'error',
message: '复制失败',
});
copyResult = false;
}
// 复制操作后再将构造的标签 移除
document.body.removeChild(inputDom);
// 返回复制操作的最终结果
return copyResult;
}
}
function Copytxt(Value){
copyContent(Value)
}
const handleRefresh = () => {
for (let i = 0; i < newcolumnList.value.length; i++) {
const item = newcolumnList.value[i];
item.values = '';
if (item.type === 2 || item.type === 13) emit('inputTxt', item.values, item);
else if (item.type === 3) emit('selectCheck', item.values, item);
else if (item.type === 4) emit('timeCheck', item.values, item);
}
};
/** 回车键执行 */
const handleInput = () => {
props.initFn && props.initFn();
};
/** 宽度执行回调 */
const handleWidthChange = (newWidth, oldWidth, column, event) => {
for (let index = 0; index < props.columnList.length; index++) {
const element = props.columnList[index];
if (element.prop + element.label !== column.property + column.label) continue;
element.width = newWidth > props.minWidth ? newWidth : props.minWidth;
break;
}
functions.setStorage($route.fullPath + (props.columnListName || 'columnList'), props.columnList);
};
defineExpose({ handleCheckSelect, handleClearSelect });
</script>
<style lang="scss">
// :root{
// --el-checkbox-checked-bg-color:'#D3832A';
// --el-checkbox-checked-input-border-color:'#D3832A';
// }
.el-table {
//悬停
--el-table-row-hover-bg-color: #d3832a;
}
// 勾选合计行
.selection-total-row {
width: 100%;
margin: 10px 0 5px;
font-size: 14px;
}
// .el-table .warning-row {
// --el-table-tr-bg-color: var(--el-color-warning-light-9);
// }
// .el-table .success-row {
// --el-table-tr-bg-color: var(--el-color-success-light-9);
// }
.maboxhi {
width: 100%;
display: flex;
flex-direction: column;
}
.on {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px !important;
}
.selectbr {
border: none !important;
font-size: 12px !important;
}
.tabtitles {
color: #092c4d !important;
font-size: 12px !important;
margin-right: 5px !important;
}
.el-table .ascending .sort-caret.ascending {
border-bottom-color: #d3832a !important;
font-size: 12px !important;
}
.el-table .descending .sort-caret.descending {
border-top-color: #d3832a !important;
font-size: 12px !important;
}
.tabculconte {
color: #333333;
font-size: 12px !important;
}
.slotbuts {
height: 25px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px !important;
}
.slotbuts .el-text {
color: #d3832a !important;
margin-right: 10px !important;
cursor: pointer;
font-size: 12px !important;
&:nth-last-child(1) {
margin-right: 0px !important;
}
}
.el-popper.is-pure.is-light.el-dropdown__popper .el-text {
color: #d3832a !important;
margin-right: 10px !important;
cursor: pointer;
font-size: 12px !important;
&:nth-last-child(1) {
margin-right: 0px !important;
}
}
.el-table .el-input__inner {
height: 23px !important;
}
// .el-checkbox__input.is-checked .el-checkbox__inner{
// background-color: #D3832A !important;
// border-color: #D3832A !important;
// }
// .is .el-checkbox__input.is-checked .el-checkbox__inner{
// background-color: #D3832A !important;
// border-color: #D3832A !important;
// }
// .is-checked .el-checkbox__inner:hover{
// border-color: #D3832A !important;
// }
// .el-checkbox__input.is-indeterminate .el-checkbox__inner{
// background-color: #D3832A !important;
// border-color: #D3832A !important;
// }
.el-table .el-table__cell {
padding: 0px !important;
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
border-color: #d3832a !important;
background-color: #d3832a !important;
}
.el-checkbox__input.is-focus .el-checkbox__inner {
border-color: #d3832a !important;
}
//修改选中后文本的颜色
.el-checkbox__input.is-checked + .el-checkbox__label {
color: #d3832a !important;
}
.el-checkbox__inner:hover {
border-color: #d3832a !important;
font-size: 12px !important;
}
.table-SelectedRow-bgcolor {
> td {
background-color: #f7e8d7 !important;
}
}
.colors {
color: #409eff !important;
cursor: pointer !important;
}
.el-table .cell {
line-height: 22px !important;
}
.el-table .el-input__inner {
font-size: 12px !important;
}
.el-table__body tr.hover-row.current-row > td,
.el-table__body tr.hover-row.el-table__row--striped.current-row > td,
.el-table__body tr.hover-row.el-table__row--striped > td,
.el-table__body tr.hover-row > td {
background-color: #d3d8e1 !important;
}
:deep(.el-date-editor.el-input, .el-date-editor.el-input__wrapper) {
height: 23px;
}
.copys {
display: inline-block;
margin-left: 5px;
padding-top: 5px;
margin-right: 5px;
}
.tilbox {
display: flex;
align-items: center;
}
.el-table .el-popper {
max-width: 20% !important;
}
.el-input {
// width: 200px !important;
}
.el-table__footer {
tr {
background-color: var(--el-fill-color-light) !important;
}
}
.static-class {
padding: 0 4px;
scroll-behavior: smooth;
background-color: #ff0000;
color: #fff !important;
}
.el_placeholder{
width: 100%;
position: absolute;
margin-top: -12% !important;
z-index: 9999999;
left: 0;
}
</style>