Files
klp-oa/klp-ui/src/views/wms/post/aps/requirement.vue
砂糖 289555fd44 feat(aps): 新增APS排产管理模块完整功能
本次提交完成APS(高级计划与排程)模块的全量开发:
1.  新增CRM订单相关API接口,包含列表、详情、明细查询
2.  新增产需单相关CRUD API与页面,支持排产单管理与订单绑定
3.  新增按日期查询排产单、订单下钻详情页面
4.  为排产单实体类添加日期格式化注解,修复参数绑定问题
5.  统一封装APS模块主题样式,提供通用混入与变量
6.  实现产需单与销售订单的绑定解绑、明细自动生成功能
2026-06-25 15:44:26 +08:00

1023 lines
33 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-req-page">
<el-row :gutter="20" class="aps-req-row">
<!-- 左侧产需单列表 -->
<el-col :span="8" class="aps-req-left" v-loading="reqLoading">
<!-- 筛选区 -->
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
<el-input v-model="queryParams.scheduleNo" placeholder="排产单号" clearable @keyup.enter.native="handleSearch"
style="width: 130px;" size="small" />
<el-input v-model="queryParams.customerName" placeholder="客户名" clearable
@keyup.enter.native="handleSearch" style="width: 130px;" size="small" />
<el-date-picker v-model="queryParams.prodDate" type="date" placeholder="生产日期" value-format="yyyy-MM-dd"
style="width: 130px;" size="small" clearable @change="handleSearch" />
<el-button class="aps-btn-red" icon="el-icon-search" size="mini" @click="handleSearch">筛选</el-button>
<el-button class="aps-btn-red" icon="el-icon-plus" size="mini" @click="handleAdd"></el-button>
</div>
</div>
</div>
<div class="panel-header">
<span>产需单列表</span>
<span style="font-weight:normal;color:var(--text-muted);font-size:11px;"> {{ total }} </span>
</div>
<div class="list-container">
<div v-for="item in reqList" :key="item.scheduleId" class="list-item"
:class="{ active: currentReq && currentReq.scheduleId === item.scheduleId }" @click="handleReqClick(item)">
<div class="item-title">
{{ item.scheduleNo }}
<span class="badge" :class="'badge-' + statusBadgeType(item.scheduleStatus)">{{
statusMap[item.scheduleStatus] || '未知' }}</span>
</div>
<div class="item-sub">
生产日期{{ item.prodDate || '-' }} 客户{{ item.customerName || '-' }} 业务员{{ item.businessUser || '-' }}
</div>
<div class="item-actions">
<button class="link-btn" @click.stop="handleDelete(item)">删除</button>
</div>
</div>
<div v-if="reqList.length === 0 && !reqLoading" style="padding: 40px; text-align: center; color: #909399;">
暂无产需单数据
</div>
</div>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList"
style="padding: 10px; margin-bottom: 10px !important;" />
</el-col>
<!-- 右侧详情区 -->
<el-col :span="16" class="aps-req-right">
<el-empty v-if="!currentReq" description="选择产需单查看更多信息" />
<div v-else class="detail-panel">
<div class="detail-card">
<div class="detail-card-header">
<span>产需单信息</span>
<button class="header-btn" @click="handleEdit(currentReq)">编辑</button>
</div>
<div class="detail-card-body">
<table class="req-info-table">
<tbody>
<tr>
<td class="req-td-label">排产单号</td>
<td class="req-td-value">{{ currentReq.scheduleNo }}</td>
<td class="req-td-label">生产日期</td>
<td class="req-td-value">{{ currentReq.prodDate }}</td>
</tr>
<tr>
<td class="req-td-label">排产状态</td>
<td class="req-td-value"><span class="aps-status-tag"
:class="'status-' + (currentReq.scheduleStatus || 0)">{{ statusMap[currentReq.scheduleStatus] ||
'未知' }}</span></td>
<td class="req-td-label">业务员</td>
<td class="req-td-value">{{ currentReq.businessUser }}</td>
</tr>
<tr>
<td class="req-td-label">联系电话</td>
<td class="req-td-value">{{ currentReq.businessPhone }}</td>
<td class="req-td-label">订货单位</td>
<td class="req-td-value">{{ currentReq.customerName }}</td>
</tr>
<tr>
<td class="req-td-label">交货期()</td>
<td class="req-td-value">{{ currentReq.deliveryCycle }}</td>
<td class="req-td-label">产品用途</td>
<td class="req-td-value">{{ currentReq.usePurpose }}</td>
</tr>
<tr>
<td class="req-td-label">品名</td>
<td class="req-td-value" colspan="3">{{ currentReq.productType }}</td>
</tr>
<tr>
<td class="req-td-label">厚度公差</td>
<td class="req-td-value">{{ currentReq.thicknessTolerance }}</td>
<td class="req-td-label">宽度公差</td>
<td class="req-td-value">{{ currentReq.widthTolerance }}</td>
</tr>
<tr>
<td class="req-td-label">表面质量</td>
<td class="req-td-value">{{ currentReq.surfaceQuality }}</td>
<td class="req-td-label">表面处理</td>
<td class="req-td-value">{{ currentReq.surfaceTreatment }}</td>
</tr>
<tr>
<td class="req-td-label">内径尺寸</td>
<td class="req-td-value">{{ currentReq.innerDiameter }}</td>
<td class="req-td-label">外径要求</td>
<td class="req-td-value">{{ currentReq.outerDiameter }}</td>
</tr>
<tr>
<td class="req-td-label">包装要求</td>
<td class="req-td-value">{{ currentReq.packReq }}</td>
<td class="req-td-label">切边要求</td>
<td class="req-td-value">{{ currentReq.cutEdgeReq }}</td>
</tr>
<tr>
<td class="req-td-label">单件重量</td>
<td class="req-td-value">{{ currentReq.singleCoilWeight }}</td>
<td class="req-td-label">交货重量偏差</td>
<td class="req-td-value">{{ currentReq.weightDeviation }}</td>
</tr>
<tr>
<td class="req-td-label">其他技术要求</td>
<td class="req-td-value" colspan="3">{{ currentReq.otherTechReq }}</td>
</tr>
<tr>
<td class="req-td-label">付款情况说明</td>
<td class="req-td-value" colspan="3">{{ currentReq.paymentDesc }}</td>
</tr>
<tr>
<td class="req-td-label">备注</td>
<td class="req-td-value" colspan="3">{{ currentReq.remark }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="detail-card">
<div class="detail-card-header">
<span>已绑定的销售订单{{ boundOrderList.length }} </span>
<button class="header-btn" @click="openBindDialog">+ 添加订单</button>
</div>
<div class="detail-card-body" v-loading="boundLoading">
<div v-if="(currentReq.orderList || []).length === 0"
style="padding: 16px; text-align: center; color: #909399;">暂无绑定订单</div>
<div v-else class="bound-order-cards">
<div v-for="order in currentReq.orderList" :key="order.orderId" class="bound-order-card">
<div class="bound-order-card-header">
<span>{{ order.orderCode || ('订单 #' + order.orderId) }}</span>
<button class="link-btn" @click="handleUnbindByOrderId(order.orderId)">解绑</button>
</div>
<div class="bound-order-card-body">
<div style="font-size:12px; color:#7f8c8d; margin-bottom:3px;">
客户<span style="color:#2c3e50;">{{ order.companyName || '-' }}</span>
</div>
<div style="font-size:12px; color:#7f8c8d; margin-bottom:3px;">
销售员<span style="color:#2c3e50;">{{ order.salesman || '-' }}</span>
</div>
<div style="font-size:12px; color:#7f8c8d;">
交货日期<span style="color:#2c3e50;">{{ order.deliveryDate || '-' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="detail-card">
<div class="detail-card-header">
<span>排产明细{{ requirementDetailList.length }} 合计 {{ detailTotalWeight }} T</span>
</div>
<div class="detail-card-body" style="padding:0;">
<el-table :data="requirementDetailList" border size="small" v-loading="detailLoading" class="aps-table">
<el-table-column label="规格" prop="spec" min-width="120" />
<el-table-column label="材质" prop="material" width="90" align="center" />
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" />
<el-table-column label="备注" prop="remark" min-width="120" />
</el-table>
<el-empty v-if="requirementDetailList.length === 0 && !detailLoading" description="暂无排产明细" />
</div>
</div>
</div>
</el-col>
</el-row>
<!-- 新增/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="700px" append-to-body
:close-on-click-modal="false">
<el-form ref="reqForm" :model="reqForm" :rules="reqRules" label-width="110px" size="small">
<!-- 第一行必填项 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="排产单号" prop="scheduleNo">
<el-input v-model="reqForm.scheduleNo" placeholder="自动生成或手动填写" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生产日期" prop="prodDate">
<el-date-picker v-model="reqForm.prodDate" type="date" value-format="yyyy-MM-dd" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<!-- 基本信息 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="订货单位" prop="customerName">
<el-input v-model="reqForm.customerName" />
</el-form-item>
<el-form-item label="业务员" prop="businessUser">
<el-input v-model="reqForm.businessUser" />
</el-form-item>
<el-form-item label="联系电话" prop="businessPhone">
<el-input v-model="reqForm.businessPhone" />
</el-form-item>
<el-form-item label="品名" prop="productType">
<el-input v-model="reqForm.productType" />
</el-form-item>
<el-form-item label="交货期(天)" prop="deliveryCycle">
<el-input-number v-model="reqForm.deliveryCycle" :min="0" :max="999" style="width:100%" />
</el-form-item>
<el-form-item label="产品用途" prop="usePurpose">
<el-input v-model="reqForm.usePurpose" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厚度公差" prop="thicknessTolerance">
<el-input v-model="reqForm.thicknessTolerance" />
</el-form-item>
<el-form-item label="宽度公差" prop="widthTolerance">
<el-input v-model="reqForm.widthTolerance" />
</el-form-item>
<el-form-item label="表面质量" prop="surfaceQuality">
<el-input v-model="reqForm.surfaceQuality" />
</el-form-item>
<el-form-item label="表面处理" prop="surfaceTreatment">
<el-input v-model="reqForm.surfaceTreatment" />
</el-form-item>
<el-form-item label="内径尺寸" prop="innerDiameter">
<el-input v-model="reqForm.innerDiameter" />
</el-form-item>
<el-form-item label="外径要求" prop="outerDiameter">
<el-input v-model="reqForm.outerDiameter" />
</el-form-item>
</el-col>
</el-row>
<!-- 包装及其他 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="包装要求" prop="packReq">
<el-input v-model="reqForm.packReq" />
</el-form-item>
<el-form-item label="切边要求" prop="cutEdgeReq">
<el-input v-model="reqForm.cutEdgeReq" />
</el-form-item>
<el-form-item label="单件重量" prop="singleCoilWeight">
<el-input v-model="reqForm.singleCoilWeight" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交货重量偏差" prop="weightDeviation">
<el-input v-model="reqForm.weightDeviation" />
</el-form-item>
<el-form-item label="其他技术要求" prop="otherTechReq">
<el-input v-model="reqForm.otherTechReq" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="付款情况说明" prop="paymentDesc">
<el-input v-model="reqForm.paymentDesc" type="textarea" :rows="2" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="reqForm.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="btnLoading" type="danger" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</div>
</el-dialog>
<!-- 绑定订单对话框 -->
<el-dialog title="添加绑定订单" :visible.sync="bindDialogVisible" width="860px" append-to-body
:close-on-click-modal="false" class="aps-bind-dialog">
<div style="display: flex; gap: 16px; height: 420px;">
<!-- 左侧订单列表 -->
<div style="width: 45%; display: flex; flex-direction: column;">
<div style="display: flex; gap: 6px; margin-bottom: 10px;">
<el-input v-model="bindQuery.keyword" placeholder="搜索订单编号/客户名" size="small" clearable style="flex:1;"
@change="searchBindOrders" />
<el-button size="small" class="aps-btn-red" icon="el-icon-search" @click="searchBindOrders">搜索</el-button>
</div>
<div style="flex:1; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px;"
v-loading="bindSearchLoading" element-loading-text="搜索中...">
<div v-for="item in candidateOrderList" :key="item.orderId" class="bind-order-item"
:class="{ selected: isBindSelected(item), previewing: bindPreviewOrder && bindPreviewOrder.orderId === item.orderId }"
@click="handleBindRowClick(item)">
<el-checkbox :value="isBindSelected(item)" @change="toggleBindOrder(item)" @click.stop
style="margin-right: 8px;" />
<div style="flex:1; min-width:0;">
<div style="font-weight:500; font-size:13px; color:#2c3e50;">{{ item.orderCode }}</div>
<div style="font-size:11px; color:#7f8c8d; margin-top:2px;">{{ item.companyName }} · {{ item.salesman }}
</div>
</div>
</div>
<div v-if="candidateOrderList.length === 0" style="padding:30px; text-align:center; color:#909399;">暂无订单
</div>
</div>
</div>
<!-- 右侧产品内容预览 -->
<div style="width: 55%; display: flex; flex-direction: column;">
<div
style="font-size:13px; font-weight:600; color:#2c3e50; margin-bottom:8px; padding-left:8px; border-left:3px solid #ff4d4f;">
{{ bindPreviewOrder ? bindPreviewOrder.orderCode + ' - 产品明细' : '点击订单行预览产品明细' }}
</div>
<div style="flex:1; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; padding: 10px;"
v-if="bindPreviewOrder">
<el-table :data="bindPreviewProducts" border size="small" style="width:100%;">
<el-table-column label="规格" prop="spec" min-width="100" />
<el-table-column label="材质" prop="material" width="80" />
<el-table-column label="数量(吨)" prop="quantity" width="80" align="right" />
<!-- <el-table-column label="含税单价" prop="taxPrice" width="80" align="right" /> -->
<el-table-column label="备注" prop="remark" min-width="80" />
</el-table>
<div style="margin-top:8px; font-size:12px; color:#7f8c8d; display:flex; gap:16px;">
<span>产品名称{{ bindPreviewProductName }}</span>
<span>总数量{{ bindPreviewTotalQty }} </span>
<span>含税总额{{ bindPreviewTotalAmount }}</span>
</div>
</div>
<div v-else
style="flex:1; display:flex; align-items:center; justify-content:center; color:#bfbfbf; font-size:13px;">
请点击左侧订单行查看产品明细
</div>
</div>
</div>
<div slot="footer" class="dialog-footer" style="margin-top:12px;">
<span style="font-size:12px; color:#7f8c8d; margin-right:auto;">已选 {{ selectedBindOrders.length }} 个订单</span>
<el-button :loading="bindBtnLoading" type="danger" @click="confirmBind"> </el-button>
<el-button @click="bindDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listRequirement,
getRequirement,
addRequirement,
updateRequirement,
delRequirement,
addRel,
delRel,
addRequirementDetail
} from '@/api/aps/requirement'
import { listCrmOrder } from '@/api/aps/order'
import { parseProductContent } from '@/utils/productContent'
export default {
name: 'ApsRequirement',
data() {
return {
// 左侧列表
reqLoading: false,
reqList: [],
total: 0,
currentReq: null,
queryParams: {
scheduleNo: '',
prodDate: '',
customerName: '',
pageNum: 1,
pageSize: 10
},
statusMap: { 0: '草稿', 1: '待审核', 2: '已下达', 3: '已退回' },
statusBadgeMap: { 0: 'gray', 1: 'blue', 2: 'green', 3: 'red' },
// 绑定订单
boundOrderList: [],
bindDialogVisible: false,
bindQuery: { keyword: '', pageNum: 1, pageSize: 50 },
candidateOrderList: [],
selectedBindOrders: [],
bindBtnLoading: false,
bindSearchLoading: false,
bindPreviewOrder: null,
bindPreviewProducts: [],
bindPreviewProductName: '',
bindPreviewTotalQty: 0,
bindPreviewTotalAmount: 0,
// 排产明细
detailLoading: false,
requirementDetailList: [],
// 新增/编辑对话框
dialogVisible: false,
dialogTitle: '新增产需单',
btnLoading: false,
reqForm: this.getEmptyForm(),
reqRules: {
scheduleNo: [{ required: true, message: '排产单号不能为空', trigger: 'blur' }],
prodDate: [{ required: true, message: '生产日期不能为空', trigger: 'change' }]
}
}
},
computed: {
detailTotalWeight() {
return this.requirementDetailList.reduce((sum, item) => {
const n = Number(item.scheduleWeight)
return isNaN(n) ? sum : sum + n
}, 0)
}
},
created() {
this.getList()
},
methods: {
getEmptyForm() {
return {
scheduleId: undefined,
scheduleNo: '',
prodDate: '',
customerName: '',
businessUser: '',
businessPhone: '',
deliveryCycle: undefined,
usePurpose: '',
productType: '',
thicknessTolerance: '',
widthTolerance: '',
surfaceQuality: '',
surfaceTreatment: '',
innerDiameter: '',
outerDiameter: '',
packReq: '',
cutEdgeReq: '',
singleCoilWeight: '',
weightDeviation: '',
otherTechReq: '',
paymentDesc: '',
remark: ''
}
},
handleSearch() {
this.queryParams.pageNum = 1
this.getList()
},
statusBadgeType(status) {
return this.statusBadgeMap[status] || 'gray'
},
getList() {
this.reqLoading = true
listRequirement(this.queryParams).then(res => {
this.reqList = res.rows || []
this.total = res.total || 0
}).catch(() => {
this.reqList = []
this.total = 0
}).finally(() => {
this.reqLoading = false
})
},
handleReqClick(item) {
this.currentReq = item
this.detailLoading = true
getRequirement(item.scheduleId).then(res => {
if (res.data) {
this.currentReq = res.data
this.requirementDetailList = res.data.detailList || []
}
}).catch(() => {
this.requirementDetailList = []
}).finally(() => {
this.detailLoading = false
})
},
// ====== 新增/编辑 ======
resetForm() {
this.reqForm = this.getEmptyForm()
},
handleAdd() {
this.resetForm()
this.dialogTitle = '新增产需单'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.reqForm && this.$refs.reqForm.clearValidate()
})
},
handleEdit(item) {
getRequirement(item.scheduleId).then(res => {
this.reqForm = { ...this.getEmptyForm(), ...res.data }
this.dialogTitle = '编辑产需单'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.reqForm && this.$refs.reqForm.clearValidate()
})
})
},
submitForm() {
this.$refs.reqForm.validate(valid => {
if (!valid) return
this.btnLoading = true
const action = this.reqForm.scheduleId ? updateRequirement : addRequirement
action(this.reqForm).then(() => {
this.$modal.msgSuccess(this.reqForm.scheduleId ? '修改成功' : '新增成功')
this.dialogVisible = false
this.getList()
}).catch(() => {
this.$modal.msgError('操作失败')
}).finally(() => {
this.btnLoading = false
})
})
},
handleDelete(item) {
this.$confirm(`确认删除产需单「${item.scheduleNo}」吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delRequirement(item.scheduleId).then(() => {
this.$modal.msgSuccess('删除成功')
if (this.currentReq && this.currentReq.scheduleId === item.scheduleId) {
this.currentReq = null
}
this.getList()
})
}).catch(() => { })
},
openBindDialog() {
this.bindDialogVisible = true
this.selectedBindOrders = []
this.bindPreviewOrder = null
this.bindPreviewProducts = []
this.bindPreviewProductName = ''
this.bindPreviewTotalQty = 0
this.bindPreviewTotalAmount = 0
this.searchBindOrders()
},
searchBindOrders() {
this.bindSearchLoading = true
listCrmOrder(this.bindQuery).then(res => {
this.candidateOrderList = res.rows || []
}).catch(() => {
this.candidateOrderList = []
}).finally(() => {
this.bindSearchLoading = false
})
},
isBindSelected(order) {
return this.selectedBindOrders.some(o => o.orderId === order.orderId)
},
toggleBindOrder(order) {
const idx = this.selectedBindOrders.findIndex(o => o.orderId === order.orderId)
if (idx >= 0) {
this.selectedBindOrders.splice(idx, 1)
} else {
this.selectedBindOrders.push(order)
}
},
handleBindRowClick(order) {
this.bindPreviewOrder = order
if (order.productContent) {
const parsed = parseProductContent(order.productContent)
this.bindPreviewProducts = parsed.products || []
this.bindPreviewProductName = parsed.productName || ''
this.bindPreviewTotalQty = parsed.totalQuantity || 0
this.bindPreviewTotalAmount = parsed.totalTaxTotal || 0
} else {
this.bindPreviewProducts = []
this.bindPreviewProductName = ''
this.bindPreviewTotalQty = 0
this.bindPreviewTotalAmount = 0
}
},
confirmBind() {
if (this.selectedBindOrders.length === 0) {
this.$message.warning('请选择要绑定的订单')
return
}
this.bindBtnLoading = true
const scheduleId = this.currentReq.scheduleId
const allPromises = []
this.selectedBindOrders.forEach(order => {
// 1. 创建订单关联
allPromises.push(
addRel({
orderId: order.orderId,
scheduleId: scheduleId,
relWeight: null,
remark: ''
})
)
// 2. 解析 productContent每行产品创建一条排产明细
if (order.productContent) {
const parsed = parseProductContent(order.productContent)
const products = parsed.products || []
const productType = parsed.productName || ''
products.forEach(prod => {
allPromises.push(
addRequirementDetail({
scheduleId: scheduleId,
orderDetailId: order.orderId,
spec: prod.spec || '',
material: prod.material || '',
scheduleWeight: prod.quantity || 0,
productType: productType,
remark: prod.remark || ''
})
)
})
}
})
Promise.all(allPromises).then(() => {
this.$modal.msgSuccess('绑定成功,排产明细已生成')
this.bindDialogVisible = false
this.handleReqClick(this.currentReq)
}).catch(() => {
this.$modal.msgError('绑定或创建明细失败')
}).finally(() => {
this.bindBtnLoading = false
})
},
handleUnbindByOrderId(orderId) {
const rel = this.boundOrderList.find(r => r.orderId === orderId)
if (!rel) {
this.$message.warning('未找到关联记录')
return
}
this.$confirm('确认解绑该订单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delRel(rel.relId).then(() => {
this.$modal.msgSuccess('解绑成功')
this.handleReqClick(this.currentReq)
})
}).catch(() => { })
},
}
}
</script>
<style scoped lang="scss">
@import './scss/aps-theme.scss';
.aps-req-page {
height: 100%;
padding: 8px;
box-sizing: border-box;
background: $aps-silver-1;
}
.aps-req-row {
height: 100%;
}
// ====== 左侧(参照 HTML 产需单列表) ======
.aps-req-left {
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
border: 1px solid $aps-silver-3;
border-radius: 6px;
box-sizing: border-box;
overflow: hidden;
min-height: 0;
}
.panel-header {
background: $aps-silver-mid;
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: $aps-text;
border-bottom: 1px solid $aps-border;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.list-container {
flex: 1;
overflow-y: auto;
min-height: 0;
}
.list-item {
padding: 10px 12px;
border-bottom: 1px solid $aps-silver-1;
cursor: pointer;
transition: background 0.15s;
}
.list-item:hover {
background: $aps-silver-1;
}
.list-item.active {
background: $aps-red-1;
border-left: 3px solid $aps-red-2;
}
.list-item .item-title {
font-weight: 500;
color: $aps-text;
font-size: 13px;
margin-bottom: 3px;
display: flex;
align-items: center;
gap: 6px;
}
.list-item .item-sub {
font-size: 12px;
color: $aps-text-muted;
margin-bottom: 4px;
}
.list-item .item-actions {
display: flex;
gap: 8px;
}
// ====== 徽标 ======
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 500;
}
.badge-red {
background: #fdecea;
color: $aps-red-2;
border: 1px solid #f5b7b1;
}
.badge-green {
background: #eafaf1;
color: #27ae60;
border: 1px solid #a9dfbf;
}
.badge-blue {
background: #eaf4fb;
color: #2980b9;
border: 1px solid #aed6f1;
}
.badge-gray {
background: $aps-silver-1;
color: $aps-text-muted;
border: 1px solid $aps-border;
}
// ====== 右侧(平铺) ======
.aps-req-right {
height: 100%;
background: #fff;
border: 1px solid $aps-silver-3;
border-radius: 6px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.detail-panel {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
background: $aps-bg;
min-height: 0;
}
// ====== 右侧详情卡片 ======
.detail-card {
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
overflow: hidden;
}
.detail-card-header {
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
color: white;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-card-header .header-btn {
background: rgba(255, 255, 255, 0.25);
color: white;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 3px;
padding: 4px 12px;
font-size: 12px;
font-family: inherit;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.detail-card-header .header-btn:hover {
background: rgba(255, 255, 255, 0.4);
border-color: rgba(255, 255, 255, 0.7);
}
.detail-card-body {
padding: 0;
}
.req-info-table {
width: 100%;
border-collapse: collapse;
border: 1px solid $aps-border;
font-size: 13px;
td {
border: 1px solid $aps-border;
padding: 6px 10px;
vertical-align: middle;
}
.req-td-label {
background: $aps-silver-1;
color: $aps-text-muted;
font-weight: 500;
font-size: 12px;
white-space: nowrap;
width: 100px;
text-align: right;
}
.req-td-value {
color: $aps-text;
background: #fff;
word-break: break-all;
&:empty::after {
content: '-';
color: $aps-silver-3;
}
}
}
.aps-status-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 3px;
font-size: 12px;
font-weight: 500;
&.status-0 {
background: $aps-silver-2;
color: $aps-silver-4;
}
&.status-1 {
background: #e6f7ff;
color: #1890ff;
}
&.status-2 {
background: #f6ffed;
color: #52c41a;
}
&.status-3 {
background: $aps-red-1;
color: $aps-red-2;
}
}
.aps-table {
width: 100%;
::v-deep th {
background: $aps-silver-1 !important;
color: $aps-silver-5 !important;
font-weight: 600 !important;
}
}
.link-btn {
color: $aps-red-2;
cursor: pointer;
font-size: 12px;
text-decoration: none;
background: none;
border: none;
padding: 0;
font-family: inherit;
}
.link-btn:hover {
text-decoration: underline;
}
// ====== Element 按钮覆盖 ======
::v-deep .el-button--danger {
background: $aps-red-2;
border-color: $aps-red-2;
}
::v-deep .el-button--danger:hover {
background: $aps-red-3;
border-color: $aps-red-3;
}
// ====== 按钮 ======
.aps-btn-red {
@include aps-btn-red;
}
.aps-btn-silver {
@include aps-btn-silver;
}
// ====== 绑定订单卡片 ======
.bound-order-cards {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.bound-order-card {
border: 1px solid $aps-border;
border-radius: $aps-radius;
background: $aps-white;
flex: 1;
min-width: 200px;
max-width: 300px;
}
.bound-order-card .bound-order-card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: $aps-silver-1;
border-bottom: 1px solid $aps-border;
font-size: 13px;
font-weight: 600;
color: $aps-text;
border-radius: $aps-radius $aps-radius 0 0;
}
.bound-order-card .bound-order-card-body {
padding: 10px 12px;
}
// ====== 绑定弹窗 ======
.bind-order-item {
display: flex;
align-items: center;
padding: 8px 10px;
border-bottom: 1px solid #ecf0f1;
cursor: pointer;
transition: background 0.15s;
}
.bind-order-item:hover {
background: #f5f5f5;
}
.bind-order-item.selected {
background: #fdecea;
}
.bind-order-item.previewing {
background: #fdecea;
border-left: 3px solid $aps-red-2;
}
.aps-bind-dialog ::v-deep .el-dialog__body {
padding: 16px 20px 0 20px;
}
</style>