This commit is contained in:
lcw
2026-03-14 19:46:21 +08:00
parent 93489b06fa
commit 9fb505eb8e
248 changed files with 21714 additions and 1840 deletions

View File

@ -0,0 +1,308 @@
<template>
<section class="query-wrap">
<div class="query-title">{{ title }}</div>
<div class="query-grid">
<div v-for="field in renderFields" :key="field.key" class="query-cell">
<div class="cell-label">{{ field.label }}</div>
<div class="cell-control" :class="{ 'is-checkbox': field.type === 'checkbox' }">
<el-input clearable v-if="field.type === 'input'" v-model="formState[field.key]" class="control-input"
:placeholder="field.placeholder || ''" />
<el-input clearable v-else-if="field.type === 'number'" v-model="formState[field.key]" class="control-input"
type="number" :placeholder="field.placeholder || ''" />
<el-select clearable v-else-if="field.type === 'select'" v-model="formState[field.key]" class="control-select"
:placeholder="field.placeholder || '请选择'" :multiple="field.multiple || false" collapse-tags
collapse-tags-tooltip>
<el-option v-for="item in field.options || []" :key="item.value ?? item" :label="item.label ?? item"
:value="item.value ?? item" />
</el-select>
<el-date-picker clearable v-else-if="field.type === 'date'" v-model="formState[field.key]"
class="control-date" type="date" :placeholder="field.placeholder || '请选择日期'" value-format="YYYY-MM-DD" />
<el-date-picker clearable v-else-if="field.type === 'datetime'" v-model="formState[field.key]"
class="control-date" type="datetime" :placeholder="field.placeholder || '请选择时间'"
value-format="YYYY-MM-DD HH:mm:ss" />
<el-date-picker clearable v-else-if="field.type === 'daterange'" v-model="formState[field.key]"
class="control-date" type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
value-format="YYYY-MM-DD" />
<el-date-picker clearable v-else-if="field.type === 'datetimerange'" v-model="formState[field.key]"
class="control-date" type="datetimerange" range-separator="" start-placeholder="开始时间"
end-placeholder="结束时间" value-format="YYYY-MM-DD HH:mm:ss" />
<template v-else-if="field.type === 'department'">
<MOSTY.Department clearable v-model="formState[field.key]" class="control-select" />
</template>
<div v-else-if="field.type === 'checkbox'" class=" checkbox-wrap">
<el-checkbox v-model="formState[field.key]" />
</div>
</div>
</div>
</div>
<div class="query-action">
<div>
<slot name="but"></slot>
</div>
<div>
<el-button size="small" type="primary" @click="handleSearch">{{ searchText }}</el-button>
<el-button size="small" type="button" @click="handleReset">重置 </el-button>
</div>
</div>
</section>
</template>
<script setup>
import { computed, reactive, watch } from 'vue'
import * as MOSTY from "@/components/MyComponents/index";
const props = defineProps({
title: {
type: String,
default: '查询条件'
},
searchText: {
type: String,
default: '查询'
},
fields: {
type: Array,
default: () => []
},
searchArr: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['search', 'submit', 'reset'])
const formState = reactive({})
const renderFields = computed(() => {
const source = props.searchArr.length ? props.searchArr : props.fields
return source.map((field) => ({
...field,
key: field.key || field.prop,
type: field.type || field.showType || 'input'
})).filter((field) => field.key)
})
const getResetValue = (field) => {
if (field.defaultVal !== undefined) {
return Array.isArray(field.defaultVal) ? [...field.defaultVal] : field.defaultVal
}
if (field.type === 'checkbox') {
return false
}
if (field.type === 'daterange' || field.type === 'datetimerange') {
return []
}
if (field.type === 'select') {
if (field.multiple) {
return []
}
return null
}
return ''
}
const buildResetPayload = () => {
const payload = {}
renderFields.value.forEach((field) => {
payload[field.key] = getResetValue(field)
})
return payload
}
const setFormState = (value = {}) => {
Object.keys(formState).forEach((key) => {
delete formState[key]
})
renderFields.value.forEach((field) => {
if (value[field.key] !== undefined) {
formState[field.key] = Array.isArray(value[field.key]) ? [...value[field.key]] : value[field.key]
return
}
formState[field.key] = getResetValue(field)
})
}
const getFormSnapshot = () => {
const snapshot = {}
renderFields.value.forEach((field) => {
const value = formState[field.key]
if (Array.isArray(value)) {
snapshot[field.key] = [...value]
return
}
snapshot[field.key] = value
})
return snapshot
}
const handleSearch = () => {
const payload = getFormSnapshot()
emit('search', payload)
emit('submit', payload)
}
const handleReset = () => {
const payload = buildResetPayload()
setFormState(payload)
emit('reset', true)
emit('search', payload)
emit('submit', payload)
}
watch(
renderFields,
() => {
setFormState(buildResetPayload())
},
{ immediate: true }
)
defineExpose({
formState,
handleSearch,
handleReset
})
</script>
<style scoped lang="scss">
.query-wrap {
border: 1px solid #b8d3ff;
background: #fff;
}
.query-title {
height: 32px;
background: linear-gradient(to right, #9ed7ff, #e6f0f8);
line-height: 32px;
padding-left: 10px;
font-size: 16px;
font-weight: 700;
color: #0d2148;
border-bottom: 1px solid #b8d3ff;
}
.query-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.query-cell {
display: flex;
min-height: 40px;
border-right: 1px solid #b8d3ff;
border-bottom: 1px solid #b8d3ff;
}
.query-cell:nth-child(4n) {
border-right: 0;
}
.cell-label {
width: 130px;
flex-shrink: 0;
border-right: 1px solid #b8d3ff;
font-size: 14px;
color: #0d2148;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
}
.cell-control {
flex: 1;
min-width: 0;
padding: 4px 8px;
display: flex;
align-items: center;
overflow: hidden;
}
.cell-control.is-checkbox {
justify-content: center;
padding: 0;
}
.control-input,
.control-select,
.control-date {
width: 100%;
max-width: 100%;
min-width: 0;
}
:deep(.control-input .el-input__wrapper),
:deep(.control-select .el-select__wrapper),
:deep(.control-date .el-input__wrapper) {
width: 100%;
max-width: 100%;
min-height: 28px;
border-radius: 0;
box-shadow: 0 0 0 1px #b8d3ff inset;
}
:deep(.control-date.el-date-editor) {
width: 100%;
max-width: 100%;
min-width: 0;
}
:deep(.control-date.el-date-editor .el-range-input) {
min-width: 0;
}
:deep(.control-input .el-input__inner),
:deep(.control-select .el-select__placeholder),
:deep(.control-date .el-input__inner) {
font-size: 14px;
color: #0d2148;
}
:deep(.control-date .el-range-input) {
font-size: 14px;
color: #0d2148;
}
:deep(.control-date .el-range__icon),
:deep(.control-date .el-range__close-icon) {
line-height: 1;
}
.checkbox-wrap {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
:deep(.checkbox-wrap .el-checkbox) {
margin-right: 0;
height: 100%;
display: flex;
align-items: center;
}
:deep(.checkbox-wrap .el-checkbox__inner) {
width: 14px;
height: 14px;
}
.query-action {
height: 36px;
display: flex;
// justify-content: flex-end;
justify-content: space-between;
align-items: center;
padding: 0 8px;
}
.search-btn {
height: 26px;
min-width: 70px;
border: 1px solid #0f5bbd;
background: #0f5bbd;
color: #fff;
font-size: 14px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<section class="table-wrap">
<div class="table-box">
<el-table ref="tableRef" v-loading="loading" :data="data" :row-key="rowKey" border stripe class="warn-data-table"
@selection-change="onSelectionChange" :height="height">
<el-table-column v-if="selectionMode !== 'none'" type="selection" :width="selectionColumnWidth"
:align="selectionColumnAlign" />
<el-table-column v-for="column in columns" :key="column.prop || column.label || column.type" :prop="column.prop"
:type="column.type" :label="column.label" :width="column.width" :min-width="column.minWidth"
:align="column.align" :show-overflow-tooltip="true">
<template v-if="column.slotName" #default="scope">
<slot v-if="column.slotName" :name="column.slotName" :row="scope.row" :column="column"
:$index="scope.$index">
{{ column.prop ? scope.row[column.prop] : '' }}
</slot>
</template>
</el-table-column>
</el-table>
</div>
</section>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
data: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
selectionMode: {
type: String,
default: 'multiple'
},
rowKey: {
type: [String, Function],
default: 'id'
},
selectionColumnWidth: {
type: Number,
default: 38
},
selectionColumnAlign: {
type: String,
default: 'center'
},
toolbarActions: {
type: Array,
default: () => [
{ key: 'sign', text: '批量签收', red: true },
{ key: 'export', text: '批量导出' },
{ key: 'analysis', text: '批量分析' },
{ key: 'count', text: '计算人数' }
]
},
tableHeight: {
type: [Number, String],
default: '600px'
},
loading: {
type: Boolean,
default: false
}
})
const height = ref()
watch(() => props.tableHeight, (newVal) => {
if (newVal) {
height.value = newVal
}
})
const emit = defineEmits(['selection-change', 'row-change'])
const tableRef = ref()
const onSelectionChange = (selection) => {
if (props.selectionMode === 'single' && selection.length > 1) {
const selectedRow = selection[selection.length - 1]
tableRef.value.clearSelection()
tableRef.value.toggleRowSelection(selectedRow, true)
emit('selection-change', [selectedRow])
emit('row-change', selectedRow)
return
}
emit('selection-change', selection)
emit('row-change', selection[0] || null)
}
</script>
<style scoped lang="scss">
.table-wrap {
margin-top: 10px;
background: #fff;
}
:deep(.el-table--fit) {
position: static;
// width: calc(100% - 20px) !important;
height: calc(100% - 100px);
}
.tool-bar {
height: 24px;
display: flex;
align-items: center;
gap: 16px;
font-size: 14px;
color: #1f56ab;
}
.tool-link {
cursor: pointer;
}
.tool-link.red {
color: #e51414;
}
.table-box {
border: 1px solid #b5ccf2;
overflow: hidden;
}
:deep(.warn-data-table) {
width: 100%;
}
:deep(.warn-data-table .el-table__inner-wrapper::before) {
display: none;
}
:deep(.warn-data-table .el-table__header-wrapper th.el-table__cell) {
background: #16263e !important;
color: #fff !important;
border-color: #b5ccf2;
padding:0;
}
:deep(.warn-data-table .el-table__header-wrapper th.el-table__cell .cell) {
text-align: center;
overflow: visible;
white-space: normal;
text-overflow: clip;
word-break: break-word;
}
:deep(.warn-data-table .el-table__body-wrapper td.el-table__cell) {
border-color: #b5ccf2;
padding: 3px 0;
}
:deep(.warn-data-table .el-table__cell) {
font-size: 13px;
color: #0f223f;
}
:deep(.warn-data-table .el-table__cell .cell) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.warn-data-table.el-table--border::after),
:deep(.warn-data-table.el-table--group::after),
:deep(.warn-data-table::before) {
background-color: #b5ccf2;
}
</style>