Commit 19fc3e4f4ad01f31f511ba19d7dd9335d5b86556

Authored by 史婷婷
1 parent dbdce199

feat: 产品试样确认单-列表&筛选

  1 +import request from '@/utils/request'
  2 +import { ContentTypeEnum } from '@/utils/httpEnum';
  3 +
  4 +const baseUrl = '/confirmationSlip';
  5 +// 查询列表
  6 +export function queryApi(params) {
  7 + return request({
  8 + url: baseUrl + `/query`,
  9 + method: 'get',
  10 + params
  11 + })
  12 +}
  13 +
  14 +// 根据ID查询详情数据
  15 +export function getDetailApi(id) {
  16 + return request({
  17 + url: baseUrl,
  18 + method: 'get',
  19 + params: { id }
  20 + })
  21 +}
  22 +
  23 +
  24 +// 修改保存
  25 +export function updateApi(params) {
  26 + return request({
  27 + url: baseUrl,
  28 + method: 'put',
  29 + data: params,
  30 + contentType: ContentTypeEnum.JSON
  31 + })
  32 +}
@@ -784,6 +784,14 @@ @@ -784,6 +784,14 @@
784 "navigationBarBackgroundColor": "#ffffff", 784 "navigationBarBackgroundColor": "#ffffff",
785 "navigationBarTextStyle": "black" 785 "navigationBarTextStyle": "black"
786 } 786 }
  787 + },
  788 + {
  789 + "path": "pages/confirmation_form/index",
  790 + "style": {
  791 + "navigationBarTitleText": "产品试样确认单",
  792 + "navigationBarBackgroundColor": "#ffffff",
  793 + "navigationBarTextStyle": "black"
  794 + }
787 } 795 }
788 ], 796 ],
789 "subPackages": [ 797 "subPackages": [
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row company">{{ form.purchaseOrderName }}</text>
  7 + <view class="row"><text class="label">补货单编号</text><text class="value">{{ form.code }}</text></view>
  8 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName }}</text>
  9 + </view>
  10 + <view class="row"><text class="label">办事处</text><text class="value">{{ form.deptName }}</text>
  11 + </view>
  12 + <view class="row"><text class="label">区域</text><text class="value">{{ form.regionName }}</text>
  13 + </view>
  14 + <view class="row"><text class="label">购货单位</text><text class="value">{{ form.customerName }}</text>
  15 + </view>
  16 + <view class="row"><text class="label">原计划发货日期</text><text class="value">{{ form.originPlanShipDate
  17 + }}</text></view>
  18 + </view>
  19 +
  20 + <!-- 产品 -->
  21 + <view class="section2">
  22 + <Product mode="approve" :list="initPurchaseOrderLineList"
  23 + :canEditSupplementary="canEditSupplementary" @change="purchaseOrderLineListChange" />
  24 + </view>
  25 + <view class="section3">
  26 + <view class="view-total">
  27 + <view class="head">合计</view>
  28 + <view class="row">
  29 + <view class="row2">
  30 + <text class="label">需发</text><text class="value">{{ form.totalQuantity }}</text>
  31 + </view>
  32 + <view class="row2">
  33 + <text class="label">实发</text><text class="value">{{ form.totalShippedQuantity }}</text>
  34 + </view>
  35 + </view>
  36 + <view class="row">
  37 + <view class="row2">
  38 + <text class="label">需求补货</text><text class="value">{{ form.totalSupplementaryQuantity
  39 + }}</text>
  40 + </view>
  41 + </view>
  42 + </view>
  43 + </view>
  44 +
  45 + </view>
  46 + </scroll-view>
  47 + </view>
  48 +</template>
  49 +
  50 +<script>
  51 +import { getDetailApi } from '@/api/replenishment_order.js'
  52 +import Product from './product.vue'
  53 +
  54 +export default {
  55 + name: 'ReplenishmentOrderApprove',
  56 + components: { Product },
  57 + props: { id: { type: [String, Number], default: '' } },
  58 + data() {
  59 + return {
  60 + form: {},
  61 + initPurchaseOrderLineList: [],
  62 + roleCodes: [],
  63 + canEditSupplementary: false
  64 + }
  65 + },
  66 + computed: {
  67 + },
  68 + watch: {
  69 + id: {
  70 + immediate: true,
  71 + handler(val) {
  72 + const v = (val !== undefined && val !== null) ? String(val) : ''
  73 + if (v) {
  74 + this.loadDetail(v);
  75 + this.getRoleInfo();
  76 + }
  77 + }
  78 + }
  79 + },
  80 + onLoad(query) {
  81 +
  82 + },
  83 + methods: {
  84 + getRoleInfo() {
  85 + this.roleCodes = this.$store.getters.roleCodes || [];
  86 + console.log('roleCodes', this.roleCodes)
  87 + // 一分厂经营办计划员/二分厂经营办计划员/三分厂经营办计划员/四分厂经营办计划员
  88 + const allowed = ['yfcjybjhy', 'efcjybjhy', 'sfcjybjhy', 'ztfcjybjhy'];
  89 + this.canEditSupplementary = allowed.some((code) => this.roleCodes.includes(code));
  90 + console.log('canEditSupplementary', this.canEditSupplementary)
  91 + },
  92 + async loadDetail(id) {
  93 + try {
  94 + const res = await getDetailApi(id)
  95 + const m = res.data || {}
  96 + const next = { ...this.form, ...m }
  97 + // 确保ID存在
  98 + next.id = m.id || id
  99 + // 映射列表
  100 + // 注意:详情返回的是 replenishmentOrderLineList,需要赋值给 initPurchaseOrderLineList 以便 Product 组件初始化
  101 + // 且需要处理字段兼容性,确保 Product 组件能正确显示和编辑
  102 + const lines = Array.isArray(m.replenishmentOrderLineList) ? m.replenishmentOrderLineList.map(x => ({
  103 + ...x,
  104 + // 确保 Product 组件需要的字段存在
  105 + // Product组件使用: quantity(需发), shippedQuantity(实发), supplementaryQuantity(需求补货), salesPrice(单价)
  106 + // 详情接口返回的字段应该已经包含了这些,如果有差异需要在此处转换
  107 + // 注意:add.vue中 onRelateConfirm 做了映射,这里是回显,通常直接使用即可
  108 + })) : []
  109 +
  110 + this.form = next;
  111 + this.initPurchaseOrderLineList = lines;
  112 + // 初始计算合计
  113 + this.calculateSummary(lines)
  114 + } catch (e) {
  115 + this.form = {}
  116 + }
  117 + },
  118 + calculateSummary(list) {
  119 + const summary = (list || []).reduce((acc, it) => {
  120 + const qty = Number(it.supplementaryQuantity) || 0
  121 + const shipped = Number(it.shippedQuantity) || 0
  122 + const orderQty = Number(it.quantity) || 0
  123 + acc.totalSupplementaryQuantity += qty
  124 + acc.totalShippedQuantity += shipped
  125 + acc.totalQuantity += orderQty
  126 + return acc
  127 + }, { totalQuantity: 0, totalShippedQuantity: 0, totalSupplementaryQuantity: 0 })
  128 +
  129 + const fixedTotalQuantity = Number(summary.totalQuantity.toFixed(2))
  130 + const fixedTotalShippedQuantity = Number(summary.totalShippedQuantity.toFixed(2))
  131 + const fixedTotalSupplementaryQuantity = Number(summary.totalSupplementaryQuantity.toFixed(2))
  132 + this.form.totalQuantity = fixedTotalQuantity
  133 + this.form.totalShippedQuantity = fixedTotalShippedQuantity
  134 + this.form.totalSupplementaryQuantity = fixedTotalSupplementaryQuantity
  135 + },
  136 + purchaseOrderLineListChange(data) {
  137 + const list = Array.isArray(data) ? data : []
  138 + this.form.purchaseOrderLineList = list
  139 + this.calculateSummary(list)
  140 + },
  141 + getFormValues() {
  142 + const m = this.form || {}
  143 + return JSON.parse(JSON.stringify(m))
  144 + },
  145 + // 审批-通过:校验表单
  146 + checkForm() {
  147 + if (this.canEditSupplementary) {
  148 + const list = Array.isArray(this.form.purchaseOrderLineList) ? this.form.purchaseOrderLineList : [];
  149 + console.log('checkForm__list', list)
  150 + if (list.length === 0) {
  151 + uni.showToast({ title: '请先添加产品', icon: 'none' })
  152 + return false
  153 + }
  154 + const fields = [
  155 + { key: 'supplementaryQuantity', label: '需求补货数量' },
  156 + ]
  157 + for (let i = 0; i < list.length; i++) {
  158 + const it = list[i] || {}
  159 + for (const f of fields) {
  160 + const v = it && it[f.key]
  161 + if (v === undefined || v === null || String(v).trim() === '') {
  162 + uni.showToast({ title: `产品第${i + 1}条:${f.label}不能为空!`, icon: 'none' })
  163 + return false
  164 + }
  165 + }
  166 + }
  167 + return true
  168 + } else {
  169 + return true
  170 + }
  171 +
  172 + }
  173 + }
  174 +}
  175 +</script>
  176 +
  177 +<style lang="scss" scoped>
  178 +.page {
  179 + display: flex;
  180 + flex-direction: column;
  181 + height: 100vh;
  182 +}
  183 +
  184 +.scroll {
  185 + flex: 1;
  186 + background: #f3f3f3;
  187 +}
  188 +
  189 +.detail-page {
  190 + padding-bottom: 150rpx;
  191 +}
  192 +
  193 +.section {
  194 + padding: 32rpx;
  195 + background: #fff;
  196 + margin-bottom: 20rpx;
  197 + position: relative;
  198 +
  199 +}
  200 +
  201 +.row {
  202 + display: flex;
  203 + margin-bottom: 28rpx;
  204 +
  205 + &:last-child {
  206 + margin-bottom: 0;
  207 + }
  208 +
  209 + &.company {
  210 + font-size: 36rpx;
  211 + font-weight: 600;
  212 + color: rgba(0, 0, 0, 0.9);
  213 + padding-top: 10rpx;
  214 + margin-bottom: 32rpx;
  215 + line-height: 50rpx;
  216 + }
  217 +
  218 + .label {
  219 + width: 240rpx;
  220 + line-height: 32rpx;
  221 + font-size: 28rpx;
  222 + color: rgba(0, 0, 0, 0.6);
  223 + }
  224 +
  225 + .value {
  226 + flex: 1;
  227 + line-height: 32rpx;
  228 + font-size: 28rpx;
  229 + color: rgba(0, 0, 0, 0.9);
  230 + text-align: right;
  231 + word-break: break-all;
  232 + }
  233 +}
  234 +
  235 +.title-header {
  236 + background-color: #fff;
  237 + display: flex;
  238 + align-items: center;
  239 + padding: 32rpx 32rpx 22rpx;
  240 + border-bottom: 1rpx dashed #f0f0f0;
  241 +
  242 + &_icon {
  243 + width: 32rpx;
  244 + height: 28rpx;
  245 + margin-right: 16rpx;
  246 + }
  247 +
  248 + span {
  249 + color: rgba(0, 0, 0, 0.9);
  250 + font-size: 32rpx;
  251 + line-height: 44rpx;
  252 + font-weight: 600;
  253 + }
  254 +}
  255 +
  256 +.section3 {
  257 + padding: 0 32rpx;
  258 + background-color: #fff;
  259 + margin-top: 20rpx;
  260 +}
  261 +
  262 +.view-total {
  263 + padding: 20rpx 0;
  264 +
  265 + .head {
  266 + font-size: 32rpx;
  267 + font-weight: 600;
  268 + line-height: 50rpx;
  269 + color: rgba(0, 0, 0, 0.9);
  270 + padding-bottom: 16rpx;
  271 + margin-bottom: 24rpx;
  272 + border-bottom: 1px dashed #E7E7E7;
  273 + }
  274 +
  275 + .row {
  276 + display: flex;
  277 + margin-bottom: 24rpx;
  278 + line-height: 32rpx;
  279 +
  280 + .row2 {
  281 + width: 50%;
  282 + }
  283 +
  284 + .label {
  285 + width: 180rpx;
  286 + margin-right: 14rpx;
  287 + color: rgba(0, 0, 0, 0.6);
  288 + font-size: 28rpx;
  289 + }
  290 +
  291 + .value {
  292 + flex: 1;
  293 + color: rgba(0, 0, 0, 0.9);
  294 + font-size: 28rpx;
  295 + white-space: pre-wrap;
  296 + word-break: break-all;
  297 + }
  298 + }
  299 +}
  300 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row company">{{ form.purchaseOrderName }}</text>
  7 + <view :class="['status', `status_${form.status}`]" />
  8 + <view class="row"><text class="label">补货单编号</text><text class="value">{{ form.code }}</text></view>
  9 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName }}</text></view>
  10 + <view class="row"><text class="label">办事处</text><text class="value">{{ form.deptName }}</text></view>
  11 + <view class="row"><text class="label">区域</text><text class="value">{{ form.regionName }}</text></view>
  12 + <view class="row"><text class="label">购货单位</text><text class="value">{{ form.customerName }}</text></view>
  13 + <view class="row"><text class="label">原计划发货日期</text><text class="value">{{ form.originPlanShipDate }}</text></view>
  14 + </view>
  15 +
  16 + <!-- 产品 -->
  17 + <view class="section2">
  18 + <Product mode="view" :list="form.replenishmentOrderLineList"
  19 + :orderDate="form.orderDate"
  20 + :totalQuantity="form.totalQuantity"
  21 + :totalShippedQuantity="form.totalShippedQuantity"
  22 + :totalSupplementaryQuantity="form.totalSupplementaryQuantity"
  23 + />
  24 + </view>
  25 + </view>
  26 + </scroll-view>
  27 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
  28 + </view>
  29 +</template>
  30 +
  31 +<script>
  32 +import { getDetailApi, cancelApi } from '@/api/replenishment_order.js'
  33 +import Product from './product.vue'
  34 +import DetailButtons from '@/components/detail-buttons/index.vue'
  35 +
  36 +export default {
  37 + name: 'ReplenishmentOrderDetail',
  38 + components: { Product, DetailButtons },
  39 + data() {
  40 + return {
  41 + form: {},
  42 + buttons: [
  43 + { text: '编辑', visible: true, variant: 'outline', event: 'edit' },
  44 + { text: '审核详情', visible: true, variant: 'outline', event: 'auditDetail' },
  45 + { text: '填写补货时间', visible: true, variant: 'outline', event: 'fill' },
  46 + { text: '审核', visible: true, variant: 'primary', event: 'audit' },
  47 + { text: '取消', visible: true, variant: 'outline', event: 'cancel', style: { color: 'rgba(0,0,0,0.9)', border: '1px solid #DCDCDC' } },
  48 + ]
  49 + }
  50 + },
  51 + computed: {
  52 + statusFlags() {
  53 + const m = this.form || {}
  54 + const e = String(m.status || '')
  55 + return {
  56 + isRefuse: e === 'REFUSE' || false,
  57 + isAudit: e === 'AUDIT' || false,
  58 + canEdit: e === 'REFUSE' && m.isOwner || false,
  59 + canAudit: e === 'AUDIT' && m.showExamine || false,
  60 + canCancel: e === 'REFUSE' && m.isOwner || false,
  61 + canFill: e === 'PASS' && !m.completed || false,
  62 + }
  63 + },
  64 + displayButtons() {
  65 + const f = this.statusFlags
  66 + return [
  67 + { ...this.buttons[0], visible: f.canEdit && this.$auth.hasPermi('shipping-plan-manage:replenishment-order:modify') },
  68 + { ...this.buttons[1], visible: this.$auth.hasPermi('shipping-plan-manage:replenishment-order:review') },
  69 + { ...this.buttons[2], visible: f.canFill && this.$auth.hasPermi('shipping-plan-manage:replenishment-order:fill') },
  70 + { ...this.buttons[3], visible: f.canAudit && this.$auth.hasPermi('shipping-plan-manage:replenishment-order:approve') },
  71 + { ...this.buttons[4], visible: f.canCancel && this.$auth.hasPermi('shipping-plan-manage:replenishment-order:cancel') },
  72 + ]
  73 + }
  74 + },
  75 + onLoad(query) {
  76 + const id = (query && (query.id || query.code)) || ''
  77 + if (id) this.loadDetail(id)
  78 + },
  79 + methods: {
  80 + async loadDetail(id) {
  81 + try {
  82 + const res = await getDetailApi(id)
  83 + this.form = res.data || {}
  84 + } catch (e) {
  85 + this.form = {}
  86 + }
  87 + },
  88 + handleButtonClick(btn) {
  89 + if (!btn || btn.disabled) return
  90 + const map = {
  91 + edit: () => this.onEdit(),
  92 + auditDetail: () => this.onAuditDetail(),
  93 + audit: () => this.onAudit(),
  94 + fill: () => this.onFill(),
  95 + cancel: () => this.onCancel(),
  96 + }
  97 + const fn = map[btn.event]
  98 + if (typeof fn === 'function') fn()
  99 + },
  100 + onEdit() {
  101 + const id = this.form.id || this.form.code
  102 + if (id) uni.navigateTo({ url: `/pages/replenishment_order/modify?id=${id}` })
  103 + },
  104 + onAuditDetail() {
  105 + uni.setStorageSync('sourceBusinessId', this.form.id)
  106 + uni.navigateTo({ url: '/pages/flow/audit_detail' })
  107 + },
  108 + onAudit() {
  109 + uni.setStorageSync('sourceBusinessId', this.form.id)
  110 + uni.navigateTo({ url: '/pages/flow/audit' })
  111 + },
  112 + onFill() {
  113 + const id = this.form.id || this.form.code
  114 + if (id) uni.navigateTo({ url: `/pages/replenishment_order/fill?id=${id}` })
  115 + },
  116 + onCancel() {
  117 + const id = this.form.id || this.form.code
  118 + if (!id) return
  119 + uni.showModal({
  120 + title: '提示',
  121 + content: '确定要取消该补货单吗?',
  122 + success: async (res) => {
  123 + if (res.confirm) {
  124 + try {
  125 + await cancelApi(id)
  126 + uni.showToast({ title: '取消成功', icon: 'success' })
  127 + setTimeout(() => { uni.redirectTo({ url: '/pages/replenishment_order/index' }) }, 300)
  128 + } catch (e) {
  129 + uni.showToast({ title: (e && e.msg) || '取消失败', icon: 'none' })
  130 + }
  131 + }
  132 + }
  133 + })
  134 + },
  135 + }
  136 +}
  137 +</script>
  138 +
  139 +<style lang="scss" scoped>
  140 +.page {
  141 + display: flex;
  142 + flex-direction: column;
  143 + height: 100vh;
  144 +}
  145 +
  146 +.scroll {
  147 + flex: 1;
  148 + background: #f3f3f3;
  149 +}
  150 +
  151 +.detail-page {
  152 + padding-bottom: 150rpx;
  153 +}
  154 +
  155 +.section {
  156 + padding: 32rpx;
  157 + background: #fff;
  158 + margin-bottom: 20rpx;
  159 + position: relative;
  160 +
  161 + .status {
  162 + position: absolute;
  163 + top: 16rpx;
  164 + right: 52rpx;
  165 + width: 180rpx;
  166 + height: 146rpx;
  167 + background-repeat: no-repeat;
  168 + background-size: 100% 100%;
  169 + background-position: center;
  170 +
  171 + &_AUDIT {
  172 + background-image: url('~@/static/images/dev_manage/status_1.png');
  173 + }
  174 +
  175 + &_PASS {
  176 + background-image: url('~@/static/images/dev_manage/status_2.png');
  177 + }
  178 +
  179 + &_REFUSE {
  180 + background-image: url('~@/static/images/dev_manage/status_3.png');
  181 + }
  182 +
  183 + &_CANCEL {
  184 + background-image: url('~@/static/images/dev_manage/status_4.png');
  185 + }
  186 +
  187 + }
  188 +}
  189 +
  190 +.row {
  191 + display: flex;
  192 + margin-bottom: 28rpx;
  193 +
  194 + &:last-child {
  195 + margin-bottom: 0;
  196 + }
  197 +
  198 + &.company {
  199 + font-size: 36rpx;
  200 + font-weight: 600;
  201 + color: rgba(0, 0, 0, 0.9);
  202 + padding-top: 10rpx;
  203 + margin-bottom: 32rpx;
  204 + line-height: 50rpx;
  205 + }
  206 +
  207 + .label {
  208 + width: 240rpx;
  209 + line-height: 32rpx;
  210 + font-size: 28rpx;
  211 + color: rgba(0, 0, 0, 0.6);
  212 + }
  213 +
  214 + .value {
  215 + flex: 1;
  216 + line-height: 32rpx;
  217 + font-size: 28rpx;
  218 + color: rgba(0, 0, 0, 0.9);
  219 + text-align: right;
  220 + word-break: break-all;
  221 + }
  222 +}
  223 +
  224 +.title-header {
  225 + background-color: #fff;
  226 + display: flex;
  227 + align-items: center;
  228 + padding: 32rpx 32rpx 22rpx;
  229 + border-bottom: 1rpx dashed #f0f0f0;
  230 +
  231 + &_icon {
  232 + width: 32rpx;
  233 + height: 28rpx;
  234 + margin-right: 16rpx;
  235 + }
  236 +
  237 + span {
  238 + color: rgba(0, 0, 0, 0.9);
  239 + font-size: 32rpx;
  240 + line-height: 44rpx;
  241 + font-weight: 600;
  242 + }
  243 +}
  244 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <view class="dev-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入订货单位" clearButton="auto"
  6 + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search"
  7 + @input="onSearchInput" />
  8 + <view class="tool-icons">
  9 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  10 + </view>
  11 + </view>
  12 + </view>
  13 +
  14 +
  15 + <!-- 列表卡片组件 -->
  16 + <view class="list-box">
  17 + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id"
  18 + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError">
  19 + <template v-slot="{ item, selected }">
  20 + <view class="card" @click.stop="onCardClick(item)">
  21 + <view class="card-header">
  22 + <text class="title omit2">{{ item.orderingUnitName }}</text>
  23 + <text :class="['status', `status_${item.status}`]">{{ filterStatus(item.status) }}</text>
  24 + </view>
  25 + <view class="info-row">
  26 + <text>试样种类</text><text>{{ item.sampleTypeName || '-' }}</text>
  27 + </view>
  28 + <view class="info-row">
  29 + <text>生产厂</text><text>{{ item.workshopName || '-' }}</text>
  30 + </view>
  31 + <view class="info-row">
  32 + <text>客户类型</text><text>{{ item.customerTypeName || '-' }}</text>
  33 + </view>
  34 + </view>
  35 + </template>
  36 + </card-list>
  37 + </view>
  38 +
  39 +
  40 +
  41 + <!-- 筛选弹框 -->
  42 + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset"
  43 + @confirm="onFilterConfirm">
  44 + <template v-slot="{ model }">
  45 + <view class="filter-form">
  46 + <view class="form-item">
  47 + <view class="label">生产厂</view>
  48 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  49 + v-model="model.workshopId" @change="onWorkshopChange" :localdata="workshopOptions" />
  50 + </view>
  51 + <view class="form-item">
  52 + <view class="label">审核状态</view>
  53 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  54 + v-model="model.status" @change="onStatusChange" :localdata="statusLocal" />
  55 + </view>
  56 + <view class="form-item">
  57 + <view class="label">试样种类</view>
  58 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  59 + v-model="model.sampleType" @change="onSampleTypeChange" :localdata="sampleTypeLocal" />
  60 + </view>
  61 + <view class="form-item">
  62 + <view class="label">客户类型</view>
  63 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  64 + v-model="model.customerType" @change="onEnterpriseTypeChange" :localdata="enterpriseTypeLocal" />
  65 + </view>
  66 + </view>
  67 + </template>
  68 + </filter-modal>
  69 + </view>
  70 +</template>
  71 +
  72 +<script>
  73 +import CardList from '@/components/card/index.vue'
  74 +import FilterModal from '@/components/filter/index.vue'
  75 +import { workshopQueryApi } from '@/api/devManage.js'
  76 +import SingleSelectSheet from '@/components/single-select/index.vue'
  77 +import {
  78 + queryApi
  79 +} from '@/api/confirmation_form.js'
  80 +import {
  81 + getDicByCodes
  82 +} from '@/utils/dic'
  83 +
  84 +export default {
  85 + components: {
  86 + CardList,
  87 + FilterModal,
  88 + SingleSelectSheet
  89 + },
  90 + data() {
  91 + return {
  92 + searchKeyword: '',
  93 + searchKeywordDebounced: '',
  94 + tabs: [],
  95 + // 给到 card 的筛选值
  96 + query: {
  97 + status: '',
  98 + workshopId: '',
  99 + sampleType: '',
  100 + customerType: '',
  101 + },
  102 + extraParams: {},
  103 +
  104 + // 批量选择
  105 + rowKey: 'id',
  106 + currentItems: [],
  107 +
  108 + // 筛选弹框
  109 + filterVisible: false,
  110 + filterForm: {
  111 + status: '',
  112 + workshopId: '',
  113 + sampleType: '',
  114 + customerType: '',
  115 + },
  116 + dicOptions: {
  117 + AUDIT_STATUS: [], // 审核
  118 + ENTERPRISE_TYPE: [], // 客户类型
  119 + SAMPLE_TYPE: [], // 试样种类
  120 + },
  121 + statusLocal: [],
  122 + enterpriseTypeLocal: [], // 客户类型
  123 + sampleTypeLocal: [], // 试样种类
  124 + workshopOptions: [],
  125 + }
  126 + },
  127 + computed: {
  128 + extraCombined() {
  129 + return {
  130 + orderingUnitName: this.searchKeywordDebounced || undefined
  131 + }
  132 + }
  133 + },
  134 + watch: {
  135 + extraCombined: {
  136 + deep: true,
  137 + handler(v) {
  138 + this.extraParams = v
  139 + },
  140 + immediate: true
  141 + },
  142 +
  143 + },
  144 + created() {
  145 + this.loadAllDicData()
  146 + this.loadWorkshopOptions()
  147 + },
  148 + onLoad() { },
  149 + // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  150 + onReachBottom() {
  151 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  152 + this.$refs.cardRef.onLoadMore()
  153 + }
  154 + },
  155 + beforeDestroy() {
  156 + if (this.searchDebounceTimer) {
  157 + clearTimeout(this.searchDebounceTimer)
  158 + this.searchDebounceTimer = null
  159 + }
  160 + },
  161 + methods: {
  162 + async loadWorkshopOptions() {
  163 + try {
  164 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  165 + const list = (res && res.data && res.data.datas) || []
  166 + this.workshopOptions = list.map(it => ({ text: it.name || it.workshopName || '', value: it.id || it.workshopId || '' }))
  167 + } catch (e) {
  168 + this.workshopOptions = []
  169 + }
  170 + },
  171 + onCardLoaded({
  172 + items
  173 + }) {
  174 + this.currentItems = items
  175 + },
  176 + onCardError() {
  177 + uni.showToast({
  178 + title: '列表加载失败',
  179 + icon: 'none'
  180 + })
  181 + },
  182 + // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
  183 + onSearchInput(val) {
  184 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  185 + this.searchDebounceTimer = setTimeout(() => {
  186 + this.searchKeywordDebounced = this.searchKeyword
  187 + this.searchDebounceTimer = null
  188 + }, 1200)
  189 + },
  190 + // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
  191 + search(e) {
  192 + const val = e && e.value != null ? e.value : this.searchKeyword
  193 + this.searchKeyword = val
  194 + this.searchKeywordDebounced = val
  195 + },
  196 + onAdd() {
  197 + uni.navigateTo({
  198 + url: '/pages/confirmation_form/add'
  199 + })
  200 + },
  201 + openFilter() {
  202 + this.filterVisible = true
  203 + },
  204 + onFilterReset(payload) {
  205 + this.filterForm = payload
  206 + },
  207 + onFilterConfirm(payload) {
  208 + if ((payload.status === '' || payload.status == null) && this.filterForm.status !== '') {
  209 + payload.status = this.filterForm.status
  210 + }
  211 + this.query = {
  212 + ...payload
  213 + }
  214 + },
  215 + onStatusChange(e) {
  216 + const raw = e && e.detail && e.detail.value !== undefined ?
  217 + e.detail.value :
  218 + (e && e.value !== undefined ? e.value : '')
  219 + this.filterForm.status = raw
  220 + },
  221 +
  222 + onSampleTypeChange(e) {
  223 + const raw = e && e.detail && e.detail.value !== undefined ?
  224 + e.detail.value :
  225 + (e && e.value !== undefined ? e.value : '')
  226 + this.filterForm.sampleType = raw
  227 + },
  228 +
  229 + onEnterpriseTypeChange(e) {
  230 + const raw = e && e.detail && e.detail.value !== undefined ?
  231 + e.detail.value :
  232 + (e && e.value !== undefined ? e.value : '')
  233 + this.filterForm.customerType = raw
  234 + },
  235 +
  236 + // 列表接口(真实请求)
  237 + fetchList({
  238 + pageIndex,
  239 + pageSize,
  240 + query,
  241 + extra
  242 + }) {
  243 + const params = {
  244 + pageIndex,
  245 + pageSize,
  246 + ...extra,
  247 + ...query
  248 + }
  249 + if (this.searchKeywordDebounced) {
  250 + params.orderingUnitName = this.searchKeywordDebounced
  251 + }
  252 + return queryApi(params)
  253 + .then(res => {
  254 + const _data = res.data || {};
  255 + const records = _data.datas || [];
  256 + const totalCount = _data.totalCount || 0;
  257 + const hasNext = _data.hasNext || false
  258 + return {
  259 + records,
  260 + totalCount,
  261 + hasNext
  262 + }
  263 + })
  264 + .catch(err => {
  265 + console.error('fetchList error', err)
  266 + this.onCardError()
  267 + return {
  268 + records: [],
  269 + totalCount: 0,
  270 + hasNext: false
  271 + }
  272 + })
  273 + },
  274 + loadAllDicData() {
  275 + const dicCodes = ['AUDIT_STATUS', 'ENTERPRISE_TYPE', 'SAMPLE_TYPE']
  276 + return getDicByCodes(dicCodes).then(results => {
  277 + this.dicOptions.AUDIT_STATUS = results.AUDIT_STATUS.data || []
  278 + this.dicOptions.ENTERPRISE_TYPE = results.ENTERPRISE_TYPE.data || []
  279 + this.dicOptions.SAMPLE_TYPE = results.SAMPLE_TYPE.data || []
  280 + this.statusLocal = (this.dicOptions.AUDIT_STATUS || []).map(it => ({
  281 + value: it.code,
  282 + text: it.name
  283 + }))
  284 + this.enterpriseTypeLocal = (this.dicOptions.ENTERPRISE_TYPE || []).map(it => ({
  285 + value: it.code,
  286 + text: it.name
  287 + }))
  288 + this.sampleTypeLocal = (this.dicOptions.SAMPLE_TYPE || []).map(it => ({
  289 + value: it.code,
  290 + text: it.name
  291 + }))
  292 + }).catch(() => {
  293 + this.dicOptions = {
  294 + AUDIT_STATUS: [],
  295 + ENTERPRISE_TYPE: [], // 客户类型
  296 + SAMPLE_TYPE: [], // 试样种类
  297 + }
  298 + this.statusLocal = []
  299 + this.enterpriseTypeLocal = []
  300 + this.sampleTypeLocal = []
  301 + })
  302 + },
  303 + onCardClick(item) {
  304 + const id = (item && (item.id || item.code)) || ''
  305 + if (!id) return
  306 + const query = '?id=' + encodeURIComponent(id)
  307 + uni.navigateTo({
  308 + url: '/pages/replenishment_order/detail' + query
  309 + })
  310 + },
  311 + onWorkshopChange(e) {
  312 + const raw = e && e.detail && e.detail.value !== undefined ? e.detail.value : (e && e.value !== undefined ? e.value : '')
  313 + this.filterForm.workshopId = raw
  314 + const match = (this.workshopOptions || []).find(o => String(o.value) === String(raw))
  315 + this.filterForm.workshopIdName = match ? (match.text || '') : ''
  316 + },
  317 + filterStatus(status) {
  318 + return this.statusLocal.filter(item => item.value === status)[0].text || '';
  319 + },
  320 + }
  321 +}
  322 +</script>
  323 +
  324 +<style lang="scss" scoped>
  325 +.page {
  326 + display: flex;
  327 + flex-direction: column;
  328 + height: 100vh;
  329 +}
  330 +
  331 +.dev-list-fixed {
  332 + position: fixed;
  333 + top: 96rpx;
  334 + left: 0;
  335 + right: 0;
  336 + z-index: 2;
  337 + background: #fff;
  338 +
  339 + .search-row {
  340 + display: flex;
  341 + align-items: center;
  342 + padding: 16rpx 32rpx;
  343 +
  344 + .uni-searchbar {
  345 + padding: 0;
  346 + flex: 1;
  347 + }
  348 +
  349 + .tool-icons {
  350 + display: flex;
  351 +
  352 + .tool-icon {
  353 + width: 48rpx;
  354 + height: 48rpx;
  355 + display: block;
  356 + margin-left: 32rpx;
  357 + }
  358 + }
  359 + }
  360 +
  361 +}
  362 +
  363 +/* 仅当前页覆盖 uni-search-bar 盒子高度 */
  364 +::v-deep .uni-searchbar__box {
  365 + height: 80rpx !important;
  366 + justify-content: start;
  367 +
  368 + .uni-searchbar__box-search-input {
  369 + font-size: 32rpx !important;
  370 + }
  371 +}
  372 +
  373 +.list-box {
  374 + flex: 1;
  375 + padding-top: 140rpx;
  376 +
  377 + &.pad-batch {
  378 + padding-bottom: 144rpx;
  379 + }
  380 +
  381 + .card {
  382 + position: relative;
  383 + }
  384 +
  385 + .card-header {
  386 + margin-bottom: 28rpx;
  387 + position: relative;
  388 +
  389 + .title {
  390 + font-size: 36rpx;
  391 + font-weight: 600;
  392 + line-height: 50rpx;
  393 + color: rgba(0, 0, 0, 0.9);
  394 + width: 578rpx;
  395 + }
  396 +
  397 + .status {
  398 + font-weight: 600;
  399 + position: absolute;
  400 + top: -32rpx;
  401 + right: -12rpx;
  402 + height: 48rpx;
  403 + line-height: 48rpx;
  404 + color: #fff;
  405 + font-size: 24rpx;
  406 + padding: 0 14rpx;
  407 + border-radius: 6rpx;
  408 +
  409 + // 审核中
  410 + &.status_AUDIT {
  411 + background: $theme-primary;
  412 + }
  413 +
  414 + // 审核通过
  415 + &.status_PASS {
  416 + background: #2BA471;
  417 + }
  418 +
  419 + // 已驳回
  420 + &.status_REFUSE {
  421 + background: #d54941;
  422 + }
  423 +
  424 + // 已取消
  425 + &.status_CANCEL {
  426 + background: #e7e7e7;
  427 + color: rgba(0, 0, 0, 0.6);
  428 + }
  429 +
  430 + }
  431 +
  432 + }
  433 +
  434 + .info-row {
  435 + display: flex;
  436 + align-items: center;
  437 + color: rgba(0, 0, 0, 0.6);
  438 + font-size: 28rpx;
  439 + margin-bottom: 24rpx;
  440 +
  441 + &:last-child {
  442 + margin-bottom: 0;
  443 + }
  444 +
  445 + text {
  446 + width: 60%;
  447 + line-height: 32rpx;
  448 +
  449 + &:last-child {
  450 + color: rgba(0, 0, 0, 0.9);
  451 + width: 40%;
  452 + }
  453 +
  454 + &.category {
  455 + display: inline-block;
  456 + padding: 4rpx 12rpx;
  457 + border-radius: 6rpx;
  458 + font-size: 24rpx;
  459 + width: auto;
  460 +
  461 + &.category_A {
  462 + background: #FFF0ED;
  463 + color: #D54941;
  464 + }
  465 +
  466 + &.category_B {
  467 + background: #FFF1E9;
  468 + color: #E37318;
  469 + }
  470 +
  471 + &.category_C {
  472 + background: #F2F3FF;
  473 + color: $theme-primary;
  474 + }
  475 +
  476 + &.category_D {
  477 + background: #E3F9E9;
  478 + color: #2BA471;
  479 + }
  480 + }
  481 + }
  482 +
  483 + }
  484 +}
  485 +
  486 +.filter-form {
  487 + .form-item {
  488 + margin-bottom: 24rpx;
  489 + }
  490 +
  491 + .label {
  492 + margin-bottom: 20rpx;
  493 + color: rgba(0, 0, 0, 0.9);
  494 + height: 44rpx;
  495 + line-height: 44rpx;
  496 + font-size: 30rpx;
  497 + }
  498 +
  499 + .uni-easyinput {
  500 + border: 1rpx solid #f3f3f3;
  501 + }
  502 +
  503 +}
  504 +
  505 +/* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
  506 +::v-deep .filter-form .uni-data-checklist .checklist-group {
  507 + .checklist-box {
  508 + &.is--tag {
  509 + width: 212rpx;
  510 + margin-top: 0;
  511 + margin-bottom: 24rpx;
  512 + margin-right: 24rpx;
  513 + height: 80rpx;
  514 + padding: 0 20rpx;
  515 + border-radius: 12rpx;
  516 + background-color: #f3f3f3;
  517 + border-color: #f3f3f3;
  518 +
  519 + &:nth-child(3n) {
  520 + margin-right: 0;
  521 + }
  522 +
  523 + .checklist-content {
  524 + display: flex;
  525 + justify-content: center;
  526 + }
  527 +
  528 + .checklist-text {
  529 + color: rgba(0, 0, 0, 0.9);
  530 + font-size: 28rpx;
  531 + text-align: center;
  532 + }
  533 + }
  534 +
  535 + &.is-checked {
  536 + background-color: $theme-primary-plain-bg !important;
  537 + border-color: $theme-primary-plain-bg !important;
  538 +
  539 + .checklist-text {
  540 + color: $theme-primary !important;
  541 + }
  542 + }
  543 + }
  544 +
  545 +}
  546 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <view class="section">
  6 + <!-- 编辑模式下,订单编号通常不允许修改,或者作为只读显示,但为了保持与新增页UI一致,这里保留结构但设为不可点击 -->
  7 + <uni-list-item class="select-item is-filled" :rightText="form.purchaseOrderName || ''">
  8 + <template v-slot:body>
  9 + <view class="item-title"><text class="required">*</text><text>订单编号</text></view>
  10 + </template>
  11 + </uni-list-item>
  12 + <uni-list-item title="补货单编号">
  13 + <template v-slot:footer>
  14 + <view class="readonly-text">{{ form.code }}</view>
  15 + </template>
  16 + </uni-list-item>
  17 + <uni-list-item title="分厂">
  18 + <template v-slot:footer>
  19 + <view class="readonly-text">{{ form.workshopName }}</view>
  20 + </template>
  21 + </uni-list-item>
  22 + <uni-list-item title="办事处">
  23 + <template v-slot:footer>
  24 + <view class="readonly-text">{{ form.deptName }}</view>
  25 + </template>
  26 + </uni-list-item>
  27 + <uni-list-item title="区域">
  28 + <template v-slot:footer>
  29 + <view class="readonly-text">{{ form.regionName }}</view>
  30 + </template>
  31 + </uni-list-item>
  32 + <uni-list-item title="购货单位">
  33 + <template v-slot:footer>
  34 + <view class="readonly-text">{{ form.customerName }}</view>
  35 + </template>
  36 + </uni-list-item>
  37 + <uni-list-item title="原计划发货日期">
  38 + <template v-slot:footer>
  39 + <uni-datetime-picker type="date" v-model="form.originPlanShipDate" :end="maxDeliveryDate" />
  40 + </template>
  41 + </uni-list-item>
  42 + </view>
  43 +
  44 + <!-- 产品 -->
  45 + <view class="section2">
  46 + <!-- mode="add" 允许编辑 -->
  47 + <Product mode="add" :list="initPurchaseOrderLineList" @change="purchaseOrderLineListChange"
  48 + :orderDate="form.orderDate" />
  49 + </view>
  50 + <view class="footer">
  51 + <view class="view-total">
  52 + <view class="head">合计</view>
  53 + <view class="row">
  54 + <view class="row2">
  55 + <text class="label">需发</text><text class="value">{{ form.totalQuantity }}</text>
  56 + </view>
  57 + <view class="row2">
  58 + <text class="label">实发</text><text class="value">{{ form.totalShippedQuantity }}</text>
  59 + </view>
  60 + </view>
  61 + <view class="row">
  62 + <view class="row2">
  63 + <text class="label">需求补货</text><text class="value">{{ form.totalSupplementaryQuantity }}</text>
  64 + </view>
  65 + </view>
  66 + </view>
  67 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
  68 + </view>
  69 + </uni-list>
  70 + </scroll-view>
  71 + </view>
  72 +</template>
  73 +
  74 +<script>
  75 +import { updateApi, getDetailApi } from '@/api/replenishment_order.js'
  76 +import Product from './product.vue'
  77 +
  78 +export default {
  79 + name: 'ReplenishmentOrderModify',
  80 + components: { Product },
  81 + data() {
  82 + return {
  83 + form: {
  84 + purchaseOrderId: '',
  85 + id: '',
  86 + // 订单基础信息
  87 + purchaseOrderName: '',
  88 + customerName: '',
  89 + customerId: '',
  90 + workshopName: '',
  91 + workshopId: '',
  92 + originPlanShipDate: '',
  93 + deptName: '',
  94 + deptId: '',
  95 + totalQuantity: '',
  96 + totalShippedQuantity: '',
  97 + totalSupplementaryQuantity: '',
  98 + code: '',
  99 + // 默认当前日期 格式为 yyyy-MM-dd
  100 + orderDate: new Date().toISOString().substring(0, 10),
  101 +
  102 + },
  103 + initPurchaseOrderLineList: [],
  104 + maxDeliveryDate: new Date().toISOString().substring(0, 10),
  105 + }
  106 + },
  107 + onLoad(query) {
  108 + const id = (query && (query.id || query.code)) || ''
  109 + if (id) {
  110 + this.loadDetail(id)
  111 + }
  112 + },
  113 + methods: {
  114 + async loadDetail(id) {
  115 + try {
  116 + const res = await getDetailApi(id)
  117 + const m = res.data || {}
  118 + const next = { ...this.form, ...m }
  119 + // 确保ID存在
  120 + next.id = m.id || id
  121 + // 映射列表
  122 + // 注意:详情返回的是 replenishmentOrderLineList,需要赋值给 initPurchaseOrderLineList 以便 Product 组件初始化
  123 + // 且需要处理字段兼容性,确保 Product 组件能正确显示和编辑
  124 + const lines = Array.isArray(m.replenishmentOrderLineList) ? m.replenishmentOrderLineList.map(x => ({
  125 + ...x,
  126 + // 确保 Product 组件需要的字段存在
  127 + // Product组件使用: quantity(需发), shippedQuantity(实发), supplementaryQuantity(需求补货), salesPrice(单价)
  128 + // 详情接口返回的字段应该已经包含了这些,如果有差异需要在此处转换
  129 + // 注意:add.vue中 onRelateConfirm 做了映射,这里是回显,通常直接使用即可
  130 + })) : []
  131 +
  132 + this.form = next;
  133 + this.initPurchaseOrderLineList = lines;
  134 + // 初始计算合计
  135 + this.calculateSummary(lines)
  136 + } catch (e) {
  137 + uni.showToast({ title: '加载失败', icon: 'none' })
  138 + }
  139 + },
  140 + validateRequired() {
  141 + const checks = [
  142 + { key: 'purchaseOrderName', label: '订单编号' }
  143 + ]
  144 + for (const it of checks) {
  145 + const val = this.form[it.key]
  146 + if (val === undefined || val === null || String(val).trim() === '') {
  147 + uni.showToast({ title: `请先选择${it.label}`, icon: 'none' })
  148 + return false
  149 + }
  150 + }
  151 + return true
  152 + },
  153 + validateLineListRequired() {
  154 + const list = Array.isArray(this.form.purchaseOrderLineList) ? this.form.purchaseOrderLineList : []
  155 + if (list.length === 0) {
  156 + uni.showToast({ title: '请先添加产品', icon: 'none' })
  157 + return false
  158 + }
  159 + const fields = [
  160 + { key: 'brand', label: '牌号' },
  161 + { key: 'quantity', label: '需发' },
  162 + { key: 'supplementaryQuantity', label: '需求补货' },
  163 + ]
  164 + for (let i = 0; i < list.length; i++) {
  165 + const it = list[i] || {}
  166 + for (const f of fields) {
  167 + const v = it && it[f.key]
  168 + if (v === undefined || v === null || String(v).trim() === '') {
  169 + uni.showToast({ title: `产品第${i + 1}条:${f.label}不能为空!`, icon: 'none' })
  170 + return false
  171 + }
  172 + }
  173 + const has = (v) => v !== undefined && v !== null && String(v).trim() !== ''
  174 + if (has(it.thicknessTolPos) && has(it.thicknessTolNeg)) {
  175 + const pos = Number(it.thicknessTolPos)
  176 + const neg = Number(it.thicknessTolNeg)
  177 + if (!(pos > neg)) {
  178 + uni.showToast({ title: `产品第${i + 1}条:厚度公差上限需大于下限`, icon: 'none' })
  179 + return false
  180 + }
  181 + }
  182 + if (has(it.widthTolPos) && has(it.widthTolNeg)) {
  183 + const pos = Number(it.widthTolPos)
  184 + const neg = Number(it.widthTolNeg)
  185 + if (!(pos > neg)) {
  186 + uni.showToast({ title: `产品第${i + 1}条:宽度公差上限需大于下限`, icon: 'none' })
  187 + return false
  188 + }
  189 + }
  190 + if (has(it.lengthTolPos) && has(it.lengthTolNeg)) {
  191 + const pos = Number(it.lengthTolPos)
  192 + const neg = Number(it.lengthTolNeg)
  193 + if (!(pos > neg)) {
  194 + uni.showToast({ title: `产品第${i + 1}条:长度公差上限需大于下限`, icon: 'none' })
  195 + return false
  196 + }
  197 + }
  198 + }
  199 + return true
  200 + },
  201 + async onSubmit() {
  202 + if (!this.validateRequired()) return
  203 + if (!this.validateLineListRequired()) return
  204 + const payload = { ...this.form }
  205 + // 后端接口通常期望 replenishmentOrderLineList
  206 + payload.replenishmentOrderLineList = payload.purchaseOrderLineList || [];
  207 + // 清理可能存在的多余字段
  208 + delete payload.purchaseOrderLineList;
  209 +
  210 + console.log('onSubmit__payload', payload)
  211 + try {
  212 + await updateApi(payload)
  213 + uni.showToast({ title: '保存成功', icon: 'success' })
  214 + setTimeout(() => { uni.redirectTo({ url: '/pages/replenishment_order/index' }) }, 300)
  215 + } catch (e) {
  216 + uni.showToast({ title: (e && e.msg) || '保存失败', icon: 'none' })
  217 + }
  218 + },
  219 + calculateSummary(list) {
  220 + const summary = (list || []).reduce((acc, it) => {
  221 + const qty = Number(it.supplementaryQuantity) || 0
  222 + const shipped = Number(it.shippedQuantity) || 0
  223 + const orderQty = Number(it.quantity) || 0
  224 + acc.totalSupplementaryQuantity += qty
  225 + acc.totalShippedQuantity += shipped
  226 + acc.totalQuantity += orderQty
  227 + return acc
  228 + }, { totalQuantity: 0, totalShippedQuantity: 0, totalSupplementaryQuantity: 0 })
  229 +
  230 + const fixedTotalQuantity = Number(summary.totalQuantity.toFixed(2))
  231 + const fixedTotalShippedQuantity = Number(summary.totalShippedQuantity.toFixed(2))
  232 + const fixedTotalSupplementaryQuantity = Number(summary.totalSupplementaryQuantity.toFixed(2))
  233 + this.form.totalQuantity = fixedTotalQuantity
  234 + this.form.totalShippedQuantity = fixedTotalShippedQuantity
  235 + this.form.totalSupplementaryQuantity = fixedTotalSupplementaryQuantity
  236 + },
  237 + purchaseOrderLineListChange(data) {
  238 + const list = Array.isArray(data) ? data : []
  239 + this.form.purchaseOrderLineList = list
  240 + this.calculateSummary(list)
  241 + },
  242 + }
  243 +}
  244 +</script>
  245 +
  246 +<style lang="scss" scoped>
  247 +.page {
  248 + display: flex;
  249 + flex-direction: column;
  250 + height: 100%;
  251 +}
  252 +
  253 +.scroll {
  254 + flex: 1;
  255 + padding: 6rpx 0 400rpx;
  256 +}
  257 +
  258 +
  259 +
  260 +.title-header {
  261 + background-color: #fff;
  262 + display: flex;
  263 + align-items: center;
  264 + padding: 32rpx 32rpx 22rpx;
  265 +
  266 + .title-header_icon {
  267 + width: 32rpx;
  268 + height: 28rpx;
  269 + margin-right: 16rpx;
  270 + }
  271 +
  272 + span {
  273 + color: rgba(0, 0, 0, 0.9);
  274 + font-size: 32rpx;
  275 + line-height: 44rpx;
  276 + font-weight: 600;
  277 + }
  278 +}
  279 +
  280 +
  281 +.section {
  282 + background: #fff;
  283 + margin-bottom: 20rpx;
  284 +}
  285 +
  286 +.section2 {
  287 + background: #f1f1f1;
  288 +}
  289 +
  290 +::v-deep .uni-list {
  291 + background: transparent;
  292 +
  293 + &-item {
  294 + &__extra-text {
  295 + font-size: 32rpx;
  296 + }
  297 +
  298 + &__content-title {
  299 + font-size: 32rpx;
  300 + color: rgba(0, 0, 0, 0.9);
  301 + }
  302 +
  303 + &__container {
  304 + padding: 32rpx;
  305 + // align-items: center;
  306 +
  307 + .uni-easyinput {
  308 +
  309 + .is-disabled {
  310 + background-color: transparent !important;
  311 + }
  312 +
  313 + &__placeholder-class {
  314 + font-size: 32rpx;
  315 + color: rgba(0, 0, 0, 0.4);
  316 + }
  317 +
  318 + &__content {
  319 + border: none;
  320 +
  321 + &-input {
  322 + padding-left: 0 !important;
  323 + height: 48rpx;
  324 + line-height: 48rpx;
  325 + font-size: 32rpx;
  326 + }
  327 +
  328 + .content-clear-icon {
  329 + font-size: 44rpx !important;
  330 + }
  331 + }
  332 + }
  333 +
  334 + .amount-row {
  335 + flex: 1;
  336 + display: flex;
  337 + align-items: center;
  338 +
  339 + .uni-easyinput {
  340 + flex: 1;
  341 + }
  342 +
  343 + .unit {
  344 + margin-left: 16rpx;
  345 + color: rgba(0, 0, 0, 0.9);
  346 + }
  347 + }
  348 +
  349 + .item-title,
  350 + .uni-list-item__content {
  351 + flex: none;
  352 + min-height: 48rpx;
  353 + line-height: 48rpx;
  354 + font-size: 32rpx;
  355 + position: relative;
  356 + width: 210rpx;
  357 + margin-right: 32rpx;
  358 + color: rgba(0, 0, 0, 0.9);
  359 + padding-right: 0;
  360 +
  361 +
  362 + .required {
  363 + color: red;
  364 + position: absolute;
  365 + top: 50%;
  366 + transform: translateY(-50%);
  367 + left: -16rpx;
  368 + }
  369 + }
  370 +
  371 + }
  372 +
  373 + &.select-item {
  374 + &.is-empty {
  375 + .uni-list-item__extra-text {
  376 + color: rgba(0, 0, 0, 0.4) !important;
  377 + }
  378 + }
  379 +
  380 + &.is-filled {
  381 + .uni-list-item__extra-text {
  382 + color: rgba(0, 0, 0, 0.9) !important;
  383 + }
  384 + }
  385 +
  386 + .serial-number-row {
  387 + display: flex;
  388 + align-items: center;
  389 + }
  390 +
  391 + }
  392 +
  393 + &.mgb10 {
  394 + margin-bottom: 20rpx;
  395 + }
  396 +
  397 + }
  398 +
  399 + .title-header {
  400 + background-color: #fff;
  401 + display: flex;
  402 + align-items: center;
  403 + padding: 32rpx 32rpx 22rpx;
  404 +
  405 + &_icon {
  406 + width: 32rpx;
  407 + height: 28rpx;
  408 + margin-right: 16rpx;
  409 + }
  410 +
  411 + span {
  412 + color: rgba(0, 0, 0, 0.9);
  413 + font-size: 32rpx;
  414 + line-height: 44rpx;
  415 + font-weight: 600;
  416 + }
  417 + }
  418 +}
  419 +
  420 +/* 只读 easyinput 根据内容自适应高度 */
  421 +::v-deep .uni-list-item__container {
  422 + align-items: flex-start;
  423 +}
  424 +
  425 +/* 只读文本样式 */
  426 +.readonly-text {
  427 + color: rgba(0, 0, 0, 0.9);
  428 + font-size: 32rpx;
  429 + line-height: 48rpx;
  430 + text-align: right;
  431 + white-space: pre-wrap;
  432 + word-break: break-all;
  433 +}
  434 +
  435 +
  436 +.footer {
  437 + position: fixed;
  438 + left: 0;
  439 + right: 0;
  440 + bottom: 0;
  441 + padding: 0 32rpx 32rpx;
  442 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  443 + background: #fff;
  444 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  445 + z-index: 10;
  446 +
  447 + .btn {
  448 + height: 80rpx;
  449 + line-height: 80rpx;
  450 + border-radius: 12rpx;
  451 + font-size: 32rpx;
  452 + }
  453 +
  454 + .submit {
  455 + background: $theme-primary;
  456 + color: #fff;
  457 + }
  458 +
  459 + .view-total {
  460 + padding: 20rpx 0;
  461 +
  462 + .head {
  463 + font-size: 32rpx;
  464 + font-weight: 600;
  465 + line-height: 50rpx;
  466 + color: rgba(0, 0, 0, 0.9);
  467 + padding-bottom: 16rpx;
  468 + margin-bottom: 24rpx;
  469 + border-bottom: 1px dashed #E7E7E7;
  470 + }
  471 +
  472 + .row {
  473 + display: flex;
  474 + margin-bottom: 24rpx;
  475 + line-height: 32rpx;
  476 +
  477 + .row2 {
  478 + width: 50%;
  479 + }
  480 +
  481 + .label {
  482 + width: 180rpx;
  483 + margin-right: 14rpx;
  484 + color: rgba(0, 0, 0, 0.6);
  485 + font-size: 32rpx;
  486 + }
  487 +
  488 + .value {
  489 + flex: 1;
  490 + color: rgba(0, 0, 0, 0.9);
  491 + font-size: 32rpx;
  492 + white-space: pre-wrap;
  493 + word-break: break-all;
  494 + font-weight: 600;
  495 + }
  496 + }
  497 + }
  498 +}
  499 +</style>
  1 +<template>
  2 + <view class="product">
  3 +
  4 + <!-- 新增&详情-产品 -->
  5 + <view class="header bp">
  6 + <image class="opCollapse" src="/static/images/title.png" />
  7 + <text class="title">{{ title || '产品' }}</text>
  8 + <view class="ops">
  9 + <image v-if="mode === 'add'" class="opAdd" @click="onAdd" src="/static/images/plus.png" />
  10 + <view v-if="mode === 'view'" class="op1" @click="toggleViewCollapse">
  11 + <image class="opAdd" :src="collapsedView ? '/static/images/down.png' : '/static/images/up.png'" />
  12 + <text class="op">{{ collapsedView ? '展开' : '收起' }} </text>
  13 + </view>
  14 +
  15 + </view>
  16 + </view>
  17 +
  18 + <view v-if="mode === 'add'" class="section">
  19 + <view v-for="(item, idx) in items" :key="'a-' + idx" class="block">
  20 + <uni-list class="edit-list">
  21 + <uni-list-item title="牌号">
  22 + <template v-slot:footer>
  23 + <uni-easyinput v-model="item.brand" placeholder="请输入牌号" :inputBorder="false" />
  24 + </template>
  25 + </uni-list-item>
  26 + <uni-list-item title="厚度(mm)">
  27 + <template v-slot:footer>
  28 + <uni-easyinput type="digit" v-model="item.thickness" placeholder="请输入厚度"
  29 + :inputBorder="false" @input="onNonNegativeNumberInput($event, item, idx, 'thickness')"
  30 + @blur="onNonNegativeNumberBlur(item, idx, 'thickness')" />
  31 + </template>
  32 + </uni-list-item>
  33 + <uni-list-item title="厚度公差上限(mm)">
  34 + <template v-slot:footer>
  35 + <uni-easyinput type="digit" v-model="item.thicknessTolPos" placeholder="请输入厚度公差上限"
  36 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'thicknessTolPos')"
  37 + @blur="onRealNumberBlur(item, idx, 'thicknessTolPos')" />
  38 + </template>
  39 + </uni-list-item>
  40 + <uni-list-item title="厚度公差下限(mm)">
  41 + <template v-slot:footer>
  42 + <uni-easyinput type="digit" v-model="item.thicknessTolNeg" placeholder="请输入厚度公差下限"
  43 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'thicknessTolNeg')"
  44 + @blur="onRealNumberBlur(item, idx, 'thicknessTolNeg')" />
  45 + </template>
  46 + </uni-list-item>
  47 + <view v-show="!item.collapsed">
  48 + <uni-list-item title="宽度(mm)">
  49 + <template v-slot:footer>
  50 + <uni-easyinput type="digit" v-model="item.width" placeholder="请输入宽度"
  51 + :inputBorder="false" @input="onNonNegativeNumberInput($event, item, idx, 'width')"
  52 + @blur="onNonNegativeNumberBlur(item, idx, 'width')" />
  53 + </template>
  54 + </uni-list-item>
  55 + <uni-list-item title="宽度公差上限(mm)">
  56 + <template v-slot:footer>
  57 + <uni-easyinput type="digit" v-model="item.widthTolPos" placeholder="请输入宽度公差上限"
  58 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'widthTolPos')"
  59 + @blur="onRealNumberBlur(item, idx, 'widthTolPos')" />
  60 + </template>
  61 + </uni-list-item>
  62 + <uni-list-item title="宽度公差下限(mm)">
  63 + <template v-slot:footer>
  64 + <uni-easyinput type="digit" v-model="item.widthTolNeg" placeholder="请输入宽度公差下限"
  65 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'widthTolNeg')"
  66 + @blur="onRealNumberBlur(item, idx, 'widthTolNeg')" />
  67 + </template>
  68 + </uni-list-item>
  69 + <uni-list-item title="长度(mm)">
  70 + <template v-slot:footer>
  71 + <uni-easyinput type="digit" v-model="item.length" placeholder="请输入长度"
  72 + :inputBorder="false" @input="onNonNegativeNumberInput($event, item, idx, 'length')"
  73 + @blur="onNonNegativeNumberBlur(item, idx, 'length')" />
  74 + </template>
  75 + </uni-list-item>
  76 + <uni-list-item title="长度公差上限(mm)">
  77 + <template v-slot:footer>
  78 + <uni-easyinput type="digit" v-model="item.lengthTolPos" placeholder="请输入长度公差上限"
  79 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'lengthTolPos')"
  80 + @blur="onRealNumberBlur(item, idx, 'lengthTolPos')" />
  81 + </template>
  82 + </uni-list-item>
  83 + <uni-list-item title="长度公差下限(mm)">
  84 + <template v-slot:footer>
  85 + <uni-easyinput type="digit" v-model="item.lengthTolNeg" placeholder="请输入长度公差下限"
  86 + :inputBorder="false" @input="onRealNumberInput($event, item, idx, 'lengthTolNeg')"
  87 + @blur="onRealNumberBlur(item, idx, 'lengthTolNeg')" />
  88 + </template>
  89 + </uni-list-item>
  90 + <uni-list-item title="状态">
  91 + <template v-slot:footer>
  92 + <uni-easyinput v-model="item.status" placeholder="请输入状态" :inputBorder="false" />
  93 + </template>
  94 + </uni-list-item>
  95 + <uni-list-item title="需发数量(kg)">
  96 + <template v-slot:footer>
  97 + <uni-easyinput type="digit" v-model="item.quantity" placeholder="请输入数量kg"
  98 + :inputBorder="false"
  99 + @input="onTwoDecimalInput($event, item, idx, 'quantity')"
  100 + @blur="onTwoDecimalBlur(item, idx, 'quantity')"
  101 + />
  102 + </template>
  103 + </uni-list-item>
  104 + <uni-list-item title="实发数量(kg)">
  105 + <template v-slot:footer>
  106 + <uni-easyinput type="digit" v-model="item.shippedQuantity" placeholder="请输入实发数量kg"
  107 + :inputBorder="false"
  108 + @input="onTwoDecimalInput($event, item, idx, 'shippedQuantity')"
  109 + @blur="onTwoDecimalBlur(item, idx, 'shippedQuantity')"
  110 + />
  111 + </template>
  112 + </uni-list-item>
  113 + <uni-list-item title="需求补货数量(kg)">
  114 + <template v-slot:footer>
  115 + <uni-easyinput type="digit" v-model="item.supplementaryQuantity"
  116 + placeholder="请输入需求补货数量kg" :inputBorder="false"
  117 + @input="onTwoDecimalInput($event, item, idx, 'supplementaryQuantity')"
  118 + @blur="onTwoDecimalBlur(item, idx, 'supplementaryQuantity')"
  119 + />
  120 + </template>
  121 + </uni-list-item>
  122 + <uni-list-item class="amount-item">
  123 + <template v-slot:body>
  124 + <view class="item-title"><text>单价</text></view>
  125 + </template>
  126 + <template v-slot:footer>
  127 + <view class="amount-row">
  128 + <uni-easyinput type="digit" v-model="item.salesPrice" placeholder="0.00"
  129 + :inputBorder="false"
  130 + @input="onNonNegativeNumberInput($event, item, idx, 'salesPrice')"
  131 + @blur="onNonNegativeNumberBlur(item, idx, 'salesPrice')" />
  132 + <text class="unit">元</text>
  133 + </view>
  134 + </template>
  135 + </uni-list-item>
  136 + <uni-list-item class="amount-item">
  137 + <template v-slot:body>
  138 + <view class="item-title"><text>包装费</text></view>
  139 + </template>
  140 + <template v-slot:footer>
  141 + <view class="amount-row">
  142 + <uni-easyinput type="digit" v-model="item.packagingFee" placeholder="0.00"
  143 + :inputBorder="false"
  144 + @input="onNonNegativeNumberInput($event, item, idx, 'packagingFee')"
  145 + @blur="onNonNegativeNumberBlur(item, idx, 'packagingFee')" />
  146 + <text class="unit">元</text>
  147 + </view>
  148 + </template>
  149 + </uni-list-item>
  150 + <uni-list-item title="生产科(车间)确认交付时间">
  151 + <template v-slot:footer>
  152 + <view class="value">{{ item.confirmedDeliveryDate }}</view>
  153 + </template>
  154 + </uni-list-item>
  155 + <uni-list-item title="备注">
  156 + <template v-slot:footer>
  157 + <uni-easyinput v-model="item.remarks" placeholder="请输入备注" :inputBorder="false" />
  158 + </template>
  159 + </uni-list-item>
  160 + </view>
  161 + </uni-list>
  162 +
  163 + <view class="block-ops">
  164 + <div class="del" @click="onRemove(item.purchaseOrderId)">
  165 + <image src="/static/images/delete.png" class="icon" />
  166 + 删除
  167 + </div>
  168 + <div class="toggle" @click="toggleItem(idx)">
  169 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'"
  170 + class="icon" />
  171 + {{ item.collapsed ? '展开' : '收起' }}
  172 + </div>
  173 + </view>
  174 + </view>
  175 + </view>
  176 +
  177 + <view v-else-if="mode === 'fill'" class="section">
  178 + <view v-for="(item, idx) in items" :key="'a-' + idx" class="block">
  179 + <uni-list class="edit-list">
  180 + <uni-list-item title="牌号">
  181 + <template v-slot:footer>
  182 + <text class="value">{{ item.brand }}</text>
  183 + </template>
  184 + </uni-list-item>
  185 + <!-- 厚(公差) * 宽(公差) * 长(公差) -->
  186 + <uni-list-item title="规格(mm)">
  187 + <template v-slot:footer>
  188 + <view class="value value-spec">
  189 + <view v-if="item.thickness" class="value-spec_val">{{ item.thickness }}</view>
  190 + <view v-if="item.thickness" class="value-spec_box">
  191 + <view v-if="item.thicknessTolPos" class="value-spec_box_1">{{ item.thicknessTolPos >
  192 + 0 ? '+'
  193 + +
  194 + item.thicknessTolPos : item.thicknessTolPos }}
  195 + </view>
  196 + <view v-if="item.thicknessTolNeg" class="value-spec_box_2">{{ item.thicknessTolNeg >
  197 + 0 ? '+'
  198 + +
  199 + item.thicknessTolNeg : item.thicknessTolNeg }}
  200 + </view>
  201 + </view>
  202 + <view v-if="item.width" class="value-spec_val p12">*</view>
  203 + <view v-if="item.width" class="value-spec_val">{{ item.width }}</view>
  204 + <view v-if="item.width" class="value-spec_box">
  205 + <view v-if="item.widthTolPos" class="value-spec_box_1">{{ item.widthTolPos > 0 ? '+'
  206 + +
  207 + item.widthTolPos : item.widthTolPos }}
  208 + </view>
  209 + <view v-if="item.widthTolNeg" class="value-spec_box_2">{{ item.widthTolNeg > 0 ? '+'
  210 + +
  211 + item.widthTolNeg : item.widthTolNeg }}
  212 + </view>
  213 + </view>
  214 + <view v-if="item.length" class="value-spec_val p12">*</view>
  215 + <view v-if="item.length" class="value-spec_val">{{ item.length }}</view>
  216 + <view v-if="item.length" class="value-spec_box">
  217 + <view v-if="item.lengthTolPos" class="value-spec_box_1">{{ item.lengthTolPos > 0 ?
  218 + '+' +
  219 + item.lengthTolPos : item.lengthTolPos }}
  220 + </view>
  221 + <view v-if="item.lengthTolNeg" class="value-spec_box_2">{{ item.lengthTolNeg > 0 ?
  222 + '+' +
  223 + item.lengthTolNeg : item.lengthTolNeg }}
  224 + </view>
  225 + </view>
  226 + </view>
  227 + </template>
  228 + </uni-list-item>
  229 + <view v-show="!item.collapsed">
  230 + <uni-list-item title="状态">
  231 + <template v-slot:footer>
  232 + <text class="value">{{ item.status }}</text>
  233 + </template>
  234 + </uni-list-item>
  235 + <uni-list-item title="需发数量(kg)">
  236 + <template v-slot:footer>
  237 + <text class="value">{{ item.quantity }}</text>
  238 + </template>
  239 + </uni-list-item>
  240 + <uni-list-item title="实发数量(kg)">
  241 + <template v-slot:footer>
  242 + <text class="value">{{ item.shippedQuantity }}</text>
  243 + </template>
  244 + </uni-list-item>
  245 + <uni-list-item title="需求补货数量(kg)">
  246 + <template v-slot:footer>
  247 + <text class="value">{{ item.supplementaryQuantity }}</text>
  248 + </template>
  249 + </uni-list-item>
  250 + <uni-list-item class="amount-item" title="单价(元/kg)">
  251 + <template v-slot:footer>
  252 + <text class="value">{{ item.salesPrice }}</text>
  253 + </template>
  254 + </uni-list-item>
  255 + <uni-list-item class="amount-item" title="包装费(元/kg)">
  256 + <template v-slot:footer>
  257 + <text class="value">{{ item.packagingFee }}</text>
  258 + </template>
  259 + </uni-list-item>
  260 + <uni-list-item title="生产科(车间)确认交付时间">
  261 + <template v-slot:footer>
  262 + <uni-datetime-picker type="date"
  263 + v-model="item.confirmedDeliveryDate" />
  264 + </template>
  265 + </uni-list-item>
  266 + <uni-list-item title="备注">
  267 + <template v-slot:footer>
  268 + <text class="value">{{ item.remarks }}元</text>
  269 + </template>
  270 + </uni-list-item>
  271 + </view>
  272 + </uni-list>
  273 +
  274 + <view class="block-ops">
  275 + <div class="toggle" @click="toggleItem(idx)">
  276 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'"
  277 + class="icon" />
  278 + {{ item.collapsed ? '展开' : '收起' }}
  279 + </div>
  280 + </view>
  281 + </view>
  282 + </view>
  283 +
  284 + <view v-else-if="mode === 'approve'" class="section">
  285 + <view v-for="(item, idx) in items" :key="'a-' + idx" class="block">
  286 + <uni-list class="edit-list">
  287 + <uni-list-item title="牌号">
  288 + <template v-slot:footer>
  289 + <text class="value">{{ item.brand }}</text>
  290 + </template>
  291 + </uni-list-item>
  292 + <!-- 厚(公差) * 宽(公差) * 长(公差) -->
  293 + <uni-list-item title="规格(mm)">
  294 + <template v-slot:footer>
  295 + <view class="value value-spec">
  296 + <view v-if="item.thickness" class="value-spec_val">{{ item.thickness }}</view>
  297 + <view v-if="item.thickness" class="value-spec_box">
  298 + <view v-if="item.thicknessTolPos" class="value-spec_box_1">{{ item.thicknessTolPos >
  299 + 0 ? '+'
  300 + +
  301 + item.thicknessTolPos : item.thicknessTolPos }}
  302 + </view>
  303 + <view v-if="item.thicknessTolNeg" class="value-spec_box_2">{{ item.thicknessTolNeg >
  304 + 0 ? '+'
  305 + +
  306 + item.thicknessTolNeg : item.thicknessTolNeg }}
  307 + </view>
  308 + </view>
  309 + <view v-if="item.width" class="value-spec_val p12">*</view>
  310 + <view v-if="item.width" class="value-spec_val">{{ item.width }}</view>
  311 + <view v-if="item.width" class="value-spec_box">
  312 + <view v-if="item.widthTolPos" class="value-spec_box_1">{{ item.widthTolPos > 0 ? '+'
  313 + +
  314 + item.widthTolPos : item.widthTolPos }}
  315 + </view>
  316 + <view v-if="item.widthTolNeg" class="value-spec_box_2">{{ item.widthTolNeg > 0 ? '+'
  317 + +
  318 + item.widthTolNeg : item.widthTolNeg }}
  319 + </view>
  320 + </view>
  321 + <view v-if="item.length" class="value-spec_val p12">*</view>
  322 + <view v-if="item.length" class="value-spec_val">{{ item.length }}</view>
  323 + <view v-if="item.length" class="value-spec_box">
  324 + <view v-if="item.lengthTolPos" class="value-spec_box_1">{{ item.lengthTolPos > 0 ?
  325 + '+' +
  326 + item.lengthTolPos : item.lengthTolPos }}
  327 + </view>
  328 + <view v-if="item.lengthTolNeg" class="value-spec_box_2">{{ item.lengthTolNeg > 0 ?
  329 + '+' +
  330 + item.lengthTolNeg : item.lengthTolNeg }}
  331 + </view>
  332 + </view>
  333 + </view>
  334 + </template>
  335 + </uni-list-item>
  336 + <view v-show="!item.collapsed">
  337 + <uni-list-item title="状态">
  338 + <template v-slot:footer>
  339 + <text class="value">{{ item.status }}</text>
  340 + </template>
  341 + </uni-list-item>
  342 + <uni-list-item title="需发数量(kg)">
  343 + <template v-slot:footer>
  344 + <text class="value">{{ item.quantity }}</text>
  345 + </template>
  346 + </uni-list-item>
  347 + <uni-list-item title="实发数量(kg)">
  348 + <template v-slot:footer>
  349 + <text class="value">{{ item.shippedQuantity }}</text>
  350 + </template>
  351 + </uni-list-item>
  352 + <uni-list-item title="需求补货数量(kg)">
  353 + <template v-slot:footer>
  354 + <uni-easyinput v-if="canEditSupplementary" type="digit" v-model="item.supplementaryQuantity"
  355 + placeholder="请输入需求补货数量kg" :inputBorder="false"
  356 + @input="onTwoDecimalInput($event, item, idx, 'supplementaryQuantity')"
  357 + @blur="onTwoDecimalBlur(item, idx, 'supplementaryQuantity')"
  358 + />
  359 + <text v-else class="value">{{ item.supplementaryQuantity }}</text>
  360 + </template>
  361 + </uni-list-item>
  362 + <uni-list-item class="amount-item" title="单价(元/kg)">
  363 + <template v-slot:footer>
  364 + <text class="value">{{ item.salesPrice }}</text>
  365 + </template>
  366 + </uni-list-item>
  367 + <uni-list-item class="amount-item" title="包装费(元/kg)">
  368 + <template v-slot:footer>
  369 + <text class="value">{{ item.packagingFee }}</text>
  370 + </template>
  371 + </uni-list-item>
  372 + <uni-list-item title="生产科(车间)确认交付时间">
  373 + <template v-slot:footer>
  374 + <text class="value">{{ item.confirmedDeliveryDate }}</text>
  375 + </template>
  376 + </uni-list-item>
  377 + <uni-list-item title="备注">
  378 + <template v-slot:footer>
  379 + <text class="value">{{ item.remarks }}元</text>
  380 + </template>
  381 + </uni-list-item>
  382 + </view>
  383 + </uni-list>
  384 +
  385 + <view class="block-ops">
  386 + <div class="toggle" @click="toggleItem(idx)">
  387 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'"
  388 + class="icon" />
  389 + {{ item.collapsed ? '展开' : '收起' }}
  390 + </div>
  391 + </view>
  392 + </view>
  393 + </view>
  394 +
  395 + <view v-else-if="mode === 'view'" class="view-list" v-show="!collapsedView">
  396 + <view v-for="(item, idx) in items" :key="'v-' + idx" class="card">
  397 + <view class="row"><text class="label">牌号</text><text class="value">{{ item.brand }}</text></view>
  398 + <!-- 厚(公差) * 宽(公差) * 长(公差) -->
  399 + <view class="row row-spec"><text class="label">规格(mm)</text>
  400 + <view class="value value-spec">
  401 + <view v-if="item.thickness" class="value-spec_val">{{ item.thickness }}</view>
  402 + <view v-if="item.thickness" class="value-spec_box">
  403 + <view v-if="item.thicknessTolPos" class="value-spec_box_1">{{ item.thicknessTolPos > 0 ? '+'
  404 + +
  405 + item.thicknessTolPos : item.thicknessTolPos }}
  406 + </view>
  407 + <view v-if="item.thicknessTolNeg" class="value-spec_box_2">{{ item.thicknessTolNeg > 0 ? '+'
  408 + +
  409 + item.thicknessTolNeg : item.thicknessTolNeg }}
  410 + </view>
  411 + </view>
  412 + <view v-if="item.width" class="value-spec_val p12">*</view>
  413 + <view v-if="item.width" class="value-spec_val">{{ item.width }}</view>
  414 + <view v-if="item.width" class="value-spec_box">
  415 + <view v-if="item.widthTolPos" class="value-spec_box_1">{{ item.widthTolPos > 0 ? '+' +
  416 + item.widthTolPos : item.widthTolPos }}
  417 + </view>
  418 + <view v-if="item.widthTolNeg" class="value-spec_box_2">{{ item.widthTolNeg > 0 ? '+' +
  419 + item.widthTolNeg : item.widthTolNeg }}
  420 + </view>
  421 + </view>
  422 + <view v-if="item.length" class="value-spec_val p12">*</view>
  423 + <view v-if="item.length" class="value-spec_val">{{ item.length }}</view>
  424 + <view v-if="item.length" class="value-spec_box">
  425 + <view v-if="item.lengthTolPos" class="value-spec_box_1">{{ item.lengthTolPos > 0 ? '+' +
  426 + item.lengthTolPos : item.lengthTolPos }}
  427 + </view>
  428 + <view v-if="item.lengthTolNeg" class="value-spec_box_2">{{ item.lengthTolNeg > 0 ? '+' +
  429 + item.lengthTolNeg : item.lengthTolNeg }}
  430 + </view>
  431 + </view>
  432 + </view>
  433 + </view>
  434 + <view class="row"><text class="label">状态</text><text class="value">{{ item.status }}</text></view>
  435 + <view class="row"><text class="label">需发数量(kg)</text><text class="value">{{ item.quantity }}</text>
  436 + </view>
  437 + <view class="row"><text class="label">实发数量(kg)</text><text class="value">{{ item.shippedQuantity
  438 + }}</text>
  439 + </view>
  440 + <view class="row"><text class="label">需求补货数量(kg)</text><text class="value">{{ item.supplementaryQuantity
  441 + }}</text></view>
  442 + <view class="row"><text class="label">单价(元/kg)</text><text class="value">{{ item.salesPrice }}</text>
  443 + </view>
  444 + <view class="row"><text class="label">包装费(元/kg)</text><text class="value">{{
  445 + item.packagingFee }}</text></view>
  446 + <view class="row"><text class="label">生产科(车间)确认交付时间</text><text class="value">{{
  447 + item.confirmedDeliveryDate
  448 + }}</text>
  449 + </view>
  450 + <view class="row"><text class="label">备注</text><text class="value">{{ item.remarks }}</text></view>
  451 + </view>
  452 + <view class="view-total">
  453 + <view class="head">合计</view>
  454 + <view class="row">
  455 + <text class="label">需发</text><text class="value">{{ totalQuantity }}</text>
  456 + </view>
  457 + <view class="row">
  458 + <text class="label">实发</text><text class="value">{{ totalShippedQuantity }}</text>
  459 + </view>
  460 + <view class="row">
  461 + <text class="label">需求补货</text><text class="value">{{ totalSupplementaryQuantity }}</text>
  462 + </view>
  463 + </view>
  464 + </view>
  465 +
  466 + </view>
  467 +</template>
  468 +<script>
  469 +import { uuid } from '@/utils/uuid.js'
  470 +export default {
  471 + name: 'Product',
  472 + props: {
  473 + title: { type: String, default: '' },
  474 + mode: { type: String, default: 'add' },
  475 + list: { type: Array, default: () => [] },
  476 + max: { type: Number, default: 8 },
  477 + totalQuantity: { type: Number, default: 0 },
  478 + totalShippedQuantity: { type: Number, default: 0 },
  479 + totalSupplementaryQuantity: { type: Number, default: 0 },
  480 + orderDate: { type: String, default: '' },
  481 + canEditSupplementary: { type: Boolean, default: false }
  482 + },
  483 + data() {
  484 + return {
  485 + items: [],
  486 + collapsedView: false,
  487 + roleCodes: [],
  488 + }
  489 + },
  490 + computed: {
  491 + minDeliveryDate() {
  492 + const s = this.orderDate
  493 + if (!s) return ''
  494 + const parts = String(s).split('-')
  495 + const y = Number(parts[0])
  496 + const m = Number(parts[1])
  497 + const d = Number(parts[2])
  498 + if (!y || !m || !d) return ''
  499 + const dt = new Date(y, m - 1, d)
  500 + dt.setDate(dt.getDate() + 1)
  501 + const yy = dt.getFullYear()
  502 + const mm = String(dt.getMonth() + 1).padStart(2, '0')
  503 + const dd = String(dt.getDate()).padStart(2, '0')
  504 + return `${yy}/${mm}/${dd}`
  505 + },
  506 + // roleCodes() {
  507 + // const g = this.$store && this.$store.getters
  508 + // return (g && g.roleCodes) || [];
  509 + // },
  510 + },
  511 + watch: {
  512 + items: {
  513 + handler() { this.emitChange() },
  514 + deep: true
  515 + },
  516 + list: {
  517 + handler(v) {
  518 + const arr = Array.isArray(v) ? v : []
  519 + this.items = arr.map(x => {
  520 + const it = { ...this.defaultItem(), ...x, collapsed: true }
  521 + return it
  522 + })
  523 + },
  524 + deep: true
  525 + },
  526 + },
  527 + created() {
  528 + const init = Array.isArray(this.list) && this.list.length > 0 ? this.list.map(v => ({ ...this.defaultItem(), ...v, collapsed: false })) : [{ ...this.defaultItem(), collapsed: false }]
  529 + this.items = init;
  530 + },
  531 + methods: {
  532 + defaultItem() {
  533 + return {
  534 + purchaseOrderId: uuid(),
  535 + collapsed: false,
  536 + id: '',
  537 + // 厚度公差
  538 + thicknessTolPos: '',
  539 + thicknessTolNeg: '',
  540 + // 宽度公差
  541 + widthTolPos: '',
  542 + widthTolNeg: '',
  543 + // 长度公差
  544 + lengthTolPos: '',
  545 + lengthTolNeg: '',
  546 + // 其他字段
  547 + industry: '',
  548 + quality: '',
  549 + brand: '',
  550 + thickness: '',
  551 + width: '',
  552 + length: '',
  553 + status: '',
  554 + quantity: '',
  555 + shippedQuantity: '',
  556 + supplementaryQuantity: '',
  557 + confirmedDeliveryDate: '',
  558 + remarks: '',
  559 + }
  560 + },
  561 +
  562 + onAdd() {
  563 + const obj = this.defaultItem()
  564 + obj.collapsed = true
  565 + this.items.push(obj)
  566 + this.emitChange()
  567 + },
  568 + onRemove(id) {
  569 + if (!id) return
  570 + uni.showModal({
  571 + title: '系统提示',
  572 + content: '是否确定删除选中的产品?',
  573 + confirmText: '确定',
  574 + cancelText: '取消',
  575 + success: (res) => {
  576 + if (res && res.confirm) {
  577 + const i = this.items.findIndex(it => String(it.purchaseOrderId) === String(id))
  578 + if (i >= 0) {
  579 + this.items.splice(i, 1)
  580 + this.emitChange()
  581 + }
  582 + }
  583 + }
  584 + })
  585 + },
  586 + toggleItem(idx) {
  587 + const it = this.items[idx]
  588 + if (!it) return
  589 + it.collapsed = !it.collapsed
  590 + this.$set(this.items, idx, it)
  591 + },
  592 + emitChange() {
  593 + const out = this.items.map(it => ({ ...it }))
  594 + this.$emit('input', out)
  595 + this.$emit('update:value', out)
  596 + this.$emit('change', out)
  597 + },
  598 + onNonNegativeNumberInput(val, item, idx, field) {
  599 + let v = String(val != null ? val : (item && item[field]) || '')
  600 + v = v.replace(/[^0-9.]/g, '')
  601 + v = v.replace(/(\..*)\./g, '$1')
  602 + if (v.startsWith('.')) v = '0' + v
  603 + if (v === '') { item[field] = ''; if (typeof idx === 'number') this.$set(this.items, idx, { ...item }); return }
  604 + const num = Number(v)
  605 + if (isNaN(num) || num < 0) {
  606 + item[field] = '0'
  607 + } else {
  608 + item[field] = v
  609 + }
  610 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  611 + },
  612 + onNonNegativeNumberBlur(item, idx, field) {
  613 + const v = String((item && item[field]) || '')
  614 + const num = Number(v)
  615 + if (isNaN(num) || num < 0) item[field] = '0'
  616 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  617 + },
  618 + onRealNumberInput(val, item, idx, field) {
  619 + let s = String(val != null ? val : (item && item[field]) || '')
  620 + const neg = s.trim().startsWith('-')
  621 + s = s.replace(/[^0-9.\-]/g, '')
  622 + s = s.replace(/(?!^)-/g, '')
  623 + s = s.replace(/(\..*)\./g, '$1')
  624 + if (s.startsWith('.')) s = '0' + s
  625 + if (s.startsWith('-.')) s = '-0.' + s.slice(2)
  626 + if (neg && !s.startsWith('-')) s = '-' + s.replace(/-/g, '')
  627 + item[field] = s
  628 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  629 + },
  630 + onRealNumberBlur(item, idx, field) {
  631 + const s = String((item && item[field]) || '')
  632 + if (s === '') { if (typeof idx === 'number') this.$set(this.items, idx, { ...item }); return }
  633 + const n = Number(s)
  634 + if (isNaN(n)) item[field] = ''
  635 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  636 + },
  637 + // 限制输入为2位小数
  638 + onTwoDecimalInput(val, item, idx, field) {
  639 + let v = String(val != null ? val : (item && item[field]) || '')
  640 + v = v.replace(/[^0-9.]/g, '')
  641 + v = v.replace(/(\..*)\./g, '$1')
  642 +
  643 + // Restrict to 2 decimal places
  644 + const decimalIndex = v.indexOf('.')
  645 + if (decimalIndex !== -1 && v.length > decimalIndex + 3) {
  646 + v = v.substring(0, decimalIndex + 3)
  647 + }
  648 +
  649 + if (v.startsWith('.')) v = '0' + v
  650 +
  651 + // If the value was modified (truncated or cleaned)
  652 + if (String(val) !== v) {
  653 + // Hack: Temporarily set the dirty value to trigger Vue update mechanism
  654 + // This ensures that when we set the clean value back, Vue detects a change
  655 + item[field] = val
  656 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  657 +
  658 + // Then revert to the clean value asynchronously
  659 + setTimeout(() => {
  660 + item[field] = v
  661 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  662 + }, 0)
  663 + } else {
  664 + item[field] = v
  665 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  666 + }
  667 + },
  668 + onTwoDecimalBlur(item, idx, field) {
  669 + let v = String((item && item[field]) || '')
  670 + const num = Number(v)
  671 + if (isNaN(num) || num < 0) {
  672 + item[field] = '0'
  673 + } else {
  674 + if (v.endsWith('.')) {
  675 + item[field] = v.slice(0, -1)
  676 + }
  677 + }
  678 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  679 + },
  680 + toggleViewCollapse() {
  681 + this.collapsedView = !this.collapsedView
  682 + },
  683 + onDeliveryChange(e, item, idx) {
  684 + const getStr = (x) => {
  685 + if (x && x.detail && x.detail.value !== undefined) return x.detail.value
  686 + if (typeof x === 'string') return x
  687 + return item && item.deliveryDate ? item.deliveryDate : ''
  688 + }
  689 + const val = getStr(e)
  690 + if (!val || !this.orderDate) return
  691 + const parse = (s) => {
  692 + const p = String(s).replace(/\//g, '-').split('-')
  693 + const y = Number(p[0])
  694 + const m = Number(p[1])
  695 + const d = Number(p[2])
  696 + return new Date(y, m - 1, d)
  697 + }
  698 + const sel = parse(val)
  699 + const ord = parse(this.orderDate)
  700 + if (!(sel > ord)) {
  701 + item.deliveryDate = ''
  702 + this.$set(this.items, idx, { ...item })
  703 + uni.showToast({ title: '发货日期必须大于订货日期', icon: 'none' })
  704 + }
  705 + }
  706 + }
  707 +}
  708 +</script>
  709 +<style lang="scss" scoped>
  710 +.header {
  711 + background-color: #fff;
  712 + display: flex;
  713 + align-items: center;
  714 + padding: 24rpx 32rpx;
  715 +
  716 + &.bp {
  717 + border-bottom: 1px solid #f0f0f0;
  718 + }
  719 +}
  720 +
  721 +.dot {
  722 + width: 16rpx;
  723 + height: 16rpx;
  724 + background: #3D48A3;
  725 + border-radius: 50%;
  726 + margin-right: 12rpx;
  727 +}
  728 +
  729 +.title {
  730 + font-size: 32rpx;
  731 + color: rgba(0, 0, 0, 0.9);
  732 + font-weight: 600;
  733 +}
  734 +
  735 +.ops {
  736 + margin-left: auto;
  737 +}
  738 +
  739 +.op {
  740 + color: $theme-primary;
  741 + font-size: 28rpx;
  742 + margin-left: 8rpx;
  743 +}
  744 +
  745 +.op1 {
  746 + display: flex;
  747 + align-items: center;
  748 +}
  749 +
  750 +.opAdd {
  751 + color: rgba(0, 0, 0, 0.6);
  752 + width: 40rpx;
  753 + height: 40rpx;
  754 +}
  755 +
  756 +.opCollapse {
  757 + color: rgba(0, 0, 0, 0.6);
  758 + width: 32rpx;
  759 + height: 28rpx;
  760 + margin-right: 16rpx;
  761 +}
  762 +
  763 +::v-deep .uni-list {
  764 + background: transparent;
  765 +
  766 + .uni-list--border-top {
  767 + background-color: transparent !important;
  768 + }
  769 +
  770 + &-item {
  771 + &__extra-text {
  772 + font-size: 32rpx;
  773 + }
  774 +
  775 + &__content-title {
  776 + font-size: 32rpx;
  777 + color: rgba(0, 0, 0, 0.9);
  778 + }
  779 +
  780 + &__container {
  781 + padding: 32rpx;
  782 +
  783 + .uni-easyinput {
  784 +
  785 + .is-disabled {
  786 + background-color: transparent !important;
  787 + }
  788 +
  789 + &__placeholder-class {
  790 + font-size: 32rpx;
  791 + color: rgba(0, 0, 0, 0.4);
  792 + }
  793 +
  794 + &__content {
  795 + border: none;
  796 +
  797 + &-input {
  798 + padding-left: 0 !important;
  799 + height: 48rpx;
  800 + line-height: 48rpx;
  801 + font-size: 32rpx;
  802 + }
  803 +
  804 + .content-clear-icon {
  805 + font-size: 44rpx !important;
  806 + }
  807 + }
  808 + }
  809 +
  810 + .amount-row {
  811 + flex: 1;
  812 + display: flex;
  813 + align-items: center;
  814 +
  815 + .uni-easyinput {
  816 + flex: 1;
  817 + }
  818 +
  819 + .unit {
  820 + margin-left: 16rpx;
  821 + color: rgba(0, 0, 0, 0.9);
  822 + }
  823 + }
  824 +
  825 + .item-title,
  826 + .uni-list-item__content {
  827 + flex: none;
  828 + min-height: 48rpx;
  829 + line-height: 48rpx;
  830 + font-size: 32rpx;
  831 + position: relative;
  832 + width: 210rpx;
  833 + margin-right: 32rpx;
  834 + color: rgba(0, 0, 0, 0.9);
  835 + padding-right: 0;
  836 +
  837 +
  838 + .required {
  839 + color: red;
  840 + position: absolute;
  841 + top: 50%;
  842 + transform: translateY(-50%);
  843 + left: -16rpx;
  844 + }
  845 + }
  846 +
  847 + }
  848 +
  849 + &.select-item {
  850 + &.is-empty {
  851 + .uni-list-item__extra-text {
  852 + color: rgba(0, 0, 0, 0.4) !important;
  853 + }
  854 + }
  855 +
  856 + &.is-filled {
  857 + .uni-list-item__extra-text {
  858 + color: rgba(0, 0, 0, 0.9) !important;
  859 + }
  860 + }
  861 +
  862 + .serial-number-row {
  863 + display: flex;
  864 + align-items: center;
  865 + }
  866 +
  867 + }
  868 +
  869 + &.mgb10 {
  870 + margin-bottom: 20rpx;
  871 + }
  872 +
  873 + }
  874 +
  875 + .title-header {
  876 + background-color: #fff;
  877 + display: flex;
  878 + align-items: center;
  879 + padding: 32rpx 32rpx 22rpx;
  880 +
  881 + &_icon {
  882 + width: 32rpx;
  883 + height: 28rpx;
  884 + margin-right: 16rpx;
  885 + }
  886 +
  887 + span {
  888 + color: rgba(0, 0, 0, 0.9);
  889 + font-size: 32rpx;
  890 + line-height: 44rpx;
  891 + font-weight: 600;
  892 + }
  893 + }
  894 +}
  895 +
  896 +/* 只读 easyinput 根据内容自适应高度 */
  897 +::v-deep .uni-list-item__container {
  898 + align-items: flex-start;
  899 +}
  900 +
  901 +.block-ops {
  902 + display: flex;
  903 + padding: 20rpx 32rpx 20rpx;
  904 + justify-content: space-around;
  905 +}
  906 +
  907 +.del {
  908 + color: #D54941;
  909 + font-size: 28rpx;
  910 + display: flex;
  911 + align-items: center;
  912 +
  913 + image {
  914 + width: 40rpx;
  915 + height: 40rpx;
  916 + }
  917 +}
  918 +
  919 +.toggle {
  920 + color: $theme-primary;
  921 + font-size: 28rpx;
  922 + display: flex;
  923 + align-items: center;
  924 +
  925 + image {
  926 + width: 40rpx;
  927 + height: 40rpx;
  928 + }
  929 +}
  930 +
  931 +.section {
  932 + background: #f1f1f1;
  933 + margin-bottom: 20rpx;
  934 +
  935 + .block {
  936 + background: #ffffff;
  937 + // padding: 32rpx 0;
  938 + margin-bottom: 20rpx;
  939 +
  940 + &:last-child {
  941 + margin-bottom: 0;
  942 + }
  943 + }
  944 +
  945 + .row {
  946 + display: flex;
  947 + // margin-bottom: 24rpx;
  948 + line-height: 32rpx;
  949 + padding: 32rpx;
  950 + border-bottom: 1rpx solid #f2f2f2;
  951 +
  952 +
  953 + &.noneStyle {
  954 + border-bottom: 0;
  955 + border-bottom: none;
  956 + }
  957 +
  958 + &.row-spec {
  959 + align-items: center;
  960 + }
  961 + }
  962 +
  963 + .row:last-child {
  964 + margin-bottom: 0;
  965 + }
  966 +
  967 + .label {
  968 + width: 210rpx;
  969 + margin-right: 32rpx;
  970 + color: rgba(0, 0, 0, 0.9);
  971 + font-size: 32rpx;
  972 + line-height: 48rpx;
  973 + }
  974 +
  975 + .value {
  976 + flex: 1;
  977 + color: rgba(0, 0, 0, 0.9);
  978 + font-size: 32rpx;
  979 + white-space: pre-wrap;
  980 + word-break: break-all;
  981 + line-height: 48rpx;
  982 + }
  983 +
  984 + .value-spec {
  985 + height: 48rpx;
  986 + display: flex;
  987 + align-items: center;
  988 + color: #000000;
  989 +
  990 + &_box {
  991 + position: relative;
  992 + width: 60rpx;
  993 + height: 48rpx;
  994 +
  995 + &_1 {
  996 + font-size: 16rpx;
  997 + position: absolute;
  998 + top: -10rpx;
  999 + left: 0;
  1000 + }
  1001 +
  1002 + &_2 {
  1003 + font-size: 16rpx;
  1004 + position: absolute;
  1005 + bottom: -10rpx;
  1006 + left: 0;
  1007 + }
  1008 + }
  1009 +
  1010 + &_val {
  1011 + font-size: 28rpx;
  1012 +
  1013 + &.p12 {
  1014 + padding-right: 12rpx;
  1015 + }
  1016 + }
  1017 + }
  1018 +
  1019 + .view-total {
  1020 + padding-top: 20rpx;
  1021 +
  1022 + .head {
  1023 + font-size: 32rpx;
  1024 + font-weight: 600;
  1025 + line-height: 50rpx;
  1026 + color: rgba(0, 0, 0, 0.9);
  1027 + padding-bottom: 16rpx;
  1028 + margin-bottom: 24rpx;
  1029 + border-bottom: 1px dashed #E7E7E7;
  1030 + }
  1031 +
  1032 + .row {
  1033 + display: flex;
  1034 + margin-bottom: 24rpx;
  1035 + line-height: 32rpx;
  1036 +
  1037 + .label {
  1038 + width: 180rpx;
  1039 + margin-right: 14rpx;
  1040 + color: rgba(0, 0, 0, 0.6);
  1041 + font-size: 28rpx;
  1042 + }
  1043 +
  1044 + .value {
  1045 + flex: 1;
  1046 + color: rgba(0, 0, 0, 0.9);
  1047 + font-size: 28rpx;
  1048 + white-space: pre-wrap;
  1049 + word-break: break-all;
  1050 + }
  1051 + }
  1052 + }
  1053 +}
  1054 +
  1055 +
  1056 +.view-list {
  1057 + padding: 26rpx 32rpx;
  1058 + background: #ffffff;
  1059 +
  1060 + .card {
  1061 + background: #f3f3f3;
  1062 + border-radius: 16rpx;
  1063 + padding: 32rpx 44rpx;
  1064 + margin-bottom: 20rpx;
  1065 +
  1066 + &:last-child {
  1067 + margin-bottom: 0;
  1068 + }
  1069 + }
  1070 +
  1071 + .row {
  1072 + display: flex;
  1073 + margin-bottom: 24rpx;
  1074 + line-height: 32rpx;
  1075 +
  1076 + &.row-spec {
  1077 + height: 60rpx;
  1078 + align-items: center;
  1079 + }
  1080 + }
  1081 +
  1082 + .row:last-child {
  1083 + margin-bottom: 0;
  1084 + }
  1085 +
  1086 + .label {
  1087 + width: 200rpx;
  1088 + margin-right: 14rpx;
  1089 + color: rgba(0, 0, 0, 0.6);
  1090 + font-size: 28rpx;
  1091 + }
  1092 +
  1093 + .value {
  1094 + flex: 1;
  1095 + color: rgba(0, 0, 0, 0.9);
  1096 + font-size: 28rpx;
  1097 + white-space: pre-wrap;
  1098 + word-break: break-all;
  1099 + }
  1100 +
  1101 + .value-spec {
  1102 + height: 60rpx;
  1103 + display: flex;
  1104 + align-items: center;
  1105 + color: #000000;
  1106 +
  1107 + &_box {
  1108 + position: relative;
  1109 + width: 60rpx;
  1110 + height: 60rpx;
  1111 +
  1112 + &_1 {
  1113 + font-size: 16rpx;
  1114 + position: absolute;
  1115 + top: 0;
  1116 + left: 0;
  1117 + }
  1118 +
  1119 + &_2 {
  1120 + font-size: 16rpx;
  1121 + position: absolute;
  1122 + bottom: 0;
  1123 + left: 0;
  1124 + }
  1125 + }
  1126 +
  1127 + &_val {
  1128 + font-size: 28rpx;
  1129 +
  1130 + &.p12 {
  1131 + padding-right: 12rpx;
  1132 + }
  1133 + }
  1134 + }
  1135 +
  1136 + .view-total {
  1137 + padding-top: 20rpx;
  1138 +
  1139 + .head {
  1140 + font-size: 32rpx;
  1141 + font-weight: 600;
  1142 + line-height: 50rpx;
  1143 + color: rgba(0, 0, 0, 0.9);
  1144 + padding-bottom: 16rpx;
  1145 + margin-bottom: 24rpx;
  1146 + border-bottom: 1px dashed #E7E7E7;
  1147 + }
  1148 +
  1149 + .row {
  1150 + display: flex;
  1151 + margin-bottom: 24rpx;
  1152 + line-height: 32rpx;
  1153 +
  1154 + .label {
  1155 + width: 180rpx;
  1156 + margin-right: 14rpx;
  1157 + color: rgba(0, 0, 0, 0.6);
  1158 + font-size: 28rpx;
  1159 + }
  1160 +
  1161 + .value {
  1162 + flex: 1;
  1163 + color: rgba(0, 0, 0, 0.9);
  1164 + font-size: 28rpx;
  1165 + white-space: pre-wrap;
  1166 + word-break: break-all;
  1167 + }
  1168 + }
  1169 + }
  1170 +}
  1171 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row company">{{ form.purchaseOrderName }}</text>
  7 + <view class="row"><text class="label">补货单编号</text><text class="value">{{ form.code }}</text></view>
  8 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName }}</text></view>
  9 + <view class="row"><text class="label">办事处</text><text class="value">{{ form.deptName }}</text></view>
  10 + <view class="row"><text class="label">区域</text><text class="value">{{ form.regionName }}</text></view>
  11 + <view class="row"><text class="label">购货单位</text><text class="value">{{ form.customerName }}</text></view>
  12 + <view class="row"><text class="label">原计划发货日期</text><text class="value">{{ form.originPlanShipDate }}</text></view>
  13 + </view>
  14 +
  15 + <!-- 产品 -->
  16 + <view class="section2">
  17 + <Product mode="view" :list="form.replenishmentOrderLineList"
  18 + :orderDate="form.orderDate"
  19 + :totalQuantity="form.totalQuantity"
  20 + :totalShippedQuantity="form.totalShippedQuantity"
  21 + :totalSupplementaryQuantity="form.totalSupplementaryQuantity"
  22 + />
  23 + </view>
  24 + </view>
  25 + </scroll-view>
  26 + </view>
  27 +</template>
  28 +
  29 +<script>
  30 +import { getDetailApi } from '@/api/replenishment_order.js'
  31 +import Product from './product.vue'
  32 +
  33 +export default {
  34 + name: 'ReplenishmentOrderViewer',
  35 + components: { Product },
  36 + props: { id: { type: [String, Number], default: '' } },
  37 + data() {
  38 + return {
  39 + form: {},
  40 + }
  41 + },
  42 + computed: {
  43 + },
  44 + watch: {
  45 + id: {
  46 + immediate: true,
  47 + handler(val) {
  48 + const v = (val !== undefined && val !== null) ? String(val) : ''
  49 + if (v) this.loadDetail(v)
  50 + }
  51 + }
  52 + },
  53 + onLoad(query) {
  54 + },
  55 + methods: {
  56 + async loadDetail(id) {
  57 + try {
  58 + const res = await getDetailApi(id)
  59 + this.form = res.data || {}
  60 + } catch (e) {
  61 + this.form = {}
  62 + }
  63 + },
  64 + }
  65 +}
  66 +</script>
  67 +
  68 +<style lang="scss" scoped>
  69 +.page {
  70 + display: flex;
  71 + flex-direction: column;
  72 + height: 100vh;
  73 +}
  74 +
  75 +.scroll {
  76 + flex: 1;
  77 + background: #f3f3f3;
  78 +}
  79 +
  80 +.detail-page {
  81 + padding-bottom: 20rpx;
  82 +}
  83 +
  84 +.section {
  85 + padding: 32rpx;
  86 + background: #fff;
  87 + margin-bottom: 20rpx;
  88 + position: relative;
  89 +
  90 +}
  91 +
  92 +.row {
  93 + display: flex;
  94 + margin-bottom: 28rpx;
  95 +
  96 + &:last-child {
  97 + margin-bottom: 0;
  98 + }
  99 +
  100 + &.company {
  101 + font-size: 36rpx;
  102 + font-weight: 600;
  103 + color: rgba(0, 0, 0, 0.9);
  104 + padding-top: 10rpx;
  105 + margin-bottom: 32rpx;
  106 + line-height: 50rpx;
  107 + }
  108 +
  109 + .label {
  110 + width: 240rpx;
  111 + line-height: 32rpx;
  112 + font-size: 28rpx;
  113 + color: rgba(0, 0, 0, 0.6);
  114 + }
  115 +
  116 + .value {
  117 + flex: 1;
  118 + line-height: 32rpx;
  119 + font-size: 28rpx;
  120 + color: rgba(0, 0, 0, 0.9);
  121 + text-align: right;
  122 + word-break: break-all;
  123 + }
  124 +}
  125 +
  126 +.title-header {
  127 + background-color: #fff;
  128 + display: flex;
  129 + align-items: center;
  130 + padding: 32rpx 32rpx 22rpx;
  131 + border-bottom: 1rpx dashed #f0f0f0;
  132 +
  133 + &_icon {
  134 + width: 32rpx;
  135 + height: 28rpx;
  136 + margin-right: 16rpx;
  137 + }
  138 +
  139 + span {
  140 + color: rgba(0, 0, 0, 0.9);
  141 + font-size: 32rpx;
  142 + line-height: 44rpx;
  143 + font-weight: 600;
  144 + }
  145 +}
  146 +</style>
@@ -195,7 +195,7 @@ export default { @@ -195,7 +195,7 @@ export default {
195 items: [{ 195 items: [{
196 text: '产品试样确认单', 196 text: '产品试样确认单',
197 icon: '/static/images/index/invoice.png', 197 icon: '/static/images/index/invoice.png',
198 - link: '/pages/invoice/index', 198 + link: '/pages/confirmation_form/index',
199 name: 'ConfirmationForm' 199 name: 'ConfirmationForm'
200 },{ 200 },{
201 text: '产品试样结果反馈单', 201 text: '产品试样结果反馈单',