Files
klp-oa/klp-ui/src/views/aps/quickSheet.vue
2026-03-14 15:06:18 +08:00

749 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="aps-quick-sheet excel-theme">
<div class="sheet-toolbar">
<div class="sheet-actions">
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
<el-button size="small" type="primary" icon="el-icon-finished" :loading="saving" @click="saveAll">保存</el-button>
<el-button size="small" icon="el-icon-plus" @click="addRow">新增行</el-button>
<el-button size="small" icon="el-icon-download" @click="exportCsv">导出</el-button>
<el-date-picker
v-model="filter.range"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
size="small"
style="width: 240px"
@change="onFilterChange"
/>
</div>
<div class="preset-bar">
<span class="preset-title">预设方案</span>
<el-button
v-for="line in lineOptions"
:key="line.lineId"
size="mini"
plain
@click="applyPreset(line)"
>
{{ line.lineName || line.lineCode || ('产线' + line.lineId) }}
</el-button>
</div>
</div>
<div class="sheet-body">
<el-table
v-loading="loading"
:data="displayRows"
border
size="mini"
class="excel-table"
>
<el-table-column label="序号" width="60" align="center">
<template slot-scope="scope">
{{ ((pager.pageNum - 1) * pager.pageSize) + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
v-for="col in flatColumns"
:key="col.prop || col.label"
:label="col.label"
:prop="col.prop"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
>
<template slot-scope="scope">
<template v-if="col.prop === 'rawMaterialId'">
<el-button
size="mini"
type="text"
class="coil-picker-btn"
@click="openCoilPicker(scope.row)"
>
{{ scope.row.rawCoilNos || '选择原料钢卷' }}
</el-button>
</template>
<template v-else-if="isTimeField(col.prop)">
<el-date-picker
v-model="scope.row[col.prop]"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
size="mini"
style="width: 100%"
@change="onCellChange(scope.row)"
@input="onCellChange(scope.row)"
/>
</template>
<template v-else>
<el-autocomplete
v-if="col.prop === 'lineName'"
v-model="scope.row[col.prop]"
size="mini"
clearable
:fetch-suggestions="queryLineHistory"
@select="onLineHistorySelect(scope.row)"
@input="onLineHistoryInput(scope.row)"
/>
<el-input
v-else
v-model="scope.row[col.prop]"
size="mini"
clearable
@input="onCellChange(scope.row)"
/>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="70" fixed="right" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="clearRow(scope.row)">清空</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager-wrap">
<el-pagination
small
background
layout="prev, pager, next, total"
:current-page.sync="pager.pageNum"
:page-size="pager.pageSize"
:total="totalRows"
/>
</div>
</div>
<div v-if="missingRows.length" class="missing-tip">
<div v-for="msg in missingRows" :key="msg" class="missing-item">{{ msg }}</div>
</div>
<el-dialog title="选择原料钢卷" :visible.sync="coilPicker.visible" width="1280px" append-to-body>
<el-form :inline="true" size="small" class="coil-picker-form">
<el-form-item label="库区">
<el-select v-model="coilPicker.selectedWarehouseId" clearable filterable placeholder="全部库区" style="width: 220px" @change="onCoilFilterChange">
<el-option
v-for="w in coilPicker.warehouseOptions"
:key="w.actualWarehouseId"
:label="w.actualWarehouseName"
:value="w.actualWarehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="入场钢卷号">
<el-input v-model="coilPicker.enterCoilNo" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item label="当前钢卷号">
<el-input v-model="coilPicker.currentCoilNo" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item label="厂家">
<el-input v-model="coilPicker.manufacturer" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item>
<el-button size="mini" type="primary" @click="searchCoils">查询</el-button>
</el-form-item>
</el-form>
<div class="coil-stack-wrap" v-loading="coilPicker.loading">
<el-empty v-if="!coilColumnKeys.length" description="暂无可选原料钢卷" :image-size="60" />
<div v-else class="coil-bird-wrap">
<div class="coil-legend">
<span class="legend-item"><i class="dot layer1" />一层</span>
<span class="legend-item"><i class="dot layer2" />二层</span>
<span class="legend-item"><i class="dot occupied" />已占用</span>
</div>
<div class="coil-grid-scroll">
<div class="coil-col-ruler">
<div class="ruler-empty" />
<div v-for="col in coilColumnKeys" :key="`ruler-${col}`" class="ruler-col">{{ col }}</div>
</div>
<div class="coil-grid-main">
<div class="coil-row-ruler">
<div v-for="row in coilMaxRow" :key="`row-${row}`" class="ruler-row">{{ row }}</div>
</div>
<div class="coil-grid-columns">
<div v-for="col in coilColumnKeys" :key="`col-${col}`" class="coil-col-pair">
<div class="coil-layer">
<div
v-for="row in coilMaxRow"
:key="`l1-${col}-${row}`"
class="coil-cell layer1"
:class="{ occupied: !!(getCoilCell(col, row, 1) && getCoilCell(col, row, 1).rawMaterialId) }"
@click="selectCoilCell(col, row, 1)"
>
<template v-if="getCoilCell(col, row, 1)">
<div class="line1" :title="getCoilCell(col, row, 1).rawLocation">{{ getCoilCell(col, row, 1).rawLocation }}</div>
<div class="line2" :title="getCoilCell(col, row, 1).rawMaterialCode">{{ getCoilCell(col, row, 1).rawMaterialCode || '-' }}</div>
</template>
<template v-else>-</template>
</div>
</div>
<div class="coil-layer layer2-shift">
<div
v-for="row in (coilMaxRow - 1)"
:key="`l2-${col}-${row}`"
class="coil-cell layer2"
:class="{ occupied: !!(getCoilCell(col, row, 2) && getCoilCell(col, row, 2).rawMaterialId) }"
@click="selectCoilCell(col, row, 2)"
>
<template v-if="getCoilCell(col, row, 2)">
<div class="line1" :title="getCoilCell(col, row, 2).rawLocation">{{ getCoilCell(col, row, 2).rawLocation }}</div>
<div class="line2" :title="getCoilCell(col, row, 2).rawMaterialCode">{{ getCoilCell(col, row, 2).rawMaterialCode || '-' }}</div>
</template>
<template v-else>-</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="coil-picker-foot"> {{ coilPicker.total || 0 }} 条钢卷按库位分布展示</div>
</el-dialog>
</div>
</template>
<script>
import { fetchQuickSheetList, fetchQuickSheetPreset, saveQuickSheet, exportQuickSheet, deleteQuickSheetRow } from '@/api/aps/quickSheet'
import { listProductionLine } from '@/api/wms/productionLine'
import { getMaterialCoilLocationGrid } from '@/api/wms/coil'
import { treeActualWarehouseTwoLevel } from '@/api/wms/actualWarehouse'
import { getTemplateByKey } from './sheets/templates'
import { saveAs } from 'file-saver'
export default {
name: 'ApsQuickSheet',
data() {
return {
loading: false,
saving: false,
rows: [],
lineOptions: [],
lineHistory: [],
dirtyIds: new Set(),
autoSaveTimer: null,
templateKey: 'unified',
filter: {
range: []
},
pager: {
pageNum: 1,
pageSize: 25
},
coilPicker: {
visible: false,
loading: false,
currentRow: null,
selectedWarehouseId: undefined,
enterCoilNo: '',
currentCoilNo: '',
manufacturer: '',
warehouseOptions: [],
coilGrid: {},
total: 0
}
}
},
computed: {
currentTemplate() {
return getTemplateByKey(this.templateKey)
},
flatColumns() {
const res = []
const loop = (cols) => {
;(cols || []).forEach(c => {
if (c.children && c.children.length) loop(c.children)
else res.push(c)
})
}
loop(this.currentTemplate.columns || [])
return res
},
displayRows() {
const [startAfter, endBefore] = this.filter.range || []
const filtered = this.rows.filter(r => {
if (!r.startTime) return true
const d = String(r.startTime).slice(0, 10)
if (startAfter && d < startAfter) return false
if (endBefore && d > endBefore) return false
return true
})
const start = (this.pager.pageNum - 1) * this.pager.pageSize
const page = filtered.slice(start, start + this.pager.pageSize)
if (page.length < this.pager.pageSize) {
return page.concat(this.buildEmptyRows(this.pager.pageSize - page.length))
}
return page
},
totalRows() {
const [startAfter, endBefore] = this.filter.range || []
return this.rows.filter(r => {
if (!r.startTime) return true
const d = String(r.startTime).slice(0, 10)
if (startAfter && d < startAfter) return false
if (endBefore && d > endBefore) return false
return true
}).length
},
missingRows() {
return this.collectMissing(this.displayRows)
},
coilColumnKeys() {
const keys = Object.keys(this.coilPicker.coilGrid || {})
return keys.map(v => Number(v)).filter(v => !Number.isNaN(v)).sort((a, b) => a - b)
},
coilMaxRow() {
let max = 0
Object.values(this.coilPicker.coilGrid || {}).forEach(col => {
const l1 = col.layer1 || []
const l2 = col.layer2 || []
;[...l1, ...l2].forEach(item => {
const row = Number(item._row || 0)
if (row > max) max = row
})
})
return max || 1
}
},
created() {
const today = new Date()
const start = `${today.getFullYear()}-${`${today.getMonth() + 1}`.padStart(2, '0')}-${`${today.getDate()}`.padStart(2, '0')}`
this.filter.range = [start, '']
this.loadRows()
this.loadLines()
},
beforeDestroy() {
if (this.autoSaveTimer) clearTimeout(this.autoSaveTimer)
},
methods: {
async loadLines() {
const res = await listProductionLine({ pageNum: 1, pageSize: 1000 })
this.lineOptions = res.rows || []
},
async loadRows() {
this.loading = true
try {
const res = await fetchQuickSheetList()
this.rows = (res.data || []).map(r => this.normalizeRow(r))
this.lineHistory = this.rows
.map(r => r.lineName)
.filter(v => v && v.trim())
.filter((v, i, arr) => arr.indexOf(v) === i)
} finally {
this.loading = false
}
},
buildEmptyRows(count) {
return Array.from({ length: count }).map(() => this.buildEmptyRow())
},
async applyPreset(line) {
if (!line || !line.lineId) return
const res = await fetchQuickSheetPreset({ lineId: line.lineId })
const preset = res.data || []
if (!preset.length) return
const targetRow = this.rows.find(r => !r.lineId && !r.lineName) || this.buildEmptyRow()
const base = this.normalizeRow(preset[0])
Object.keys(base).forEach(k => {
if (k === 'quickSheetId') return
targetRow[k] = base[k]
})
targetRow.lineId = line.lineId
targetRow.lineName = line.lineName || line.lineCode || ''
if (!this.rows.includes(targetRow)) {
this.rows.push(targetRow)
}
if (this.rows.length < 25) {
this.rows = this.rows.concat(this.buildEmptyRows(25 - this.rows.length))
}
this.markDirty(targetRow)
this.scheduleAutoSave()
},
async loadWarehouseOptions() {
const res = await treeActualWarehouseTwoLevel()
const tree = res.data || []
const leaf = []
tree.forEach(p => {
;(p.children || []).forEach(c => {
leaf.push({
actualWarehouseId: c.actualWarehouseId,
actualWarehouseName: `${p.actualWarehouseName || ''}/${c.actualWarehouseName || ''}`
})
})
})
this.coilPicker.warehouseOptions = leaf
},
async loadCoilsByWarehouse() {
if (!this.coilPicker.selectedWarehouseId) {
this.coilPicker.coilGrid = {}
this.coilPicker.total = 0
return
}
this.coilPicker.loading = true
try {
const gridRes = await getMaterialCoilLocationGrid({
itemType: 'raw_material',
actualWarehouseId: this.coilPicker.selectedWarehouseId,
enterCoilNo: this.coilPicker.enterCoilNo || undefined,
currentCoilNo: this.coilPicker.currentCoilNo || undefined,
manufacturer: this.coilPicker.manufacturer || undefined
})
const data = gridRes.data || {}
const warehouses = data.warehouses || []
const coils = (data.coils || []).map(item => ({
rawMaterialId: item.coilId,
rawMaterialCode: item.currentCoilNo || item.enterCoilNo || item.coilNo || '',
rawMaterialName: item.itemName || item.materialName || '',
specification: item.specification || '',
coilWeight: item.netWeight,
packageType: item.packagingRequirement || item.packageType,
edgeType: item.trimmingRequirement || item.edgeType,
zincLayer: item.coatingType || item.zincLayer,
surfaceTreatmentDesc: item.surfaceTreatmentDesc,
actualWarehouseId: item.actualWarehouseId,
actualWarehouseName: item.actualWarehouseName || '未分配库位',
rawLocation: item.actualWarehouseName || '未分配库位'
}))
this.coilPicker.total = coils.length
const coilByWarehouseId = {}
coils.forEach(c => {
if (c.actualWarehouseId && !coilByWarehouseId[c.actualWarehouseId]) {
coilByWarehouseId[c.actualWarehouseId] = c
}
})
const grid = {}
warehouses.forEach(w => {
const code = String(w.actualWarehouseCode || '')
const m = code.match(/^([A-Za-z0-9]{3})([^-]+)-(\d{2})-(\d+)$/)
if (!m) return
const col = Number(m[2])
const row = Number(m[3])
const layer = Number(m[4])
if (!grid[col]) grid[col] = { layer1: [], layer2: [] }
const coil = coilByWarehouseId[w.actualWarehouseId] || null
const cell = {
_col: col,
_row: row,
_layer: layer,
actualWarehouseId: w.actualWarehouseId,
actualWarehouseCode: w.actualWarehouseCode,
rawLocation: w.actualWarehouseName || w.actualWarehouseCode,
...(coil || {})
}
if (layer === 1) grid[col].layer1.push(cell)
else if (layer === 2) grid[col].layer2.push(cell)
})
Object.keys(grid).forEach(k => {
grid[k].layer1.sort((a, b) => Number(a._row) - Number(b._row))
grid[k].layer2.sort((a, b) => Number(a._row) - Number(b._row))
})
this.coilPicker.coilGrid = grid
} finally {
this.coilPicker.loading = false
}
},
searchCoils() {
this.loadCoilsByWarehouse()
},
onCoilFilterChange() {
this.loadCoilsByWarehouse()
},
openCoilPicker(row) {
this.coilPicker.currentRow = row
this.coilPicker.visible = true
this.loadWarehouseOptions().then(() => {
if (!this.coilPicker.selectedWarehouseId && this.coilPicker.warehouseOptions.length) {
this.coilPicker.selectedWarehouseId = this.coilPicker.warehouseOptions[0].actualWarehouseId
}
this.loadCoilsByWarehouse()
})
},
getCoilCell(col, row, layer) {
const c = this.coilPicker.coilGrid[col]
if (!c) return null
const list = layer === 1 ? (c.layer1 || []) : (c.layer2 || [])
return list.find(i => Number(i._row) === Number(row)) || null
},
selectCoilCell(col, row, layer) {
const item = this.getCoilCell(col, row, layer)
if (!item) return
if (!item.rawMaterialId) {
this.$message.warning('该库位当前无钢卷')
return
}
this.pickRawMaterial(item)
},
pickRawMaterial(item) {
const row = this.coilPicker.currentRow
if (!row) return
this.$set(row, 'rawMaterialId', item.rawMaterialId)
this.$set(row, 'rawCoilNos', item.rawMaterialCode || '')
this.$set(row, 'rawNetWeight', item.coilWeight != null ? item.coilWeight : '')
this.$set(row, 'rawPackaging', item.packageType || '')
this.$set(row, 'rawEdgeReq', item.edgeType || '')
this.$set(row, 'rawCoatingType', item.zincLayer || item.surfaceTreatmentDesc || '')
this.$set(row, 'rawLocation', item.rawLocation || '')
this.coilPicker.visible = false
this.onCellChange(row)
},
onCellChange(row) {
if (row && !this.rows.includes(row)) {
this.rows.push(row)
}
this.markDirty(row)
this.scheduleAutoSave()
},
queryLineHistory(query, cb) {
const list = this.lineHistory || []
const q = (query || '').trim().toLowerCase()
const res = list
.filter(item => !q || String(item).toLowerCase().includes(q))
.map(item => ({ value: item }))
cb(res)
},
onLineHistoryInput(row) {
if (!row) return
if (row.lineId && row.lineName && row.lineName.trim() !== String(row.lineName).trim()) {
row.lineId = null
}
this.onCellChange(row)
},
onLineHistorySelect(row) {
return () => {
this.onCellChange(row)
}
},
onFilterChange() {
this.pager.pageNum = 1
},
normalizeRow(row) {
const base = this.buildEmptyRow()
Object.keys(base).forEach(k => {
if (row && row[k] !== undefined && row[k] !== null) base[k] = row[k]
})
const idVal = row ? (row.quickSheetId !== undefined && row.quickSheetId !== null ? row.quickSheetId : row.id) : null
base.quickSheetId = idVal != null ? idVal : null
return base
},
buildEmptyRow() {
const row = { quickSheetId: null }
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
return row
},
isRowEmpty(row) {
if (!row) return true
return !this.flatColumns.some(col => col.prop && row[col.prop])
},
isTimeField(prop) {
return prop === 'startTime' || prop === 'endTime'
},
markDirty(row) {
if (!row) return
if (!row._tmpId) row._tmpId = Math.random().toString(36).slice(2)
this.dirtyIds.add(row._tmpId)
},
async clearRow(row) {
if (!row) return
if (!row.quickSheetId) {
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
row.quickSheetId = null
return
}
await deleteQuickSheetRow(row.quickSheetId)
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
row.quickSheetId = null
},
scheduleAutoSave() {
if (this.autoSaveTimer) clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = setTimeout(() => {
this.saveAll(true)
}, 800)
},
async saveAll(isAuto = false, payload) {
const rows = payload && payload.rows
? payload.rows
: this.rows.filter(r => r && r._tmpId && this.dirtyIds.has(r._tmpId))
.map(r => ({
...r,
quickSheetId: r.quickSheetId || null
}))
if (!rows.length) {
if (!isAuto) this.$message.warning('暂无可保存数据')
return
}
const missing = this.collectMissing(rows)
if (missing.length && !isAuto) {
this.$message.warning(`必填项未填写:${missing.join('')}`)
return
}
this.saving = true
try {
await saveQuickSheet({ rows })
if (!isAuto) this.$message.success('保存成功')
rows.forEach(r => {
if (r._tmpId) this.dirtyIds.delete(r._tmpId)
})
} finally {
this.saving = false
}
},
addRow() {
this.rows.push(this.buildEmptyRow())
if (this.rows.length < this.pager.pageSize) {
this.rows = this.rows.concat(this.buildEmptyRows(this.pager.pageSize - this.rows.length))
}
},
onFilterChange() {
this.pager.pageNum = 1
},
exportCsv() {
this.loading = true
exportQuickSheet()
.then(blob => {
const filename = `quick_sheet_${new Date().getTime()}.xlsx`
saveAs(new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), filename)
})
.finally(() => {
this.loading = false
})
},
addRow() {
this.rows.unshift(this.buildEmptyRow())
this.pager.pageNum = 1
},
collectMissing(rows) {
const msgs = []
rows.forEach((row, idx) => {
if (this.isRowEmpty(row)) return
const line = idx + 1
const missing = []
if (!row.lineName && !row.lineId) missing.push('产线')
if (!row.planCode) missing.push('计划号')
if (!row.startTime) missing.push('开始时间')
if (!row.endTime) missing.push('结束时间')
if (missing.length) {
msgs.push(`${line}行缺少${missing.join('、')}`)
}
})
return msgs
}
}
}
</script>
<style scoped lang="scss">
.aps-quick-sheet {
padding: 8px;
background: #f7f9fc;
}
.sheet-toolbar,
.sheet-body {
border: 1px solid #eef2f7;
border-radius: 8px;
background: #fff;
}
.sheet-toolbar {
margin-bottom: 8px;
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.sheet-actions { display: flex; gap: 8px; }
.preset-bar { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #667085; }
.preset-title { font-weight: 600; color: #475467; }
::v-deep .excel-table {
border: 1px solid #edf1f7;
}
::v-deep .excel-table th.el-table__cell {
background: #fafbfe;
color: #5d6b82;
font-weight: 600;
border-right: 1px solid #edf1f7;
border-bottom: 1px solid #edf1f7;
padding: 4px 0;
}
::v-deep .excel-table td.el-table__cell {
border-right: 1px solid #f0f2f6;
border-bottom: 1px solid #f0f2f6;
padding: 1px 2px;
}
::v-deep .excel-table .el-table__row:hover > td {
background-color: #f7faff !important;
}
::v-deep .el-input__inner {
border-radius: 0;
height: 26px;
line-height: 26px;
border: none;
box-shadow: none;
background: transparent;
}
::v-deep .el-input__inner:focus {
border: none;
box-shadow: inset 0 0 0 1px #4a90e2;
background: #fff;
}
.missing-tip {
margin-top: 8px;
padding: 8px 12px;
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 6px;
font-size: 12px;
color: #d46b08;
}
.missing-item {
line-height: 1.6;
}
.coil-picker-btn {
padding: 0;
}
.coil-bird-wrap { border: 1px solid #ebeef5; border-radius: 6px; padding: 8px; }
.coil-legend { display: flex; gap: 14px; font-size: 12px; color: #606266; margin-bottom: 8px; }
.legend-item { display: inline-flex; align-items: center; gap: 4px; }
.dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.dot.layer1 { background: #fff3e0; border: 1px solid #f5d7a1; }
.dot.layer2 { background: #e8f5e9; border: 1px solid #b7ddb9; }
.dot.occupied { background: #f6f8fb; border: 1px solid #d7dde5; }
.coil-grid-scroll { overflow: auto; max-height: 460px; }
.coil-col-ruler { display: flex; min-width: max-content; }
.ruler-empty { width: 28px; flex: none; }
.ruler-col { width: 138px; text-align: center; font-size: 12px; color: #606266; }
.coil-grid-main { display: flex; min-width: max-content; }
.coil-row-ruler { width: 28px; display: flex; flex-direction: column; }
.ruler-row { height: 56px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #909399; }
.coil-grid-columns { display: flex; gap: 8px; }
.coil-col-pair { display: grid; grid-template-columns: 1fr 1fr; width: 180px; border: 1px solid #eef2f7; border-radius: 4px; overflow: visible; }
.coil-layer { display: flex; flex-direction: column; }
.coil-layer.layer2-shift { margin-top: 28px; }
.coil-cell { height: 56px; border-bottom: 1px solid #eef2f7; border-right: 1px solid #eef2f7; padding: 4px; font-size: 11px; color: #909399; display: flex; align-items: center; justify-content: center; text-align: center; cursor: pointer; }
.coil-cell.layer1 { background: #fff3e0; color: #e67e22; }
.coil-cell.layer2 { background: #e8f5e9; color: #2e8b57; border-right: none; }
.coil-cell.occupied { font-weight: 600; }
.coil-cell .line1 { width: 100%; font-size: 11px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coil-cell .line2 { width: 100%; font-size: 11px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coil-picker-foot { margin-top: 8px; text-align: right; color: #909399; font-size: 12px; }
</style>