Commit 776adc9db4fe0056c2e7e99d5f060d6337e1421d

Authored by gesilong
1 parent e3311a55

commit:计划变更单联调开发

@@ -9,6 +9,48 @@ export function procurementPlanQueryApi(params) { @@ -9,6 +9,48 @@ export function procurementPlanQueryApi(params) {
9 }) 9 })
10 } 10 }
11 11
  12 +export function procurementPlanChangeQueryApi(params) {
  13 + return request({
  14 + url: '/procurement/plan/change/query',
  15 + method: 'get',
  16 + params
  17 + })
  18 +}
  19 +
  20 +export function procurementPlanCanChangeOrReplenishmentApi(params) {
  21 + return request({
  22 + url: '/procurement/plan/queryCanChangeOrReplenishment',
  23 + method: 'get',
  24 + params
  25 + })
  26 +}
  27 +
  28 +export function procurementPlanChangeCreateApi(data) {
  29 + return request({
  30 + url: '/procurement/plan/change/create',
  31 + method: 'post',
  32 + data,
  33 + contentType: ContentTypeEnum.JSON
  34 + })
  35 +}
  36 +
  37 +export function procurementPlanChangeFindByIdApi(params) {
  38 + return request({
  39 + url: '/procurement/plan/change/findById',
  40 + method: 'get',
  41 + params
  42 + })
  43 +}
  44 +
  45 +export function procurementPlanChangeUpdateApi(data) {
  46 + return request({
  47 + url: '/procurement/plan/change/update',
  48 + method: 'post',
  49 + data,
  50 + contentType: ContentTypeEnum.JSON
  51 + })
  52 +}
  53 +
12 export function procurementPlanSaveApi(data) { 54 export function procurementPlanSaveApi(data) {
13 return request({ 55 return request({
14 url: '/procurement/plan', 56 url: '/procurement/plan',
@@ -74,3 +116,19 @@ export function procurementPlanStopApi(id) { @@ -74,3 +116,19 @@ export function procurementPlanStopApi(id) {
74 params: { id: id } 116 params: { id: id }
75 }) 117 })
76 } 118 }
  119 +
  120 +export function procurementPlanChangeSubmitApi(id) {
  121 + return request({
  122 + url: '/procurement/plan/change/submit',
  123 + method: 'post',
  124 + params: { id: id }
  125 + })
  126 +}
  127 +
  128 +export function procurementPlanChangeCancelApi(id) {
  129 + return request({
  130 + url: '/procurement/plan/change/cancel',
  131 + method: 'post',
  132 + params: { id: id }
  133 + })
  134 +}
@@ -452,22 +452,46 @@ @@ -452,22 +452,46 @@
452 "navigationBarTextStyle": "black" 452 "navigationBarTextStyle": "black"
453 } 453 }
454 }, 454 },
455 - // {  
456 - // "path": "pages/schedule-plan/index",  
457 - // "style": {  
458 - // "navigationBarTitleText": "计划变更单",  
459 - // "navigationBarBackgroundColor": "#ffffff",  
460 - // "navigationBarTextStyle": "black"  
461 - // }  
462 - // },  
463 - // {  
464 - // "path": "pages/supple-plan/index",  
465 - // "style": {  
466 - // "navigationBarTitleText": "计划补录单",  
467 - // "navigationBarBackgroundColor": "#ffffff",  
468 - // "navigationBarTextStyle": "black"  
469 - // }  
470 - // }, 455 + {
  456 + "path": "pages/schedule-plan/index",
  457 + "style": {
  458 + "navigationBarTitleText": "计划变更单",
  459 + "navigationBarBackgroundColor": "#ffffff",
  460 + "navigationBarTextStyle": "black"
  461 + }
  462 + },
  463 + {
  464 + "path": "pages/schedule-plan/add",
  465 + "style": {
  466 + "navigationBarTitleText": "新增计划变更单",
  467 + "navigationBarBackgroundColor": "#ffffff",
  468 + "navigationBarTextStyle": "black"
  469 + }
  470 + },
  471 + {
  472 + "path": "pages/schedule-plan/detail",
  473 + "style": {
  474 + "navigationBarTitleText": "计划变更单详情",
  475 + "navigationBarBackgroundColor": "#ffffff",
  476 + "navigationBarTextStyle": "black"
  477 + }
  478 + },
  479 + {
  480 + "path": "pages/schedule-plan/modify",
  481 + "style": {
  482 + "navigationBarTitleText": "编辑计划变更单",
  483 + "navigationBarBackgroundColor": "#ffffff",
  484 + "navigationBarTextStyle": "black"
  485 + }
  486 + },
  487 + {
  488 + "path": "pages/supple-plan/index",
  489 + "style": {
  490 + "navigationBarTitleText": "计划补录单",
  491 + "navigationBarBackgroundColor": "#ffffff",
  492 + "navigationBarTextStyle": "black"
  493 + }
  494 + },
471 { 495 {
472 "path": "pages/order_list/index", 496 "path": "pages/order_list/index",
473 "style": { 497 "style": {
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view scroll-y class="scroll">
  4 + <uni-list>
  5 + <uni-list-item class="select-item" :class="form.code ? 'is-filled' : 'is-empty'" clickable
  6 + @click="openPlanSheet" :rightText="form.code || '请选择计划编号'" showArrow>
  7 + <template v-slot:body>
  8 + <view class="item-title"><text class="required">*</text><text>计划数据</text></view>
  9 + </template>
  10 + </uni-list-item>
  11 +
  12 + <uni-list-item title="公司名称">
  13 + <template v-slot:footer>
  14 + <uni-easyinput v-model="companyNameDisplay" :inputBorder="false" disabled placeholder="-" />
  15 + </template>
  16 + </uni-list-item>
  17 +
  18 + <uni-list-item>
  19 + <template v-slot:body>
  20 + <view class="item-title"><text class="required">*</text><text>供应商名称</text></view>
  21 + </template>
  22 + <template v-slot:footer>
  23 + <uni-easyinput v-model="detail.supplierName" :inputBorder="false" placeholder="请输入供应商名称" />
  24 + </template>
  25 + </uni-list-item>
  26 +
  27 + <uni-list-item title="采购处">
  28 + <template v-slot:footer>
  29 + <uni-easyinput v-model="purchaseDepartmentDisplay" :inputBorder="false" disabled placeholder="-" />
  30 + </template>
  31 + </uni-list-item>
  32 +
  33 + <uni-list-item title="区域">
  34 + <template v-slot:footer>
  35 + <uni-easyinput v-model="regionDisplay" :inputBorder="false" disabled placeholder="-" />
  36 + </template>
  37 + </uni-list-item>
  38 +
  39 + <uni-list-item title="日期">
  40 + <template v-slot:footer>
  41 + <uni-easyinput v-model="dateDisplay" :inputBorder="false" disabled placeholder="-" />
  42 + </template>
  43 + </uni-list-item>
  44 +
  45 + <uni-list-item>
  46 + <template v-slot:body>
  47 + <view class="item-title"><text class="required">*</text><text>报价时效</text></view>
  48 + </template>
  49 + <template v-slot:footer>
  50 + <uni-easyinput v-model="detail.validityPeriod" :inputBorder="false" placeholder="请输入报价时效" />
  51 + </template>
  52 + </uni-list-item>
  53 +
  54 + <uni-list-item>
  55 + <template v-slot:body>
  56 + <view class="item-title"><text class="required">*</text><text>报价人</text></view>
  57 + </template>
  58 + <template v-slot:footer>
  59 + <uni-easyinput v-model="detail.quoterName" :inputBorder="false" placeholder="请输入报价人" />
  60 + </template>
  61 + </uni-list-item>
  62 +
  63 + <uni-list-item class="select-item" :class="typeof detail.longTermOperation === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  64 + @click="toggleBool('longTermOperation')" :rightText="boolText(detail.longTermOperation) || '请选择'" showArrow>
  65 + <template v-slot:body>
  66 + <view class="item-title"><text class="required">*</text><text>是否长单操作</text></view>
  67 + </template>
  68 + </uni-list-item>
  69 +
  70 + <uni-list-item class="select-item" :class="typeof detail.terminalCustomer === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  71 + @click="toggleBool('terminalCustomer')" :rightText="boolText(detail.terminalCustomer) || '请选择'" showArrow>
  72 + <template v-slot:body>
  73 + <view class="item-title"><text class="required">*</text><text>是否终端客户</text></view>
  74 + </template>
  75 + </uni-list-item>
  76 +
  77 + <uni-list-item class="select-item" :class="typeof detail.priced === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  78 + @click="toggleBool('priced')" :rightText="boolText(detail.priced) || '请选择'" showArrow>
  79 + <template v-slot:body>
  80 + <view class="item-title"><text class="required">*</text><text>是否未点价</text></view>
  81 + </template>
  82 + </uni-list-item>
  83 +
  84 + <uni-list-item v-if="detail.priced === true">
  85 + <template v-slot:body>
  86 + <view class="item-title"><text class="required">*</text><text>点价截止日期</text></view>
  87 + </template>
  88 + <template v-slot:footer>
  89 + <uni-datetime-picker type="date" v-model="detail.pricingDeadline" return-type="string" />
  90 + </template>
  91 + </uni-list-item>
  92 +
  93 + <uni-list-item v-if="detail.priced === true" class="select-item" :class="typeof detail.temporaryPrice === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  94 + @click="toggleBool('temporaryPrice')" :rightText="boolText(detail.temporaryPrice) || '请选择'" showArrow>
  95 + <template v-slot:body>
  96 + <view class="item-title"><text class="required">*</text><text>是否暂定价</text></view>
  97 + </template>
  98 + </uni-list-item>
  99 +
  100 + <uni-list-item class="select-item" :class="typeof detail.otherExpenseSettlement === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  101 + @click="toggleBool('otherExpenseSettlement')" :rightText="boolText(detail.otherExpenseSettlement) || '请选择'" showArrow>
  102 + <template v-slot:body>
  103 + <view class="item-title"><text class="required">*</text><text>其他费用凭票到厂另行结算</text></view>
  104 + </template>
  105 + </uni-list-item>
  106 +
  107 + <uni-list-item class="select-item" :class="detail.deliveryMode ? 'is-filled' : 'is-empty'" clickable
  108 + @click="openDeliverySheet" :rightText="getDeliveryMethodLabel()" showArrow>
  109 + <template v-slot:body>
  110 + <view class="item-title"><text class="required">*</text><text>交提货方式</text></view>
  111 + </template>
  112 + </uni-list-item>
  113 +
  114 + <uni-list-item class="select-item" :class="typeof detail.commodityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  115 + @click="toggleBool('commodityBuyout')" :rightText="boolText(detail.commodityBuyout) || '请选择'" showArrow>
  116 + <template v-slot:body>
  117 + <view class="item-title"><text>是否通货买断</text></view>
  118 + </template>
  119 + </uni-list-item>
  120 +
  121 + <uni-list-item class="select-item" :class="typeof detail.qualityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  122 + @click="toggleBool('qualityBuyout')" :rightText="boolText(detail.qualityBuyout) || '请选择'" showArrow>
  123 + <template v-slot:body>
  124 + <view class="item-title"><text class="required">*</text><text>是否质量买断</text></view>
  125 + </template>
  126 + </uni-list-item>
  127 +
  128 + <uni-list-item class="select-item" :class="typeof detail.quantityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  129 + @click="toggleBool('quantityBuyout')" :rightText="boolText(detail.quantityBuyout) || '请选择'" showArrow>
  130 + <template v-slot:body>
  131 + <view class="item-title"><text>是否数量买断</text></view>
  132 + </template>
  133 + </uni-list-item>
  134 +
  135 + <uni-list-item class="select-item" :class="typeof detail.doorDelivery === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  136 + @click="toggleBool('doorDelivery')" :rightText="boolText(detail.doorDelivery) || '请选择'" showArrow>
  137 + <template v-slot:body>
  138 + <view class="item-title"><text class="required">*</text><text>是否送货上门</text></view>
  139 + </template>
  140 + </uni-list-item>
  141 +
  142 + <uni-list-item title="另付运费">
  143 + <template v-slot:footer>
  144 + <uni-easyinput v-model="shippingCostDisplay" :inputBorder="false" disabled placeholder="-" />
  145 + </template>
  146 + </uni-list-item>
  147 +
  148 + <uni-list-item>
  149 + <template v-slot:body>
  150 + <view class="item-title"><text class="required">*</text><text>装车费</text></view>
  151 + </template>
  152 + <template v-slot:footer>
  153 + <uni-easyinput
  154 + v-model="detail.loadingFee"
  155 + type="digit"
  156 + :inputBorder="false"
  157 + placeholder="请输入装车费"
  158 + @input="onNonNegativeInput('loadingFee')"
  159 + @blur="onNonNegativeBlur('loadingFee', 2)"
  160 + />
  161 + </template>
  162 + </uni-list-item>
  163 +
  164 + <uni-list-item title="开票品名">
  165 + <template v-slot:footer>
  166 + <uni-easyinput v-model="detail.invoiceProductName" :inputBorder="false" placeholder="请输入开票品名" />
  167 + </template>
  168 + </uni-list-item>
  169 +
  170 + <uni-list-item>
  171 + <template v-slot:body>
  172 + <view class="item-title"><text class="required">*</text><text>交货日期</text></view>
  173 + </template>
  174 + <template v-slot:footer>
  175 + <uni-datetime-picker type="date" v-model="detail.deliveryDate" return-type="string" />
  176 + </template>
  177 + </uni-list-item>
  178 + <uni-list-item>
  179 + <template v-slot:body>
  180 + <view class="item-title"><text class="required">*</text><text>结算方式</text></view>
  181 + </template>
  182 + <template v-slot:footer>
  183 + <uni-easyinput v-model="detail.settlementMethod" :inputBorder="false" placeholder="请输入结算方式" />
  184 + </template>
  185 + </uni-list-item>
  186 +
  187 + <uni-list-item>
  188 + <template v-slot:body>
  189 + <view class="item-title"><text class="required">*</text><text>结算比例(%)</text></view>
  190 + </template>
  191 + <template v-slot:footer>
  192 + <uni-easyinput
  193 + v-model="detail.settlementRatio"
  194 + type="digit"
  195 + :inputBorder="false"
  196 + placeholder="请输入结算比例"
  197 + @input="onNonNegativeInput('settlementRatio')"
  198 + @blur="onNonNegativeBlur('settlementRatio', 4)"
  199 + />
  200 + </template>
  201 + </uni-list-item>
  202 +
  203 + <uni-list-item title="价格表编号">
  204 + <template v-slot:footer>
  205 + <uni-easyinput v-model="priceTableDisplay" :inputBorder="false" disabled placeholder="-" />
  206 + </template>
  207 + </uni-list-item>
  208 +
  209 + <uni-list-item>
  210 + <template v-slot:body>
  211 + <view class="item-title"><text class="required">*</text><text>协调事由</text></view>
  212 + </template>
  213 + <template v-slot:footer>
  214 + <uni-easyinput v-model="detail.coordinationReason" :inputBorder="false" placeholder="请输入协调事由" />
  215 + </template>
  216 + </uni-list-item>
  217 +
  218 + <uni-list-item>
  219 + <template v-slot:body>
  220 + <view class="item-title"><text class="required">*</text><text>票类状况</text></view>
  221 + </template>
  222 + <template v-slot:footer>
  223 + <uni-easyinput v-model="detail.invoiceTypeStatus" :inputBorder="false" placeholder="请输入票类状况" />
  224 + </template>
  225 + </uni-list-item>
  226 +
  227 + <uni-list-item v-if="attachmentList.length" title="附件">
  228 + <template v-slot:footer>
  229 + <view class="attach-list">
  230 + <view v-for="(at, idx) in attachmentList" :key="idx" class="attach-item">
  231 + <text class="attach-name">{{ at.name }}</text>
  232 + <text class="attach-view" @click.stop="onPreviewAttachment(at)">查看</text>
  233 + </view>
  234 + </view>
  235 + </template>
  236 + </uni-list-item>
  237 +
  238 + <ProductRel
  239 + ref="productRel"
  240 + mode="edit"
  241 + :list="lineList"
  242 + :product-options="productOptions"
  243 + :workshop-options="workshopOptions"
  244 + @change="onLineChange"
  245 + />
  246 + </uni-list>
  247 +
  248 + <view class="material-condition-section">
  249 + <view class="section-header">
  250 + <image class="opCollapse" src="/static/images/title.png" />
  251 + <text class="section-title">料质状况</text>
  252 + </view>
  253 + <view class="section-content">
  254 + <view class="form-row">
  255 + <text class="form-label">是否启用料质状况</text>
  256 + <text class="form-value toggle-link" @click="toggleBool('materialConditionEnabled')">{{ boolText(detail.materialConditionEnabled) || '请选择' }}</text>
  257 + </view>
  258 + <template v-if="detail.materialConditionEnabled">
  259 + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group">
  260 + <text class="material-label">{{ mc.label }}</text>
  261 + <view class="tag-list">
  262 + <view
  263 + v-for="opt in getDictOptions(mc.dictCode)"
  264 + :key="opt.code"
  265 + class="tag-btn"
  266 + :class="{ active: isTagSelected(mc.field, opt.code) }"
  267 + @click="toggleTag(mc.field, opt.code)"
  268 + >
  269 + {{ opt.name }}
  270 + </view>
  271 + </view>
  272 + </view>
  273 + </template>
  274 + </view>
  275 + </view>
  276 +
  277 + <view class="remark-section">
  278 + <view class="section-header">
  279 + <image class="opCollapse" src="/static/images/title.png" />
  280 + <text class="section-title">备注</text>
  281 + </view>
  282 + <view class="section-content">
  283 + <uni-easyinput type="textarea" v-model="detail.remark" :inputBorder="false" placeholder="请输入备注" />
  284 + </view>
  285 + </view>
  286 + <view class="remark-section">
  287 + <view class="section-header">
  288 + <image class="opCollapse" src="/static/images/title.png" />
  289 + <text class="section-title"><text class="required">*</text>变更事由</text>
  290 + </view>
  291 + <view class="section-content">
  292 + <uni-easyinput type="textarea" v-model="detail.changeReason" :inputBorder="false" placeholder="请输入变更事由" />
  293 + </view>
  294 + </view>
  295 + </scroll-view>
  296 +
  297 + <view class="bottom-bar">
  298 + <button class="btn btn-outline" @click="onBack">取消</button>
  299 + <button class="btn btn-primary" @click="onSubmit">保存</button>
  300 + </view>
  301 +
  302 + <RelateSelectSheet
  303 + v-if="planSheetVisible"
  304 + :visible.sync="planSheetVisible"
  305 + title="选择计划"
  306 + :fetch-fn="fetchChangePlanList"
  307 + :display-fields="planDisplayFields"
  308 + :page-size="20"
  309 + :multiple="false"
  310 + row-key="id"
  311 + @confirm="onPlanConfirm"
  312 + />
  313 + <SingleSelectSheet
  314 + :visible.sync="deliverySheetVisible"
  315 + title="交提货方式"
  316 + :options="deliveryOptions"
  317 + v-model="deliverySheetValue"
  318 + @confirm="onDeliveryConfirm"
  319 + />
  320 + </view>
  321 +</template>
  322 +
  323 +<script>
  324 +import ProductRel from './productRel.vue'
  325 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  326 +import SingleSelectSheet from '@/components/single-select/index.vue'
  327 +import { procurementPlanDetailApi, procurementPlanCanChangeOrReplenishmentApi, procurementPlanChangeCreateApi } from '@/api/procure-manage/procurementPlan.js'
  328 +import {
  329 + breedRelationshipQueryApi,
  330 + workshopQueryApi
  331 +} from '@/api/devManage.js'
  332 +import { getDicByCodes } from '@/utils/dic.js'
  333 +
  334 +export default {
  335 + name: 'SchedulePlanAdd',
  336 + components: { ProductRel, RelateSelectSheet, SingleSelectSheet },
  337 + data() {
  338 + return {
  339 + form: {
  340 + planId: '',
  341 + code: ''
  342 + },
  343 + detail: {},
  344 + attachmentList: [],
  345 + materialConditionConfigs: [
  346 + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' },
  347 + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' },
  348 + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' },
  349 + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' },
  350 + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' },
  351 + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' },
  352 + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' },
  353 + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' }
  354 + ],
  355 + dicOptions: {
  356 + DELIVERY_METHOD: []
  357 + },
  358 + dictData: {},
  359 + lineList: [],
  360 + productOptions: [],
  361 + workshopOptions: [],
  362 + planSheetVisible: false,
  363 + planDisplayFields: [
  364 + { label: '计划编号', field: 'code' },
  365 + { label: '公司简称', field: 'companyShortName' },
  366 + { label: '供应商名称', field: 'supplierName' },
  367 + { label: '数量', field: 'totalQuantity' },
  368 + { label: '采购处', field: 'purchaseDepartmentName' }
  369 + ],
  370 + deliverySheetVisible: false,
  371 + deliverySheetValue: '',
  372 + deliveryOptions: []
  373 + }
  374 + },
  375 + computed: {
  376 + companyNameDisplay() {
  377 + var d = this.detail || {}
  378 + return d.companyShortName != null ? d.companyShortName : (d.companyName != null ? d.companyName : '')
  379 + },
  380 + purchaseDepartmentDisplay() {
  381 + var d = this.detail || {}
  382 + return d.purchaseDepartmentName != null ? d.purchaseDepartmentName : (d.purchaseDepartment != null ? d.purchaseDepartment : '')
  383 + },
  384 + regionDisplay() {
  385 + var d = this.detail || {}
  386 + return d.regionName != null ? d.regionName : (d.region != null ? d.region : '')
  387 + },
  388 + dateDisplay() {
  389 + var d = this.detail || {}
  390 + var s = d.date == null ? '' : String(d.date).trim()
  391 + return s.length >= 10 ? s.slice(0, 10) : s
  392 + },
  393 + shippingCostDisplay() {
  394 + var v = (this.detail || {}).shippingCost
  395 + if (v == null || v === '') return ''
  396 + var n = Number(v)
  397 + if (isNaN(n)) return String(v)
  398 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  399 + },
  400 + priceTableDisplay() {
  401 + var d = this.detail || {}
  402 + var code = d.priceTableCode != null ? d.priceTableCode : (d.priceId != null ? d.priceId : '')
  403 + var name = d.priceTableName || ''
  404 + return name ? (name + (code ? ' (' + code + ')' : '')) : code
  405 + }
  406 + },
  407 + onLoad() {
  408 + this.loadDictData()
  409 + this.loadRefData()
  410 + },
  411 + methods: {
  412 + safeText(v) {
  413 + var s = v == null ? '' : String(v)
  414 + return s.trim() ? s : '-'
  415 + },
  416 + boolText(v) {
  417 + if (typeof v !== 'boolean') return ''
  418 + return v ? '是' : '否'
  419 + },
  420 + toNumber(v) {
  421 + if (v == null || v === '') return 0
  422 + var n = Number(v)
  423 + return isNaN(n) ? 0 : n
  424 + },
  425 + toFixed(v, n) {
  426 + var num = Number(v)
  427 + if (isNaN(num)) return ''
  428 + return num.toFixed(n)
  429 + },
  430 + async loadDictData() {
  431 + var codes = [].concat(
  432 + this.materialConditionConfigs.map(function (mc) { return mc.dictCode }),
  433 + ['DELIVERY_METHOD']
  434 + )
  435 + try {
  436 + var results = await getDicByCodes(codes)
  437 + var d = results || {}
  438 + this.dictData = d
  439 + this.dicOptions.DELIVERY_METHOD = (d.DELIVERY_METHOD && d.DELIVERY_METHOD.data) || []
  440 + this.deliveryOptions = this.dicOptions.DELIVERY_METHOD.map(function (o) {
  441 + return { label: o.name || o.label || '', value: o.code != null ? o.code : o.value }
  442 + })
  443 + } catch (e) {
  444 + this.dictData = {}
  445 + }
  446 + },
  447 + async loadRefData() {
  448 + try {
  449 + var results = await Promise.all([
  450 + this.$pCall(breedRelationshipQueryApi, { pageIndex: 1, pageSize: 1000 }),
  451 + this.$pCall(workshopQueryApi, { pageIndex: 1, pageSize: 1000 })
  452 + ])
  453 + var r1 = (results[0] && results[0].data) ? results[0].data : {}
  454 + var r2 = (results[1] && results[1].data) ? results[1].data : {}
  455 + this.productOptions = (r1.datas && Array.isArray(r1.datas)) ? r1.datas : ((r1.list && Array.isArray(r1.list)) ? r1.list : ((r1.records && Array.isArray(r1.records)) ? r1.records : []))
  456 + this.workshopOptions = (r2.datas && Array.isArray(r2.datas)) ? r2.datas : ((r2.list && Array.isArray(r2.list)) ? r2.list : ((r2.records && Array.isArray(r2.records)) ? r2.records : []))
  457 + this.fillLineNames()
  458 + } catch (e) {
  459 + this.productOptions = []
  460 + this.workshopOptions = []
  461 + }
  462 + },
  463 + $pCall(fn, params) {
  464 + try { return Promise.resolve(fn(params)) } catch (e) { return Promise.reject(e) }
  465 + },
  466 + getDictOptions(dictCode) {
  467 + var data = this.dictData[dictCode]
  468 + return (data && data.data) ? data.data : []
  469 + },
  470 + getDeliveryMethodLabel() {
  471 + var opts = this.dicOptions.DELIVERY_METHOD || []
  472 + var v = String(this.detail.deliveryMode != null ? this.detail.deliveryMode : '')
  473 + if (!v) return '请选择'
  474 + for (var i = 0; i < opts.length; i++) {
  475 + var o = opts[i]
  476 + var code = o.code != null ? String(o.code) : (o.value != null ? String(o.value) : '')
  477 + if (code === v) return o.name || o.label || v
  478 + }
  479 + return v
  480 + },
  481 + openPlanSheet() {
  482 + this.planSheetVisible = true
  483 + },
  484 + fetchChangePlanList(args) {
  485 + var extra = (args && args.extra) ? args.extra : {}
  486 + var keyword = extra.keyword || extra.name || ''
  487 + var params = { pageIndex: args.pageIndex || 1, pageSize: args.pageSize || 20, queryType: 'CHANGE' }
  488 + if (keyword) params.keyword = keyword
  489 + return procurementPlanCanChangeOrReplenishmentApi(params).then(function (res) {
  490 + var d = (res && res.data) ? res.data : {}
  491 + var records = (d.datas && Array.isArray(d.datas)) ? d.datas : ((d.records && Array.isArray(d.records)) ? d.records : ((d.list && Array.isArray(d.list)) ? d.list : []))
  492 + var total = d.totalCount || d.count || (records ? records.length : 0)
  493 + var hasNext = !!d.hasNext
  494 + return { records: records, totalCount: total, hasNext: hasNext }
  495 + }).catch(function () {
  496 + return { records: [], totalCount: 0, hasNext: false }
  497 + })
  498 + },
  499 + onPlanConfirm(e) {
  500 + var items = (e && e.items) ? e.items : []
  501 + if (!items.length) return
  502 + var row = items[0] || {}
  503 + var id = row.id != null ? row.id : (row.planId != null ? row.planId : (row.procurementPlanId != null ? row.procurementPlanId : ''))
  504 + this.form.planId = id
  505 + this.form.code = row.code || ''
  506 + if (id) {
  507 + this.loadSourceDetail(id)
  508 + }
  509 + },
  510 + async loadSourceDetail(id) {
  511 + if (!id) return
  512 + try {
  513 + var res = await procurementPlanDetailApi({ id: id })
  514 + var d = (res && res.data) ? res.data : {}
  515 + var boolFields = ['longTermOperation', 'terminalCustomer', 'priced', 'temporaryPrice', 'otherExpenseSettlement', 'commodityBuyout', 'qualityBuyout', 'quantityBuyout', 'doorDelivery', 'materialConditionEnabled']
  516 + boolFields.forEach(function (f) {
  517 + var v = d[f]
  518 + if (typeof v === 'string') {
  519 + d[f] = v === 'true' || v === '1'
  520 + } else if (typeof v === 'number') {
  521 + d[f] = v === 1
  522 + } else {
  523 + d[f] = !!v
  524 + }
  525 + })
  526 + this.materialConditionConfigs.forEach(function (mc) {
  527 + var v = d[mc.field]
  528 + if (typeof v === 'string' && v) {
  529 + d[mc.field] = v.split(',').filter(Boolean)
  530 + } else if (!Array.isArray(v)) {
  531 + d[mc.field] = []
  532 + }
  533 + })
  534 + d.pricingDeadline = this.trimDate(d.pricingDeadline)
  535 + d.deliveryDate = this.trimDate(d.deliveryDate)
  536 + this.detail = d
  537 + if (!this.form.code) {
  538 + this.form.code = d.code || ''
  539 + }
  540 + this.lineList = Array.isArray(d.procurementPlanLineList) ? d.procurementPlanLineList.slice() : []
  541 + this.buildAttachmentList()
  542 + this.fillLineNames()
  543 + } catch (e) {
  544 + uni.showToast({ title: '加载计划失败', icon: 'none' })
  545 + }
  546 + },
  547 + trimDate(v) {
  548 + if (v == null || v === '') return ''
  549 + var s = String(v).trim()
  550 + return s.length >= 10 ? s.slice(0, 10) : s
  551 + },
  552 + buildAttachmentList() {
  553 + var ids = this.detail.attachmentFileIds
  554 + var names = this.detail.attachmentFileNames
  555 + var idArr = (typeof ids === 'string' && ids) ? ids.split(',').filter(Boolean) : (Array.isArray(ids) ? ids : [])
  556 + var nameArr = (typeof names === 'string' && names) ? names.split(',').filter(Boolean) : (Array.isArray(names) ? names : [])
  557 + var len = Math.max(idArr.length, nameArr.length)
  558 + var list = []
  559 + for (var i = 0; i < len; i++) {
  560 + list.push({ id: idArr[i] || '', name: nameArr[i] || ('附件' + (i + 1)) })
  561 + }
  562 + this.attachmentList = list
  563 + },
  564 + onPreviewAttachment(at) {
  565 + if (!at || !at.id) {
  566 + uni.showToast({ title: '无法查看', icon: 'none' })
  567 + return
  568 + }
  569 + uni.showToast({ title: '查看附件 ' + at.name, icon: 'none' })
  570 + },
  571 + fillLineNames() {
  572 + var list = Array.isArray(this.lineList) ? this.lineList : []
  573 + if (!list.length) return
  574 + var pOpts = Array.isArray(this.productOptions) ? this.productOptions : []
  575 + var wOpts = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  576 + this.lineList = list.map(function (row) {
  577 + var r = Object.assign({}, row)
  578 + var pid = r.productId != null ? String(r.productId) : ''
  579 + var wid = r.workshopId != null ? String(r.workshopId) : ''
  580 + if ((r.productName == null || String(r.productName).trim() === '') && pid) {
  581 + var p = pOpts.find(function (o) {
  582 + var id = o.id != null ? String(o.id) : (o.productId != null ? String(o.productId) : (o.value != null ? String(o.value) : ''))
  583 + return id === pid
  584 + })
  585 + if (p) {
  586 + r.productName = p.name != null ? p.name : (p.productName != null ? p.productName : (p.label != null ? p.label : ''))
  587 + }
  588 + }
  589 + if ((r.workshopName == null || String(r.workshopName).trim() === '') && wid) {
  590 + var w = wOpts.find(function (o) {
  591 + var id = o.id != null ? String(o.id) : (o.workshopId != null ? String(o.workshopId) : (o.value != null ? String(o.value) : ''))
  592 + return id === wid
  593 + })
  594 + if (w) {
  595 + r.workshopName = w.name != null ? w.name : (w.workshopName != null ? w.workshopName : (w.label != null ? w.label : ''))
  596 + }
  597 + }
  598 + return r
  599 + })
  600 + },
  601 + onLineChange(list) {
  602 + this.lineList = Array.isArray(list) ? list.slice() : []
  603 + },
  604 + toggleBool(field) {
  605 + var cur = this.detail[field]
  606 + this.$set(this.detail, field, cur !== true)
  607 + },
  608 + onNonNegativeInput(field) {
  609 + var v = String(this.detail[field] != null ? this.detail[field] : '')
  610 + v = v.replace(/[^0-9.]/g, '')
  611 + v = v.replace(/(\..*)\./g, '$1')
  612 + this.$set(this.detail, field, v)
  613 + },
  614 + onNonNegativeBlur(field, digits) {
  615 + var raw = this.detail[field]
  616 + if (raw === '' || raw === null || raw === undefined) {
  617 + this.$set(this.detail, field, '')
  618 + return
  619 + }
  620 + var val = parseFloat(raw)
  621 + if (isNaN(val)) val = 0
  622 + var m = Math.pow(10, digits)
  623 + var rounded = Math.round(val * m) / m
  624 + this.$set(this.detail, field, rounded)
  625 + },
  626 + openDeliverySheet() {
  627 + var cur = this.detail.deliveryMode != null ? this.detail.deliveryMode : ''
  628 + var match = this.deliveryOptions.find(function (o) { return String(o.value) === String(cur) })
  629 + this.deliverySheetValue = match ? match.value : ''
  630 + this.deliverySheetVisible = true
  631 + },
  632 + onDeliveryConfirm(e) {
  633 + this.$set(this.detail, 'deliveryMode', e.value != null ? e.value : '')
  634 + },
  635 + isTagSelected(field, code) {
  636 + var selected = this.detail ? this.detail[field] : undefined
  637 + if (Array.isArray(selected)) return selected.indexOf(code) >= 0
  638 + if (typeof selected === 'string' && selected) {
  639 + return selected.split(',').indexOf(code) >= 0
  640 + }
  641 + return false
  642 + },
  643 + toggleTag(field, code) {
  644 + var selected = this.detail[field]
  645 + var arr = Array.isArray(selected) ? selected.slice() : []
  646 + var idx = arr.indexOf(code)
  647 + if (idx >= 0) {
  648 + arr.splice(idx, 1)
  649 + } else {
  650 + arr.push(code)
  651 + }
  652 + this.$set(this.detail, field, arr)
  653 + },
  654 + validateForm() {
  655 + if (!this.form.planId) {
  656 + uni.showToast({ title: '请选择计划', icon: 'none' })
  657 + return false
  658 + }
  659 + var d = this.detail || {}
  660 + var textChecks = [
  661 + { key: 'supplierName', title: '供应商名称' },
  662 + { key: 'validityPeriod', title: '报价时效' },
  663 + { key: 'quoterName', title: '报价人' },
  664 + { key: 'deliveryMode', title: '交提货方式' },
  665 + { key: 'loadingFee', title: '装车费' },
  666 + { key: 'invoiceProductName', title: '开票品名' },
  667 + { key: 'deliveryDate', title: '交货日期' },
  668 + { key: 'settlementMethod', title: '结算方式' },
  669 + { key: 'settlementRatio', title: '结算比例' },
  670 + { key: 'coordinationReason', title: '协调事由' },
  671 + { key: 'invoiceTypeStatus', title: '票类状况' },
  672 + { key: 'changeReason', title: '变更事由' }
  673 + ]
  674 + for (var i = 0; i < textChecks.length; i++) {
  675 + var tc = textChecks[i]
  676 + var tv = d[tc.key]
  677 + if (tv === '' || tv === null || tv === undefined) {
  678 + uni.showToast({ title: '请填写' + tc.title, icon: 'none' })
  679 + return false
  680 + }
  681 + }
  682 + var boolChecks = [
  683 + { key: 'longTermOperation', title: '是否长单操作' },
  684 + { key: 'terminalCustomer', title: '是否终端客户' },
  685 + { key: 'priced', title: '是否未点价' },
  686 + { key: 'otherExpenseSettlement', title: '其他费用凭票到厂另行结算' },
  687 + { key: 'commodityBuyout', title: '是否通货买断' },
  688 + { key: 'qualityBuyout', title: '是否质量买断' },
  689 + { key: 'quantityBuyout', title: '是否数量买断' },
  690 + { key: 'doorDelivery', title: '是否送货上门' }
  691 + ]
  692 + if (d.priced === true) {
  693 + boolChecks.push({ key: 'temporaryPrice', title: '是否暂定价' })
  694 + var pd = d.pricingDeadline
  695 + if (pd === '' || pd === null || pd === undefined) {
  696 + uni.showToast({ title: '请选择点价截止日期', icon: 'none' })
  697 + return false
  698 + }
  699 + }
  700 + for (var j = 0; j < boolChecks.length; j++) {
  701 + var bc = boolChecks[j]
  702 + if (typeof d[bc.key] !== 'boolean') {
  703 + uni.showToast({ title: '请选择' + bc.title, icon: 'none' })
  704 + return false
  705 + }
  706 + }
  707 + var pr = this.$refs.productRel
  708 + if (pr && typeof pr.validate === 'function') {
  709 + if (!pr.validate()) return false
  710 + }
  711 + return true
  712 + },
  713 + onBack() {
  714 + uni.navigateBack({ delta: 1 })
  715 + },
  716 + joinStr(arr) {
  717 + if (Array.isArray(arr)) return arr.filter(Boolean).join(',')
  718 + if (typeof arr === 'string') return arr
  719 + return ''
  720 + },
  721 + pickId(v) {
  722 + if (v == null || v === '') return ''
  723 + if (typeof v === 'string' || typeof v === 'number') return String(v)
  724 + if (typeof v === 'object') {
  725 + return String(v.id != null ? v.id : (v.value != null ? v.value : (v.code != null ? v.code : '')))
  726 + }
  727 + return String(v)
  728 + },
  729 + boolToStr(v) {
  730 + return v === true ? 'true' : 'false'
  731 + },
  732 + buildPayload() {
  733 + var self = this
  734 + var d = this.detail || {}
  735 + var lines = Array.isArray(this.lineList) ? this.lineList.slice() : []
  736 + var workshopId = d.workshopId != null ? d.workshopId : ''
  737 + var lineList = lines.map(function (row, i) {
  738 + var r = row || {}
  739 + var itemId = r.itemId != null ? r.itemId : (r.id != null ? String(r.id) : (r._key != null ? r._key : ('line-' + Date.now() + '-' + i)))
  740 + if (!workshopId && r.workshopId != null) workshopId = r.workshopId
  741 + return {
  742 + productId: self.pickId(r.productId),
  743 + materialType: r.materialType != null ? String(r.materialType) : '',
  744 + quantity: r.quantity != null ? String(r.quantity) : '',
  745 + actualPurchasePrice: r.actualPurchasePrice != null ? String(r.actualPurchasePrice) : '',
  746 + includingShippingPrice: r.includingShippingPrice != null ? String(r.includingShippingPrice) : '',
  747 + priceListPrices: r.priceListPrices != null ? String(r.priceListPrices) : '',
  748 + coordinationRange: r.coordinationRange != null ? String(r.coordinationRange) : '',
  749 + estimatedLoss: r.estimatedLoss != null ? String(r.estimatedLoss) : '',
  750 + applicationAttrition: r.applicationAttrition != null ? String(r.applicationAttrition) : '',
  751 + paintDamageRatio: r.paintDamageRatio != null ? String(r.paintDamageRatio) : '',
  752 + workshopId: self.pickId(r.workshopId),
  753 + itemId: itemId
  754 + }
  755 + })
  756 + var body = {
  757 + procurementPlanId: this.form.planId || d.id || '',
  758 + changeReason: d.changeReason != null ? String(d.changeReason) : '',
  759 + code: d.code != null ? String(d.code) : '',
  760 + companyShortId: this.pickId(d.companyShortId || d.companyId),
  761 + supplierId: this.pickId(d.supplierId),
  762 + purchaseDepartment: this.pickId(d.purchaseDepartment),
  763 + region: this.pickId(d.region),
  764 + date: d.date != null ? String(d.date).slice(0, 10) : '',
  765 + validityPeriod: d.validityPeriod != null ? String(d.validityPeriod) : '',
  766 + quoterId: this.pickId(d.quoterId),
  767 + workshopId: this.pickId(workshopId),
  768 + quoterName: d.quoterName != null ? String(d.quoterName) : '',
  769 + longTermOperation: this.boolToStr(d.longTermOperation),
  770 + terminalCustomer: this.boolToStr(d.terminalCustomer),
  771 + priced: this.boolToStr(d.priced),
  772 + pricingDeadline: d.pricingDeadline != null ? String(d.pricingDeadline) : '',
  773 + temporaryPrice: this.boolToStr(d.temporaryPrice),
  774 + otherExpenseSettlement: this.boolToStr(d.otherExpenseSettlement),
  775 + deliveryMode: d.deliveryMode != null ? String(d.deliveryMode) : '',
  776 + commodityBuyout: this.boolToStr(d.commodityBuyout),
  777 + qualityBuyout: this.boolToStr(d.qualityBuyout),
  778 + quantityBuyout: this.boolToStr(d.quantityBuyout),
  779 + doorDelivery: this.boolToStr(d.doorDelivery),
  780 + shippingCost: d.shippingCost != null ? String(d.shippingCost) : '',
  781 + loadingFee: d.loadingFee != null ? String(d.loadingFee) : '',
  782 + invoiceProductName: d.invoiceProductName != null ? String(d.invoiceProductName) : '',
  783 + deliveryDate: d.deliveryDate != null ? String(d.deliveryDate) : '',
  784 + settlementMethod: d.settlementMethod != null ? String(d.settlementMethod) : '',
  785 + settlementRatio: d.settlementRatio != null ? String(d.settlementRatio) : '',
  786 + priceId: this.pickId(d.priceId),
  787 + coordinationReason: d.coordinationReason != null ? String(d.coordinationReason) : '',
  788 + invoiceTypeStatus: d.invoiceTypeStatus != null ? String(d.invoiceTypeStatus) : '',
  789 + totalQuantity: d.totalQuantity != null ? String(d.totalQuantity) : '',
  790 + remark: d.remark != null ? String(d.remark) : '',
  791 + materialConditionEnabled: this.boolToStr(d.materialConditionEnabled),
  792 + lines: lineList
  793 + }
  794 + this.materialConditionConfigs.forEach(function (mc) {
  795 + body[mc.field] = self.joinStr(d[mc.field])
  796 + })
  797 + return body
  798 + },
  799 + async onSubmit() {
  800 + if (!this.validateForm()) return
  801 + var payload = this.buildPayload()
  802 + try {
  803 + await procurementPlanChangeCreateApi(payload)
  804 + uni.showToast({ title: '保存成功', icon: 'none' })
  805 + try {
  806 + uni.setStorageSync('SCHEDULE_PLAN_LIST_NEED_REFRESH', '1')
  807 + } catch (e) {}
  808 + var self = this
  809 + setTimeout(function () {
  810 + uni.navigateTo({ url: '/pages/schedule-plan/index' })
  811 + }, 400)
  812 + } catch (e) {
  813 + uni.showToast({ title: (e && (e.msg || e.message)) ? (e.msg || e.message) : '保存失败', icon: 'none' })
  814 + }
  815 + }
  816 + }
  817 +}
  818 +</script>
  819 +
  820 +<style lang="scss" scoped>
  821 +.page {
  822 + display: flex;
  823 + flex-direction: column;
  824 + height: 100%;
  825 +}
  826 +.scroll {
  827 + flex: 1;
  828 + padding: 12rpx 0 392rpx !important;
  829 +}
  830 +.bottom-bar {
  831 + z-index: 2;
  832 + position: fixed;
  833 + left: 0;
  834 + right: 0;
  835 + bottom: 0;
  836 + padding: 32rpx;
  837 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  838 + background: #fff;
  839 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  840 + display: flex;
  841 + gap: 24rpx;
  842 + .btn {
  843 + flex: 1;
  844 + height: 80rpx;
  845 + line-height: 80rpx;
  846 + border-radius: 12rpx;
  847 + font-size: 32rpx;
  848 + }
  849 + .btn-outline {
  850 + background: #fff;
  851 + color: $theme-primary;
  852 + border: 2rpx solid $theme-primary;
  853 + }
  854 + .btn-primary {
  855 + background: $theme-primary;
  856 + color: #fff;
  857 + border: 2rpx solid $theme-primary;
  858 + }
  859 +}
  860 +
  861 +::v-deep .uni-list {
  862 + .uni-easyinput {
  863 + display: flex;
  864 + .uni-input-input {
  865 + color: rgba(0, 0, 0, 0.9);
  866 + }
  867 + }
  868 + .uni-input-placeholder { z-index: 1; }
  869 + .uni-input-input { background-color: #ffffff; }
  870 + background: transparent;
  871 + &-item {
  872 + &__extra-text { font-size: 32rpx; }
  873 + &__content-title {
  874 + font-size: 32rpx;
  875 + color: rgba(0, 0, 0, 0.9);
  876 + }
  877 + &__container {
  878 + padding: 32rpx;
  879 + .uni-easyinput {
  880 + &__placeholder-class {
  881 + font-size: 32rpx;
  882 + color: rgba(0, 0, 0, 0.4);
  883 + }
  884 + &__content {
  885 + border: none;
  886 + background-color: #ffffff !important;
  887 + &-input {
  888 + padding-left: 0 !important;
  889 + height: 48rpx;
  890 + line-height: 48rpx;
  891 + font-size: 32rpx;
  892 + }
  893 + .content-clear-icon {
  894 + font-size: 44rpx !important;
  895 + }
  896 + }
  897 + }
  898 + .item-title,
  899 + .uni-list-item__content {
  900 + flex: none;
  901 + min-height: 48rpx;
  902 + line-height: 48rpx;
  903 + font-size: 32rpx;
  904 + position: relative;
  905 + width: 240rpx;
  906 + margin-right: 32rpx;
  907 + color: rgba(0, 0, 0, 0.9);
  908 + .required {
  909 + color: red;
  910 + position: absolute;
  911 + top: 50%;
  912 + transform: translateY(-50%);
  913 + left: -16rpx;
  914 + }
  915 + }
  916 + }
  917 + &.select-item {
  918 + &.is-empty {
  919 + .uni-list-item__extra-text {
  920 + color: rgba(0, 0, 0, 0.4) !important;
  921 + }
  922 + }
  923 + &.is-filled {
  924 + .uni-list-item__extra-text {
  925 + color: rgba(0, 0, 0, 0.9) !important;
  926 + }
  927 + }
  928 + .serial-number-row {
  929 + display: flex;
  930 + align-items: center;
  931 + }
  932 + }
  933 + }
  934 +}
  935 +
  936 +.readonly-text {
  937 + color: rgba(0, 0, 0, 0.9);
  938 + font-size: 32rpx;
  939 + line-height: 48rpx;
  940 + text-align: right;
  941 + white-space: pre-wrap;
  942 + word-break: break-all;
  943 +}
  944 +.value-code {
  945 + width: 260rpx;
  946 + text-align: left;
  947 +}
  948 +
  949 +.attach-list {
  950 + flex: 1;
  951 + display: flex;
  952 + flex-direction: column;
  953 + align-items: flex-end;
  954 + gap: 8rpx;
  955 +}
  956 +.attach-item {
  957 + display: flex;
  958 + align-items: center;
  959 + gap: 16rpx;
  960 + .attach-name { font-size: 26rpx; color: rgba(0,0,0,0.9); }
  961 + .attach-view { font-size: 26rpx; color: $theme-primary; }
  962 +}
  963 +
  964 +.material-condition-section,
  965 +.remark-section {
  966 + margin-top: 20rpx;
  967 + background: #fff;
  968 +}
  969 +.section-header {
  970 + display: flex;
  971 + align-items: center;
  972 + padding: 24rpx 32rpx;
  973 + border-bottom: 1rpx solid #f0f0f0;
  974 +}
  975 +.section-title {
  976 + position: relative;
  977 + font-size: 32rpx;
  978 + color: rgba(0, 0, 0, 0.9);
  979 + font-weight: 600;
  980 + .required {
  981 + color: red;
  982 + position: absolute;
  983 + top: 50%;
  984 + transform: translateY(-50%);
  985 + left: -16rpx;
  986 + }
  987 +}
  988 +.opCollapse {
  989 + width: 32rpx;
  990 + height: 28rpx;
  991 + margin-right: 16rpx;
  992 + margin-top: 8rpx;
  993 +}
  994 +.section-content {
  995 + padding: 32rpx;
  996 +}
  997 +.form-row {
  998 + display: flex;
  999 + align-items: center;
  1000 + justify-content: space-between;
  1001 + margin-bottom: 24rpx;
  1002 +}
  1003 +.form-row:last-child { margin-bottom: 0; }
  1004 +.form-label {
  1005 + font-size: 32rpx;
  1006 + color: rgba(0, 0, 0, 0.9);
  1007 +}
  1008 +.form-value {
  1009 + font-size: 32rpx;
  1010 + color: rgba(0, 0, 0, 0.9);
  1011 +}
  1012 +.toggle-link {
  1013 + color: $theme-primary;
  1014 +}
  1015 +.material-group {
  1016 + margin-top: 32rpx;
  1017 +}
  1018 +.material-label {
  1019 + display: block;
  1020 + font-size: 30rpx;
  1021 + color: rgba(0, 0, 0, 0.9);
  1022 + margin-bottom: 20rpx;
  1023 +}
  1024 +.tag-list {
  1025 + display: flex;
  1026 + flex-wrap: wrap;
  1027 + gap: 16rpx;
  1028 +}
  1029 +.tag-btn {
  1030 + padding: 12rpx 24rpx;
  1031 + border: 1rpx solid #ddd;
  1032 + border-radius: 8rpx;
  1033 + font-size: 28rpx;
  1034 + color: #666;
  1035 + background: #fff;
  1036 +}
  1037 +.tag-btn.active {
  1038 + background: $theme-primary;
  1039 + border-color: $theme-primary;
  1040 + color: #fff;
  1041 +}
  1042 +
  1043 +::v-deep .uni-easyinput {
  1044 + width: 100%;
  1045 + &__content {
  1046 + background: #f8f8f8 !important;
  1047 + border-radius: 8rpx;
  1048 + &-input { font-size: 30rpx; }
  1049 + }
  1050 +}
  1051 +</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">{{ safeText(form.code) }}</text>
  7 + <view class="row"><text class="label">公司简称</text><text class="value">{{ safeText(form.companyShortName || form.companyName) }}</text></view>
  8 + <view class="row"><text class="label">供应商名称</text><text class="value">{{ safeText(form.supplierName) }}</text></view>
  9 + <view class="row"><text class="label">采购处</text><text class="value">{{ safeText(form.purchaseDepartmentName || form.purchaseDepartment) }}</text></view>
  10 + <view class="row"><text class="label">区域</text><text class="value">{{ safeText(form.regionName || form.region) }}</text></view>
  11 + <view class="row"><text class="label">日期</text><text class="value">{{ formatDateOnly(form.date) }}</text></view>
  12 + <view class="row"><text class="label">报价时效</text><text class="value">{{ safeText(form.validityPeriod) }}</text></view>
  13 + <view class="row"><text class="label">报价人</text><text class="value">{{ safeText(form.quoterName) }}</text></view>
  14 + <view class="row"><text class="label">是否长单操作</text><text class="value">{{ boolText(form.longTermOperation) }}</text></view>
  15 + <view class="row"><text class="label">是否终端客户</text><text class="value">{{ boolText(form.terminalCustomer) }}</text></view>
  16 + <view class="row"><text class="label">是否未点价</text><text class="value">{{ boolText(form.priced) }}</text></view>
  17 + <view v-if="form.priced === true" class="row"><text class="label">点价截止日期</text><text class="value">{{ formatDateOnly(form.pricingDeadline) }}</text></view>
  18 + <view v-if="form.priced === true" class="row"><text class="label">是否暂定价</text><text class="value">{{ boolText(form.temporaryPrice) }}</text></view>
  19 + <view class="row"><text class="label">其他费用凭票到厂另行结算</text><text class="value">{{ boolText(form.otherExpenseSettlement) }}</text></view>
  20 + <view class="row"><text class="label">交提货方式</text><text class="value">{{ getDeliveryMethodLabel() }}</text></view>
  21 + <view class="row"><text class="label">是否通货买断</text><text class="value">{{ boolText(form.commodityBuyout) }}</text></view>
  22 + <view class="row"><text class="label">是否质量买断</text><text class="value">{{ boolText(form.qualityBuyout) }}</text></view>
  23 + <view class="row"><text class="label">是否数量买断</text><text class="value">{{ boolText(form.quantityBuyout) }}</text></view>
  24 + <view class="row"><text class="label">是否送货上门</text><text class="value">{{ boolText(form.doorDelivery) }}</text></view>
  25 + <view class="row"><text class="label">另付运费</text><text class="value">{{ formatNumber(form.shippingCost) }}</text></view>
  26 + <view class="row"><text class="label">装车费</text><text class="value">{{ formatNumber(form.loadingFee) }}</text></view>
  27 + <view class="row"><text class="label">开票品名</text><text class="value">{{ safeText(form.invoiceProductName) }}</text></view>
  28 + <view class="row"><text class="label">交货日期</text><text class="value">{{ formatDateOnly(form.deliveryDate) }}</text></view>
  29 + <view class="row"><text class="label">结算方式</text><text class="value">{{ safeText(form.settlementMethod) }}</text></view>
  30 + <view class="row"><text class="label">结算比例(%)</text><text class="value">{{ formatNumber(form.settlementRatio) }}</text></view>
  31 + <view class="row"><text class="label">价格表编号</text><text class="value">{{ priceTableDisplay }}</text></view>
  32 + <view class="row"><text class="label">协调事由</text><text class="value">{{ safeText(form.coordinationReason) }}</text></view>
  33 + <view class="row"><text class="label">票类状况</text><text class="value">{{ safeText(form.invoiceTypeStatus) }}</text></view>
  34 + <view class="row"><text class="label">数量合计</text><text class="value">{{ formatNumber(form.totalQuantity) }}</text></view>
  35 + <view class="row" v-if="attachmentList.length">
  36 + <text class="label">附件</text>
  37 + <view class="attach-list">
  38 + <view v-for="(at, idx) in attachmentList" :key="idx" class="attach-item">
  39 + <text class="attach-name">{{ at.name }}</text>
  40 + <text class="attach-view" @click.stop="onPreviewAttachment(at)">查看</text>
  41 + </view>
  42 + </view>
  43 + </view>
  44 + </view>
  45 +
  46 + <view class="title-header">
  47 + <image class="title-header_icon" src="/static/images/title.png" />
  48 + <span>明细信息</span>
  49 + </view>
  50 + <view v-if="lineListWithName.length" class="section">
  51 + <view v-for="(line, idx) in lineListWithName" :key="line.itemId || idx" class="line-block">
  52 + <view class="line-title">明细{{ idx + 1 }}</view>
  53 + <view class="row"><text class="label">品种</text><text class="value">{{ safeText(line.productName) }}</text></view>
  54 + <view class="row"><text class="label">料型</text><text class="value">{{ safeText(line.materialType) }}</text></view>
  55 + <view class="row"><text class="label">数量(吨)</text><text class="value">{{ formatNumber(line.quantity) }}</text></view>
  56 + <view class="row"><text class="label">实际采购价</text><text class="value">{{ formatNumber(line.actualPurchasePrice) }}</text></view>
  57 + <view class="row"><text class="label">含运到厂价</text><text class="value">{{ formatNumber(line.includingShippingPrice) }}</text></view>
  58 + <view class="row"><text class="label">价格表价格</text><text class="value">{{ formatNumber(line.priceListPrices) }}</text></view>
  59 + <view class="row"><text class="label">协调幅度</text><text class="value">{{ formatNumber(line.coordinationRange) }}</text></view>
  60 + <view class="row"><text class="label">买断料预计损耗</text><text class="value">{{ formatNumber(line.estimatedLoss) }}</text></view>
  61 + <view class="row"><text class="label">申请损耗(%)</text><text class="value">{{ formatNumber(line.applicationAttrition) }}</text></view>
  62 + <view class="row"><text class="label">选择分厂</text><text class="value">{{ safeText(line.workshopName) }}</text></view>
  63 + </view>
  64 + </view>
  65 + <view v-else class="section empty-tip">暂无明细</view>
  66 +
  67 + <view class="title-header">
  68 + <image class="title-header_icon" src="/static/images/title.png" />
  69 + <span>料质状况</span>
  70 + </view>
  71 + <view class="section">
  72 + <view class="row"><text class="label">是否启用料质状况</text><text class="value">{{ boolText(form.materialConditionEnabled) }}</text></view>
  73 + <template v-if="form.materialConditionEnabled">
  74 + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group">
  75 + <text class="material-label">{{ mc.label }}</text>
  76 + <view class="tag-list">
  77 + <view v-for="opt in getDictOptions(mc.dictCode)" :key="opt.code" class="tag-btn" :class="{ active: isTagSelected(mc.field, opt.code) }">{{ opt.name }}</view>
  78 + </view>
  79 + </view>
  80 + </template>
  81 + </view>
  82 +
  83 + <view class="title-header">
  84 + <image class="title-header_icon" src="/static/images/title.png" />
  85 + <span>备注</span>
  86 + </view>
  87 + <view class="section">
  88 + <text class="remark-text">{{ safeText(form.remark) }}</text>
  89 + </view>
  90 +
  91 + <view class="title-header">
  92 + <image class="title-header_icon" src="/static/images/title.png" />
  93 + <span>变更事由</span>
  94 + </view>
  95 + <view class="section">
  96 + <text class="remark-text">{{ safeText(form.changeReason) }}</text>
  97 + </view>
  98 + </view>
  99 + </scroll-view>
  100 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
  101 + </view>
  102 +</template>
  103 +
  104 +<script>
  105 +import { procurementPlanChangeFindByIdApi, procurementPlanChangeSubmitApi, procurementPlanChangeCancelApi } from '@/api/procure-manage/procurementPlan.js'
  106 +import { breedRelationshipQueryApi, workshopQueryApi } from '@/api/devManage.js'
  107 +import { getDicByCodes } from '@/utils/dic.js'
  108 +import DetailButtons from '@/components/detail-buttons/index.vue'
  109 +export default {
  110 + name: 'SchedulePlanDetail',
  111 + components: { DetailButtons },
  112 + data() {
  113 + return {
  114 + id: '',
  115 + form: {},
  116 + lineList: [],
  117 + attachmentList: [],
  118 + materialConditionConfigs: [
  119 + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' },
  120 + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' },
  121 + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' },
  122 + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' },
  123 + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' },
  124 + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' },
  125 + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' },
  126 + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' }
  127 + ],
  128 + dictData: {},
  129 + productOptions: [],
  130 + workshopOptions: [],
  131 + buttons: [
  132 + { text: '编辑', visible: true, variant: 'outline', event: 'edit' },
  133 + { text: '发起变更审核', visible: true, variant: 'primary', event: 'initiate' },
  134 + { text: '取消', visible: true, variant: 'outline', event: 'cancel' },
  135 + { text: '审核', visible: true, variant: 'primary', event: 'approve' },
  136 + { text: '审核详情', visible: true, event: 'approveDetail' },
  137 + ],
  138 + }
  139 + },
  140 + computed: {
  141 + productMap() {
  142 + var list = Array.isArray(this.productOptions) ? this.productOptions : []
  143 + var m = {}
  144 + list.forEach(function (o) {
  145 + var key = String(o.productId || o.id || o.value || '')
  146 + if (key) m[key] = o
  147 + })
  148 + return m
  149 + },
  150 + workshopMap() {
  151 + var list = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  152 + var m = {}
  153 + list.forEach(function (o) {
  154 + var key = String(o.workshopId || o.id || o.value || '')
  155 + if (key) m[key] = o
  156 + })
  157 + return m
  158 + },
  159 + lineListWithName() {
  160 + var self = this
  161 + var list = Array.isArray(this.lineList) ? this.lineList : []
  162 + return list.map(function (row) {
  163 + var pid = String(row.productId || '')
  164 + var wid = String(row.workshopId || '')
  165 + var p = pid ? self.productMap[pid] : null
  166 + var w = wid ? self.workshopMap[wid] : null
  167 + return Object.assign({}, row, {
  168 + productName: row.productName || (p ? (p.productName || p.name || p.label || '') : ''),
  169 + workshopName: row.workshopName || (w ? (w.workshopName || w.name || w.label || '') : '')
  170 + })
  171 + })
  172 + },
  173 + priceTableDisplay() {
  174 + var d = this.form || {}
  175 + var code = d.priceTableCode != null ? d.priceTableCode : (d.priceId != null ? d.priceId : '')
  176 + var name = d.priceTableName || ''
  177 + if (name || code) {
  178 + return (name ? name + (code ? ' (' + code + ')' : '') : String(code))
  179 + }
  180 + return '-'
  181 + },
  182 + displayButtons() {
  183 + const status = this.form ? this.form.approvalStatus : '';
  184 + const isDraft = status === 'DRAFT';
  185 + const isAudit = status === 'AUDIT';
  186 + const isPass = status === 'PASS';
  187 + const isRefuse = status === 'REFUSE';
  188 + const canExamine = isAudit && this.form && this.form.showExamine === true;
  189 + return [
  190 + { ...this.buttons[0], visible: ((isDraft || isRefuse) && this.$auth.hasPermi('plan-manage:schedule-plan:modify')) },
  191 + { ...this.buttons[1], visible: ((isDraft || isRefuse) && this.$auth.hasPermi('plan-manage:schedule-plan:initiate')) },
  192 + { ...this.buttons[2], visible: (isRefuse && this.$auth.hasPermi('plan-manage:schedule-plan:cancel')) },
  193 + { ...this.buttons[3], visible: (canExamine && this.$auth.hasPermi('plan-manage:schedule-plan:approve')) },
  194 + { ...this.buttons[4], visible: ((isAudit || isPass) && this.form.showExamine && this.$auth.hasPermi('plan-manage:schedule-plan:view')) },
  195 + ]
  196 + }
  197 + },
  198 + onLoad(query) {
  199 + this.id = (query && query.id) || ''
  200 + this.loadDictData()
  201 + this.loadRefData()
  202 + this.loadDetail()
  203 + },
  204 + methods: {
  205 + handleButtonClick(btn) {
  206 + if (!btn || btn.disabled) return
  207 + const e = btn.event || ''
  208 + if (e === 'edit') return this.onEdit()
  209 + if (e === 'initiate') return this.onInitiate()
  210 + if (e === 'approve') return this.onApprove()
  211 + if (e === 'approveDetail') return this.onApproveDetail()
  212 + if (e === 'cancel') return this.onCancel()
  213 + },
  214 + refreshListFlags() {
  215 + try {
  216 + uni.setStorageSync('SCHEDULE_PLAN_LIST_NEED_REFRESH', '1')
  217 + } catch (e) {}
  218 + },
  219 + runWithConfirm(opts) {
  220 + var self = this
  221 + var title = opts.title || '提示'
  222 + var content = opts.content || ''
  223 + var run = opts.run
  224 + var loading = opts.loading || '处理中'
  225 + var successText = opts.successText || '操作成功'
  226 + var failText = opts.failText || '操作失败'
  227 + if (!this.id) {
  228 + uni.showToast({ title: '缺少单据id', icon: 'none' })
  229 + return
  230 + }
  231 + var apiFn = function () {
  232 + uni.showLoading({ title: loading, mask: true })
  233 + Promise.resolve(run(self.id)).then(function () {
  234 + uni.hideLoading()
  235 + uni.showToast({ title: successText, icon: 'none' })
  236 + self.refreshListFlags()
  237 + self.loadDetail()
  238 + }).catch(function (e) {
  239 + uni.hideLoading()
  240 + uni.showToast({ title: (e && (e.msg || e.message)) ? (e.msg || e.message) : failText, icon: 'none' })
  241 + })
  242 + }
  243 + if (opts.skipConfirm) {
  244 + apiFn()
  245 + return
  246 + }
  247 + uni.showModal({
  248 + title: title,
  249 + content: content,
  250 + confirmText: '确定',
  251 + cancelText: '取消',
  252 + success: function (res) {
  253 + if (!res || !res.confirm) return
  254 + apiFn()
  255 + }
  256 + })
  257 + },
  258 + onInitiate() {
  259 + this.runWithConfirm({
  260 + title: '发起变更审核',
  261 + content: '确认发起变更审核吗',
  262 + loading: '提交中',
  263 + successText: '发起成功',
  264 + failText: '发起变更审核失败',
  265 + run: function (id) { return procurementPlanChangeSubmitApi(id) }
  266 + })
  267 + },
  268 + onEdit() {
  269 + if (!this.id) {
  270 + uni.showToast({ title: '缺少单据ID', icon: 'none' })
  271 + return
  272 + }
  273 + uni.navigateTo({ url: '/pages/schedule-plan/modify?id=' + this.id })
  274 + },
  275 + async loadDictData() {
  276 + var codes = [].concat(
  277 + this.materialConditionConfigs.map(function (mc) { return mc.dictCode }),
  278 + ['DELIVERY_METHOD']
  279 + )
  280 + try {
  281 + var results = await getDicByCodes(codes)
  282 + var d = results || {}
  283 + this.dictData = d
  284 + this.deliveryOptions = (d.DELIVERY_METHOD && d.DELIVERY_METHOD.data) || []
  285 + } catch (e) {
  286 + this.dictData = {}
  287 + this.deliveryOptions = []
  288 + }
  289 + },
  290 + async loadRefData() {
  291 + var self = this
  292 + try {
  293 + var results = await Promise.all([
  294 + this.pCall(breedRelationshipQueryApi, { pageIndex: 1, pageSize: 1000 }),
  295 + this.pCall(workshopQueryApi, { pageIndex: 1, pageSize: 1000 })
  296 + ])
  297 + var r1 = (results[0] && results[0].data) ? results[0].data : {}
  298 + var r2 = (results[1] && results[1].data) ? results[1].data : {}
  299 + self.productOptions = (r1.datas && Array.isArray(r1.datas)) ? r1.datas : ((r1.list && Array.isArray(r1.list)) ? r1.list : ((r1.records && Array.isArray(r1.records)) ? r1.records : []))
  300 + self.workshopOptions = (r2.datas && Array.isArray(r2.datas)) ? r2.datas : ((r2.list && Array.isArray(r2.list)) ? r2.list : ((r2.records && Array.isArray(r2.records)) ? r2.records : []))
  301 + } catch (e) {
  302 + self.productOptions = []
  303 + self.workshopOptions = []
  304 + }
  305 + },
  306 + pCall(fn, params) {
  307 + try {
  308 + return Promise.resolve(fn(params))
  309 + } catch (e) {
  310 + return Promise.reject(e)
  311 + }
  312 + },
  313 + async loadDetail() {
  314 + if (!this.id) return
  315 + try {
  316 + var res = await procurementPlanChangeFindByIdApi({ id: this.id })
  317 + var d = (res && res.data) ? res.data : {}
  318 + var boolFields = ['longTermOperation', 'terminalCustomer', 'priced', 'temporaryPrice', 'otherExpenseSettlement', 'commodityBuyout', 'qualityBuyout', 'quantityBuyout', 'doorDelivery', 'materialConditionEnabled']
  319 + boolFields.forEach(function (f) {
  320 + var v = d[f]
  321 + if (typeof v === 'string') {
  322 + d[f] = v === 'true' || v === '1'
  323 + } else if (typeof v === 'number') {
  324 + d[f] = v === 1
  325 + } else {
  326 + d[f] = !!v
  327 + }
  328 + })
  329 + this.materialConditionConfigs.forEach(function (mc) {
  330 + var v = d[mc.field]
  331 + if (typeof v === 'string' && v) {
  332 + d[mc.field] = v.split(',').filter(Boolean)
  333 + } else if (!Array.isArray(v)) {
  334 + d[mc.field] = []
  335 + }
  336 + })
  337 + this.form = d
  338 + var lines = d.lines || d.procurementPlanLineList || d.lineList || []
  339 + this.lineList = Array.isArray(lines) ? lines : []
  340 + this.buildAttachmentList()
  341 + } catch (e) {
  342 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  343 + }
  344 + },
  345 + buildAttachmentList() {
  346 + var d = this.form || {}
  347 + var ids = d.attachmentFileIds
  348 + var names = d.attachmentFileNames
  349 + var idArr = (typeof ids === 'string' && ids) ? ids.split(',').filter(Boolean) : (Array.isArray(ids) ? ids : [])
  350 + var nameArr = (typeof names === 'string' && names) ? names.split(',').filter(Boolean) : (Array.isArray(names) ? names : [])
  351 + var len = Math.max(idArr.length, nameArr.length)
  352 + var list = []
  353 + for (var i = 0; i < len; i++) {
  354 + list.push({ id: idArr[i] || '', name: nameArr[i] || ('附件' + (i + 1)) })
  355 + }
  356 + this.attachmentList = list
  357 + },
  358 + getDictOptions(dictCode) {
  359 + var data = this.dictData[dictCode]
  360 + return (data && data.data) ? data.data : []
  361 + },
  362 + isTagSelected(field, code) {
  363 + var selected = this.form ? this.form[field] : undefined
  364 + if (Array.isArray(selected)) return selected.indexOf(code) >= 0
  365 + if (typeof selected === 'string' && selected) {
  366 + return selected.split(',').indexOf(code) >= 0
  367 + }
  368 + return false
  369 + },
  370 + getDeliveryMethodLabel() {
  371 + var opts = this.deliveryOptions || []
  372 + var v = String(this.form.deliveryMode != null ? this.form.deliveryMode : '')
  373 + if (!v) return '-'
  374 + for (var i = 0; i < opts.length; i++) {
  375 + var o = opts[i]
  376 + var code = o.code != null ? String(o.code) : (o.value != null ? String(o.value) : '')
  377 + if (code === v) return o.name || o.label || '-'
  378 + }
  379 + return v
  380 + },
  381 + boolText(v) {
  382 + if (typeof v === 'boolean') return v ? '是' : '否'
  383 + if (v === 'true' || v === '1' || v === 1) return '是'
  384 + if (v === 'false' || v === '0' || v === 0) return '否'
  385 + return '-'
  386 + },
  387 + formatDateOnly(v) {
  388 + var s = v == null ? '' : String(v).trim()
  389 + if (!s) return '-'
  390 + if (s.length >= 10) return s.slice(0, 10)
  391 + return s
  392 + },
  393 + safeText(v) {
  394 + var s = v == null ? '' : String(v)
  395 + return s.trim() ? s : '-'
  396 + },
  397 + formatNumber(v) {
  398 + if (v == null || v === '') return '-'
  399 + var n = Number(v)
  400 + if (isNaN(n)) return String(v)
  401 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  402 + },
  403 + onCancel() {
  404 + this.runWithConfirm({
  405 + title: '取消',
  406 + content: '确认取消该变更单吗?',
  407 + loading: '提交中',
  408 + successText: '取消成功',
  409 + failText: '取消失败',
  410 + run: function (id) { return procurementPlanChangeCancelApi(id) }
  411 + })
  412 + },
  413 + onApprove() {
  414 + const id = this.id || (this.form && this.form.id) || ''
  415 + if (!id) return
  416 + const CACHE_KEY = 'sourceBusinessId'
  417 + const TYPE = 'contractType'
  418 + uni.setStorageSync(TYPE, 'PLAN_CHANGE')
  419 + uni.setStorageSync(CACHE_KEY, id)
  420 + uni.navigateTo({ url: '/pages/flow/audit' })
  421 + },
  422 + onApproveDetail() {
  423 + const id = this.id || (this.form && this.form.id) || ''
  424 + if (!id) return
  425 + const CACHE_KEY = 'sourceBusinessId'
  426 + const TYPE = 'contractType'
  427 + uni.setStorageSync(TYPE, 'PLAN_CHANGE')
  428 + uni.setStorageSync(CACHE_KEY, id)
  429 + uni.navigateTo({ url: '/pages/flow/audit_detail' })
  430 + },
  431 + onPreviewAttachment(at) {
  432 + if (!at || !at.id) {
  433 + uni.showToast({ title: '无法查看', icon: 'none' })
  434 + return
  435 + }
  436 + uni.showToast({ title: '查看附件 ' + at.name, icon: 'none' })
  437 + }
  438 + }
  439 +}
  440 +</script>
  441 +
  442 +<style lang="scss" scoped>
  443 +.page {
  444 + display: flex;
  445 + flex-direction: column;
  446 + height: 100%;
  447 +}
  448 +.scroll {
  449 + flex: 1;
  450 + padding: 8rpx 0 144rpx;
  451 +}
  452 +.detail-page {
  453 + background: #f3f3f3;
  454 +}
  455 +.title-header {
  456 + display: flex;
  457 + align-items: center;
  458 + padding: 24rpx 32rpx 12rpx;
  459 + background-color: #ffffff;
  460 + .title-header_icon {
  461 + width: 36rpx;
  462 + height: 36rpx;
  463 + margin-right: 12rpx;
  464 + }
  465 + span {
  466 + font-size: 34rpx;
  467 + font-weight: 600;
  468 + color: rgba(0, 0, 0, 0.9);
  469 + }
  470 +}
  471 +.section {
  472 + padding: 32rpx;
  473 + background: #fff;
  474 + margin-bottom: 20rpx;
  475 +}
  476 +.row {
  477 + display: flex;
  478 + margin-bottom: 28rpx;
  479 + &:last-child { margin-bottom: 0; }
  480 + &.company {
  481 + font-size: 36rpx;
  482 + font-weight: 600;
  483 + color: rgba(0, 0, 0, 0.9);
  484 + padding-top: 8rpx;
  485 + line-height: 50rpx;
  486 + }
  487 + .label {
  488 + max-width: 420rpx;
  489 + margin-right: 20rpx;
  490 + line-height: 32rpx;
  491 + font-size: 28rpx;
  492 + color: rgba(0, 0, 0, 0.6);
  493 + }
  494 + .value {
  495 + flex: 1;
  496 + line-height: 32rpx;
  497 + font-size: 28rpx;
  498 + color: rgba(0, 0, 0, 0.9);
  499 + text-align: right;
  500 + word-break: break-all;
  501 + }
  502 +}
  503 +.line-block {
  504 + padding-bottom: 24rpx;
  505 + margin-bottom: 24rpx;
  506 + border-bottom: 1rpx solid #f0f0f0;
  507 + &:last-child {
  508 + padding-bottom: 0;
  509 + margin-bottom: 0;
  510 + border-bottom: none;
  511 + }
  512 +}
  513 +.line-title {
  514 + font-size: 30rpx;
  515 + font-weight: 600;
  516 + color: rgba(0, 0, 0, 0.9);
  517 + margin-bottom: 20rpx;
  518 +}
  519 +.attach-list {
  520 + flex: 1;
  521 + display: flex;
  522 + flex-direction: column;
  523 + align-items: flex-end;
  524 + gap: 8rpx;
  525 +}
  526 +.attach-item {
  527 + display: flex;
  528 + align-items: center;
  529 + gap: 16rpx;
  530 + .attach-name {
  531 + font-size: 26rpx;
  532 + color: rgba(0, 0, 0, 0.9);
  533 + }
  534 + .attach-view {
  535 + font-size: 26rpx;
  536 + color: $theme-primary;
  537 + }
  538 +}
  539 +.material-group {
  540 + margin-top: 32rpx;
  541 + &:first-child { margin-top: 0; }
  542 +}
  543 +.material-label {
  544 + display: block;
  545 + font-size: 28rpx;
  546 + color: rgba(0, 0, 0, 0.6);
  547 + margin-bottom: 20rpx;
  548 +}
  549 +.tag-list {
  550 + display: flex;
  551 + flex-wrap: wrap;
  552 + gap: 16rpx;
  553 +}
  554 +.tag-btn {
  555 + padding: 12rpx 24rpx;
  556 + border: 1rpx solid #ddd;
  557 + border-radius: 8rpx;
  558 + font-size: 28rpx;
  559 + color: #999;
  560 + background: #f5f5f5;
  561 +}
  562 +.tag-btn.active {
  563 + background: $theme-primary;
  564 + border-color: $theme-primary;
  565 + color: #fff;
  566 +}
  567 +.remark-text {
  568 + font-size: 28rpx;
  569 + color: rgba(0, 0, 0, 0.9);
  570 + line-height: 1.6;
  571 + white-space: pre-wrap;
  572 +}
  573 +.empty-tip {
  574 + text-align: center;
  575 + font-size: 28rpx;
  576 + color: rgba(0, 0, 0, 0.4);
  577 + padding: 24rpx 0;
  578 +}
  579 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <view class="plan-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入编号或供应商名称"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  18 + <image v-if="$auth.hasPermi('plan-manage:schedule-plan:add')" class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  19 + </view>
  20 + </view>
  21 + </view>
  22 +
  23 + <view class="list-box">
  24 + <card-list
  25 + ref="cardRef"
  26 + :fetch-fn="fetchList"
  27 + :query="query"
  28 + :extra="extraParams"
  29 + row-key="id"
  30 + :enable-refresh="true"
  31 + :enable-load-more="true"
  32 + @loaded="onCardLoaded"
  33 + @error="onCardError"
  34 + >
  35 + <template v-slot="{ item }">
  36 + <view class="card" @click="onCardClick(item)">
  37 + <view class="card-header">
  38 + <text class="title omit2">{{ item.code || '-' }}</text>
  39 + <text :class="['status', `status_${item.approvalStatus}`]">{{ getAuditStatusText(item.approvalStatus) }}</text>
  40 + </view>
  41 + <view class="info-row">
  42 + <text>公司名称</text>
  43 + <text>{{item.companyShortName || '-' }}</text>
  44 + </view>
  45 + <view class="info-row">
  46 + <text>供应商名称</text>
  47 + <text>{{ item.supplierName || '-' }}</text>
  48 + </view>
  49 + <view class="info-row">
  50 + <text>数量</text>
  51 + <text>{{ formatQuantity(item.totalQuantity || 0) }}</text>
  52 + </view>
  53 + <view class="info-row">
  54 + <text>采购处</text>
  55 + <text>{{ item.purchaseDepartmentName || '-' }}</text>
  56 + </view>
  57 + </view>
  58 + </template>
  59 + </card-list>
  60 + </view>
  61 +
  62 + <filter-modal
  63 + :visible.sync="filterVisible"
  64 + :value.sync="filterForm"
  65 + title="筛选"
  66 + @reset="onFilterReset"
  67 + @confirm="onFilterConfirm"
  68 + >
  69 + <template v-slot="{ model }">
  70 + <view class="filter-form">
  71 + <view class="form-item">
  72 + <view class="label">编号</view>
  73 + <input class="input" v-model="model.code" placeholder="请输入编号" />
  74 + </view>
  75 + <view class="form-item">
  76 + <view class="label">供应商名称</view>
  77 + <input class="input" v-model="model.supplierName" placeholder="请输入供应商名称" />
  78 + </view>
  79 + <view class="form-item">
  80 + <view class="label">日期</view>
  81 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  82 + </view>
  83 + </view>
  84 + </template>
  85 + </filter-modal>
  86 + </view>
  87 +</template>
  88 +
  89 +<script>
  90 +import CardList from '@/components/card/index.vue'
  91 +import FilterModal from '@/components/filter/index.vue'
  92 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
  93 +import { procurementPlanChangeQueryApi } from '@/api/procure-manage/procurementPlan.js'
  94 +
  95 +export default {
  96 + name: 'SchedulePlan',
  97 + components: { CardList, FilterModal },
  98 + data() {
  99 + return {
  100 + searchKeyword: '',
  101 + searchKeywordDebounced: '',
  102 + searchDebounceTimer: null,
  103 + query: {
  104 + code: '',
  105 + supplierName: '',
  106 + dateRange: []
  107 + },
  108 + extraParams: {},
  109 + currentItems: [],
  110 + filterVisible: false,
  111 + filterForm: {
  112 + code: '',
  113 + supplierName: '',
  114 + dateRange: []
  115 + },
  116 + dicOptions: {
  117 + AUDIT_STATUS: []
  118 + }
  119 + }
  120 + },
  121 + computed: {
  122 + extraCombined() {
  123 + const keyword = this.searchKeywordDebounced || ''
  124 + return {
  125 + keyword: keyword || undefined
  126 + }
  127 + }
  128 + },
  129 + watch: {
  130 + extraCombined: {
  131 + deep: true,
  132 + handler(v) {
  133 + this.extraParams = v
  134 + },
  135 + immediate: true
  136 + }
  137 + },
  138 + created() {
  139 + this.loadDicData()
  140 + },
  141 + onShow() {
  142 + let needRefresh = ''
  143 + try { needRefresh = uni.getStorageSync('SCHEDULE_PLAN_LIST_NEED_REFRESH') } catch (e) {}
  144 + if (!needRefresh) return
  145 + try { uni.removeStorageSync('SCHEDULE_PLAN_LIST_NEED_REFRESH') } catch (e) {}
  146 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) {
  147 + this.$refs.cardRef.reload()
  148 + }
  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 + onAdd() {
  163 + uni.navigateTo({ url: '/pages/schedule-plan/add' })
  164 + },
  165 + async loadDicData() {
  166 + try {
  167 + const results = await getDicByCodes(['AUDIT_STATUS'])
  168 + this.dicOptions.AUDIT_STATUS = (results.AUDIT_STATUS && results.AUDIT_STATUS.data) || []
  169 + } catch (e) {
  170 + this.dicOptions.AUDIT_STATUS = []
  171 + }
  172 + },
  173 + getAuditStatusText(status) {
  174 + const v = status == null ? '' : String(status)
  175 + if (!v) return '-'
  176 + return getDicName('AUDIT_STATUS', v, this.dicOptions.AUDIT_STATUS) || v
  177 + },
  178 + getTotalQuantity(item) {
  179 + const lines = item && item.procurementPlanLineList && Array.isArray(item.procurementPlanLineList)
  180 + ? item.procurementPlanLineList
  181 + : []
  182 + let total = 0
  183 + lines.forEach(line => {
  184 + const q = line && line.quantity != null ? Number(line.quantity) : 0
  185 + if (!isNaN(q)) total += q
  186 + })
  187 + return total
  188 + },
  189 + formatQuantity(val) {
  190 + if (val == null) return '-'
  191 + try {
  192 + const num = Number(val)
  193 + if (isNaN(num)) return '-'
  194 + return num.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  195 + } catch (e) {
  196 + return String(val)
  197 + }
  198 + },
  199 + onCardLoaded(_ref) {
  200 + var items = _ref.items
  201 + this.currentItems = items
  202 + },
  203 + onCardError() {
  204 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  205 + },
  206 + onSearchInput() {
  207 + var _this = this
  208 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  209 + this.searchDebounceTimer = setTimeout(function () {
  210 + _this.searchKeywordDebounced = _this.searchKeyword
  211 + _this.searchDebounceTimer = null
  212 + }, 1200)
  213 + },
  214 + search(e) {
  215 + const val = e && e.value != null ? e.value : this.searchKeyword
  216 + this.searchKeyword = val
  217 + this.searchKeywordDebounced = val
  218 + },
  219 + openFilter() {
  220 + this.filterForm = {
  221 + code: this.query.code || '',
  222 + supplierName: this.query.supplierName || '',
  223 + dateRange: Array.isArray(this.query.dateRange) ? this.query.dateRange.slice(0) : []
  224 + }
  225 + this.filterVisible = true
  226 + },
  227 + onFilterReset(payload) {
  228 + this.filterForm = payload
  229 + },
  230 + onFilterConfirm(payload) {
  231 + this.query = {
  232 + code: payload.code || '',
  233 + supplierName: payload.supplierName || '',
  234 + dateRange: Array.isArray(payload.dateRange) ? payload.dateRange : []
  235 + }
  236 + },
  237 + fetchList(_ref2) {
  238 + var _this2 = this
  239 + var pageIndex = _ref2.pageIndex
  240 + var pageSize = _ref2.pageSize
  241 + var query = _ref2.query
  242 + var extra = _ref2.extra
  243 + const q = query || {}
  244 + const e = extra || {}
  245 + const range = Array.isArray(q.dateRange) ? q.dateRange : []
  246 + const keyword = e.keyword || ''
  247 + const params = {
  248 + pageIndex: pageIndex,
  249 + pageSize: pageSize,
  250 + code: q.code || '',
  251 + supplierName: q.supplierName || '',
  252 + startDate: range && range.length === 2 ? range[0] : '',
  253 + endDate: range && range.length === 2 ? range[1] : ''
  254 + }
  255 + if (keyword) {
  256 + if (!params.code) params.code = keyword
  257 + if (!params.supplierName) params.supplierName = keyword
  258 + }
  259 + return procurementPlanChangeQueryApi(params).then(function (res) {
  260 + const _data = res && res.data ? res.data : {}
  261 + let records = _data.datas || _data.list || _data.records || []
  262 + const totalCount = _data.totalCount || _data.count || 0
  263 + const hasNext = _data.hasNext || false
  264 + records = records.map(function (it) { return Object.assign({}, it) })
  265 + return { records: records, totalCount: totalCount, hasNext: hasNext }
  266 + }).catch(function () {
  267 + _this2.onCardError()
  268 + return { records: [], totalCount: 0, hasNext: false }
  269 + })
  270 + },
  271 + onCardClick(item) {
  272 + uni.navigateTo({ url: '/pages/schedule-plan/detail?id=' + item.id })
  273 + }
  274 + }
  275 +}
  276 +</script>
  277 +
  278 +<style lang="scss" scoped>
  279 +.page {
  280 + display: flex;
  281 + flex-direction: column;
  282 + height: 100vh;
  283 +}
  284 +.plan-list-fixed {
  285 + position: fixed;
  286 + top: 96rpx;
  287 + left: 0;
  288 + right: 0;
  289 + z-index: 2;
  290 + background: #fff;
  291 + .search-row {
  292 + display: flex;
  293 + align-items: center;
  294 + padding: 16rpx 32rpx;
  295 + .uni-searchbar { padding: 0; flex: 1; }
  296 + .tool-icons {
  297 + display: flex;
  298 + .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; }
  299 + }
  300 + }
  301 +}
  302 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  303 +.list-box {
  304 + flex: 1;
  305 + padding-top: 140rpx;
  306 + .card { position: relative; }
  307 + .card-header {
  308 + margin-bottom: 28rpx;
  309 + position: relative;
  310 + .title { font-size: 36rpx; font-weight: 600; line-height: 50rpx; color: rgba(0,0,0,0.9); width: 578rpx; }
  311 + .status {
  312 + position: absolute;
  313 + top: -32rpx;
  314 + right: -12rpx;
  315 + height: 48rpx;
  316 + line-height: 48rpx;
  317 + font-weight: 600;
  318 + color: #fff;
  319 + font-size: 24rpx;
  320 + padding: 0 14rpx;
  321 + border-radius: 6rpx;
  322 + box-sizing: content-box;
  323 +
  324 + &.status_AUDIT {
  325 + background: $theme-primary;
  326 + }
  327 +
  328 + &.status_PASS {
  329 + background: #2BA471;
  330 + }
  331 +
  332 + &.status_REFUSE {
  333 + background: #D54941;
  334 + }
  335 +
  336 + &.status_CANCEL {
  337 + background: #E7E7E7;
  338 + color: rgba(0, 0, 0, 0.9);
  339 + }
  340 + }
  341 + }
  342 + .info-row {
  343 + display: flex;
  344 + align-items: center;
  345 + color: rgba(0,0,0,0.6);
  346 + font-size: 28rpx;
  347 + margin-bottom: 24rpx;
  348 + height: 32rpx;
  349 + &:last-child { margin-bottom: 0; }
  350 + text {
  351 + width: 50%;
  352 + &:last-child { color: rgba(0,0,0,0.9); width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  353 + }
  354 + }
  355 +}
  356 +.filter-form {
  357 + .form-item { margin-bottom: 24rpx; }
  358 + .label { margin-bottom: 20rpx; color: rgba(0,0,0,0.9); height: 44rpx; line-height: 44rpx; font-size: 30rpx; }
  359 + .input {
  360 + width: 100%;
  361 + height: 72rpx;
  362 + line-height: 72rpx;
  363 + padding: 0 24rpx;
  364 + border: 1rpx solid #E7E7E7;
  365 + border-radius: 8rpx;
  366 + font-size: 28rpx;
  367 + color: rgba(0,0,0,0.9);
  368 + background: #fff;
  369 + box-sizing: border-box;
  370 + }
  371 +}
  372 +</style>
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view scroll-y class="scroll">
  4 + <uni-list>
  5 + <uni-list-item class="select-item" :class="form.planCode ? 'is-filled' : 'is-empty'">
  6 + <template v-slot:body>
  7 + <view class="item-title"><text class="required">*</text><text>计划数据</text></view>
  8 + </template>
  9 + <template v-slot:footer>
  10 + <text class="readonly-text">{{ form.planCode || '-' }}</text>
  11 + </template>
  12 + </uni-list-item>
  13 +
  14 + <uni-list-item title="公司名称">
  15 + <template v-slot:footer>
  16 + <uni-easyinput v-model="companyNameDisplay" :inputBorder="false" disabled placeholder="-" />
  17 + </template>
  18 + </uni-list-item>
  19 +
  20 + <uni-list-item>
  21 + <template v-slot:body>
  22 + <view class="item-title"><text class="required">*</text><text>供应商名称</text></view>
  23 + </template>
  24 + <template v-slot:footer>
  25 + <uni-easyinput v-model="detail.supplierName" :inputBorder="false" placeholder="请输入供应商名称" />
  26 + </template>
  27 + </uni-list-item>
  28 +
  29 + <uni-list-item title="采购处">
  30 + <template v-slot:footer>
  31 + <uni-easyinput v-model="purchaseDepartmentDisplay" :inputBorder="false" disabled placeholder="-" />
  32 + </template>
  33 + </uni-list-item>
  34 +
  35 + <uni-list-item title="区域">
  36 + <template v-slot:footer>
  37 + <uni-easyinput v-model="regionDisplay" :inputBorder="false" disabled placeholder="-" />
  38 + </template>
  39 + </uni-list-item>
  40 +
  41 + <uni-list-item title="日期">
  42 + <template v-slot:footer>
  43 + <uni-easyinput v-model="dateDisplay" :inputBorder="false" disabled placeholder="-" />
  44 + </template>
  45 + </uni-list-item>
  46 +
  47 + <uni-list-item>
  48 + <template v-slot:body>
  49 + <view class="item-title"><text class="required">*</text><text>报价时效</text></view>
  50 + </template>
  51 + <template v-slot:footer>
  52 + <uni-easyinput v-model="detail.validityPeriod" :inputBorder="false" placeholder="请输入报价时效" />
  53 + </template>
  54 + </uni-list-item>
  55 +
  56 + <uni-list-item>
  57 + <template v-slot:body>
  58 + <view class="item-title"><text class="required">*</text><text>报价人</text></view>
  59 + </template>
  60 + <template v-slot:footer>
  61 + <uni-easyinput v-model="detail.quoterName" :inputBorder="false" placeholder="请输入报价人" />
  62 + </template>
  63 + </uni-list-item>
  64 +
  65 + <uni-list-item class="select-item" :class="typeof detail.longTermOperation === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  66 + @click="toggleBool('longTermOperation')" :rightText="boolText(detail.longTermOperation) || '请选择'" showArrow>
  67 + <template v-slot:body>
  68 + <view class="item-title"><text class="required">*</text><text>是否长单操作</text></view>
  69 + </template>
  70 + </uni-list-item>
  71 +
  72 + <uni-list-item class="select-item" :class="typeof detail.terminalCustomer === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  73 + @click="toggleBool('terminalCustomer')" :rightText="boolText(detail.terminalCustomer) || '请选择'" showArrow>
  74 + <template v-slot:body>
  75 + <view class="item-title"><text class="required">*</text><text>是否终端客户</text></view>
  76 + </template>
  77 + </uni-list-item>
  78 +
  79 + <uni-list-item class="select-item" :class="typeof detail.priced === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  80 + @click="toggleBool('priced')" :rightText="boolText(detail.priced) || '请选择'" showArrow>
  81 + <template v-slot:body>
  82 + <view class="item-title"><text class="required">*</text><text>是否未点价</text></view>
  83 + </template>
  84 + </uni-list-item>
  85 +
  86 + <uni-list-item v-if="detail.priced === true">
  87 + <template v-slot:body>
  88 + <view class="item-title"><text class="required">*</text><text>点价截止日期</text></view>
  89 + </template>
  90 + <template v-slot:footer>
  91 + <uni-datetime-picker type="date" v-model="detail.pricingDeadline" return-type="string" />
  92 + </template>
  93 + </uni-list-item>
  94 +
  95 + <uni-list-item v-if="detail.priced === true" class="select-item" :class="typeof detail.temporaryPrice === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  96 + @click="toggleBool('temporaryPrice')" :rightText="boolText(detail.temporaryPrice) || '请选择'" showArrow>
  97 + <template v-slot:body>
  98 + <view class="item-title"><text class="required">*</text><text>是否暂定价</text></view>
  99 + </template>
  100 + </uni-list-item>
  101 +
  102 + <uni-list-item class="select-item" :class="typeof detail.otherExpenseSettlement === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  103 + @click="toggleBool('otherExpenseSettlement')" :rightText="boolText(detail.otherExpenseSettlement) || '请选择'" showArrow>
  104 + <template v-slot:body>
  105 + <view class="item-title"><text class="required">*</text><text>其他费用凭票到厂另行结算</text></view>
  106 + </template>
  107 + </uni-list-item>
  108 +
  109 + <uni-list-item class="select-item" :class="detail.deliveryMode ? 'is-filled' : 'is-empty'" clickable
  110 + @click="openDeliverySheet" :rightText="getDeliveryMethodLabel()" showArrow>
  111 + <template v-slot:body>
  112 + <view class="item-title"><text class="required">*</text><text>交提货方式</text></view>
  113 + </template>
  114 + </uni-list-item>
  115 +
  116 + <uni-list-item class="select-item" :class="typeof detail.commodityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  117 + @click="toggleBool('commodityBuyout')" :rightText="boolText(detail.commodityBuyout) || '请选择'" showArrow>
  118 + <template v-slot:body>
  119 + <view class="item-title"><text class="required">*</text><text>是否通货买断</text></view>
  120 + </template>
  121 + </uni-list-item>
  122 +
  123 + <uni-list-item class="select-item" :class="typeof detail.qualityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  124 + @click="toggleBool('qualityBuyout')" :rightText="boolText(detail.qualityBuyout) || '请选择'" showArrow>
  125 + <template v-slot:body>
  126 + <view class="item-title"><text class="required">*</text><text>是否质量买断</text></view>
  127 + </template>
  128 + </uni-list-item>
  129 +
  130 + <uni-list-item class="select-item" :class="typeof detail.quantityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  131 + @click="toggleBool('quantityBuyout')" :rightText="boolText(detail.quantityBuyout) || '请选择'" showArrow>
  132 + <template v-slot:body>
  133 + <view class="item-title"><text class="required">*</text><text>是否数量买断</text></view>
  134 + </template>
  135 + </uni-list-item>
  136 +
  137 + <uni-list-item class="select-item" :class="typeof detail.doorDelivery === 'boolean' ? 'is-filled' : 'is-empty'" clickable
  138 + @click="toggleBool('doorDelivery')" :rightText="boolText(detail.doorDelivery) || '请选择'" showArrow>
  139 + <template v-slot:body>
  140 + <view class="item-title"><text class="required">*</text><text>是否送货上门</text></view>
  141 + </template>
  142 + </uni-list-item>
  143 +
  144 + <uni-list-item title="另付运费">
  145 + <template v-slot:footer>
  146 + <uni-easyinput v-model="shippingCostDisplay" :inputBorder="false" disabled placeholder="-" />
  147 + </template>
  148 + </uni-list-item>
  149 +
  150 + <uni-list-item>
  151 + <template v-slot:body>
  152 + <view class="item-title"><text class="required">*</text><text>装车费</text></view>
  153 + </template>
  154 + <template v-slot:footer>
  155 + <uni-easyinput
  156 + v-model="detail.loadingFee"
  157 + type="digit"
  158 + :inputBorder="false"
  159 + placeholder="请输入装车费"
  160 + @input="onNonNegativeInput('loadingFee')"
  161 + @blur="onNonNegativeBlur('loadingFee', 2)"
  162 + />
  163 + </template>
  164 + </uni-list-item>
  165 +
  166 + <uni-list-item>
  167 + <template v-slot:body>
  168 + <view class="item-title"><text class="required">*</text><text>开票品名</text></view>
  169 + </template>
  170 + <template v-slot:footer>
  171 + <uni-easyinput v-model="detail.invoiceProductName" :inputBorder="false" placeholder="请输入开票品名" />
  172 + </template>
  173 + </uni-list-item>
  174 +
  175 + <uni-list-item>
  176 + <template v-slot:body>
  177 + <view class="item-title"><text class="required">*</text><text>交货日期</text></view>
  178 + </template>
  179 + <template v-slot:footer>
  180 + <uni-datetime-picker type="date" v-model="detail.deliveryDate" return-type="string" />
  181 + </template>
  182 + </uni-list-item>
  183 +
  184 + <uni-list-item>
  185 + <template v-slot:body>
  186 + <view class="item-title"><text class="required">*</text><text>结算方式</text></view>
  187 + </template>
  188 + <template v-slot:footer>
  189 + <uni-easyinput v-model="detail.settlementMethod" :inputBorder="false" placeholder="请输入结算方式" />
  190 + </template>
  191 + </uni-list-item>
  192 +
  193 + <uni-list-item>
  194 + <template v-slot:body>
  195 + <view class="item-title"><text class="required">*</text><text>结算比例(%)</text></view>
  196 + </template>
  197 + <template v-slot:footer>
  198 + <uni-easyinput
  199 + v-model="detail.settlementRatio"
  200 + type="digit"
  201 + :inputBorder="false"
  202 + placeholder="请输入结算比例"
  203 + @input="onNonNegativeInput('settlementRatio')"
  204 + @blur="onNonNegativeBlur('settlementRatio', 4)"
  205 + />
  206 + </template>
  207 + </uni-list-item>
  208 +
  209 + <uni-list-item title="价格表编号">
  210 + <template v-slot:footer>
  211 + <uni-easyinput v-model="priceTableDisplay" :inputBorder="false" disabled placeholder="-" />
  212 + </template>
  213 + </uni-list-item>
  214 +
  215 + <uni-list-item>
  216 + <template v-slot:body>
  217 + <view class="item-title"><text class="required">*</text><text>协调事由</text></view>
  218 + </template>
  219 + <template v-slot:footer>
  220 + <uni-easyinput v-model="detail.coordinationReason" :inputBorder="false" placeholder="请输入协调事由" />
  221 + </template>
  222 + </uni-list-item>
  223 +
  224 + <uni-list-item>
  225 + <template v-slot:body>
  226 + <view class="item-title"><text class="required">*</text><text>票类状况</text></view>
  227 + </template>
  228 + <template v-slot:footer>
  229 + <uni-easyinput v-model="detail.invoiceTypeStatus" :inputBorder="false" placeholder="请输入票类状况" />
  230 + </template>
  231 + </uni-list-item>
  232 +
  233 + <uni-list-item v-if="attachmentList.length" title="附件">
  234 + <template v-slot:footer>
  235 + <view class="attach-list">
  236 + <view v-for="(at, idx) in attachmentList" :key="idx" class="attach-item">
  237 + <text class="attach-name">{{ at.name }}</text>
  238 + <text class="attach-view" @click.stop="onPreviewAttachment(at)">查看</text>
  239 + </view>
  240 + </view>
  241 + </template>
  242 + </uni-list-item>
  243 +
  244 + <ProductRel
  245 + ref="productRel"
  246 + mode="edit"
  247 + :list="lineList"
  248 + :product-options="productOptions"
  249 + :workshop-options="workshopOptions"
  250 + @change="onLineChange"
  251 + />
  252 + </uni-list>
  253 +
  254 + <view class="material-condition-section">
  255 + <view class="section-header">
  256 + <image class="opCollapse" src="/static/images/title.png" />
  257 + <text class="section-title">料质状况</text>
  258 + </view>
  259 + <view class="section-content">
  260 + <view class="form-row">
  261 + <text class="form-label">是否启用料质状况</text>
  262 + <text class="form-value toggle-link" @click="toggleBool('materialConditionEnabled')">{{ boolText(detail.materialConditionEnabled) || '请选择' }}</text>
  263 + </view>
  264 + <template v-if="detail.materialConditionEnabled">
  265 + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group">
  266 + <text class="material-label">{{ mc.label }}</text>
  267 + <view class="tag-list">
  268 + <view
  269 + v-for="opt in getDictOptions(mc.dictCode)"
  270 + :key="opt.code"
  271 + class="tag-btn"
  272 + :class="{ active: isTagSelected(mc.field, opt.code) }"
  273 + @click="toggleTag(mc.field, opt.code)"
  274 + >
  275 + {{ opt.name }}
  276 + </view>
  277 + </view>
  278 + </view>
  279 + </template>
  280 + </view>
  281 + </view>
  282 +
  283 + <view class="remark-section">
  284 + <view class="section-header">
  285 + <image class="opCollapse" src="/static/images/title.png" />
  286 + <text class="section-title">备注</text>
  287 + </view>
  288 + <view class="section-content">
  289 + <uni-easyinput type="textarea" v-model="detail.remark" :inputBorder="false" placeholder="请输入备注" />
  290 + </view>
  291 + </view>
  292 +
  293 + <view class="remark-section">
  294 + <view class="section-header">
  295 + <image class="opCollapse" src="/static/images/title.png" />
  296 + <text class="section-title"><text class="required">*</text>变更事由</text>
  297 + </view>
  298 + <view class="section-content">
  299 + <uni-easyinput type="textarea" v-model="detail.changeReason" :inputBorder="false" placeholder="请输入变更事由" />
  300 + </view>
  301 + </view>
  302 + </scroll-view>
  303 +
  304 + <view class="bottom-bar">
  305 + <button class="btn btn-outline" @click="onBack">取消</button>
  306 + <button class="btn btn-primary" @click="onSubmit">保存</button>
  307 + </view>
  308 +
  309 + <SingleSelectSheet
  310 + :visible.sync="deliverySheetVisible"
  311 + title="交提货方式"
  312 + :options="deliveryOptions"
  313 + v-model="deliverySheetValue"
  314 + @confirm="onDeliveryConfirm"
  315 + />
  316 + </view>
  317 +</template>
  318 +
  319 +<script>
  320 +import ProductRel from './productRel.vue'
  321 +import SingleSelectSheet from '@/components/single-select/index.vue'
  322 +import { procurementPlanChangeFindByIdApi, procurementPlanChangeUpdateApi } from '@/api/procure-manage/procurementPlan.js'
  323 +import {
  324 + breedRelationshipQueryApi,
  325 + workshopQueryApi
  326 +} from '@/api/devManage.js'
  327 +import { getDicByCodes } from '@/utils/dic.js'
  328 +
  329 +export default {
  330 + name: 'SchedulePlanModify',
  331 + components: { ProductRel, SingleSelectSheet },
  332 + data() {
  333 + return {
  334 + id: '',
  335 + form: {
  336 + planId: '',
  337 + planCode: ''
  338 + },
  339 + detail: {},
  340 + attachmentList: [],
  341 + materialConditionConfigs: [
  342 + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' },
  343 + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' },
  344 + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' },
  345 + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' },
  346 + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' },
  347 + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' },
  348 + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' },
  349 + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' }
  350 + ],
  351 + dicOptions: {
  352 + DELIVERY_METHOD: []
  353 + },
  354 + dictData: {},
  355 + lineList: [],
  356 + productOptions: [],
  357 + workshopOptions: [],
  358 + deliverySheetVisible: false,
  359 + deliverySheetValue: '',
  360 + deliveryOptions: []
  361 + }
  362 + },
  363 + computed: {
  364 + companyNameDisplay() {
  365 + var d = this.detail || {}
  366 + return d.companyShortName != null ? d.companyShortName : (d.companyName != null ? d.companyName : '')
  367 + },
  368 + purchaseDepartmentDisplay() {
  369 + var d = this.detail || {}
  370 + return d.purchaseDepartmentName != null ? d.purchaseDepartmentName : (d.purchaseDepartment != null ? d.purchaseDepartment : '')
  371 + },
  372 + regionDisplay() {
  373 + var d = this.detail || {}
  374 + return d.regionName != null ? d.regionName : (d.region != null ? d.region : '')
  375 + },
  376 + dateDisplay() {
  377 + var d = this.detail || {}
  378 + var s = d.date == null ? '' : String(d.date).trim()
  379 + return s.length >= 10 ? s.slice(0, 10) : s
  380 + },
  381 + shippingCostDisplay() {
  382 + var v = (this.detail || {}).shippingCost
  383 + if (v == null || v === '') return ''
  384 + var n = Number(v)
  385 + if (isNaN(n)) return String(v)
  386 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  387 + },
  388 + priceTableDisplay() {
  389 + var d = this.detail || {}
  390 + var code = d.priceTableCode != null ? d.priceTableCode : (d.priceId != null ? d.priceId : '')
  391 + var name = d.priceTableName || ''
  392 + return name ? (name + (code ? ' (' + code + ')' : '')) : code
  393 + }
  394 + },
  395 + onLoad(query) {
  396 + this.id = (query && query.id) || ''
  397 + this.loadDictData()
  398 + this.loadRefData()
  399 + if (this.id) {
  400 + this.loadDetailById(this.id)
  401 + }
  402 + },
  403 + methods: {
  404 + safeText(v) {
  405 + var s = v == null ? '' : String(v)
  406 + return s.trim() ? s : '-'
  407 + },
  408 + boolText(v) {
  409 + if (typeof v !== 'boolean') return ''
  410 + return v ? '是' : '否'
  411 + },
  412 + toNumber(v) {
  413 + if (v == null || v === '') return 0
  414 + var n = Number(v)
  415 + return isNaN(n) ? 0 : n
  416 + },
  417 + toFixed(v, n) {
  418 + var num = Number(v)
  419 + if (isNaN(num)) return ''
  420 + return num.toFixed(n)
  421 + },
  422 + async loadDictData() {
  423 + var codes = [].concat(
  424 + this.materialConditionConfigs.map(function (mc) { return mc.dictCode }),
  425 + ['DELIVERY_METHOD']
  426 + )
  427 + try {
  428 + var results = await getDicByCodes(codes)
  429 + var d = results || {}
  430 + this.dictData = d
  431 + this.dicOptions.DELIVERY_METHOD = (d.DELIVERY_METHOD && d.DELIVERY_METHOD.data) || []
  432 + this.deliveryOptions = this.dicOptions.DELIVERY_METHOD.map(function (o) {
  433 + return { label: o.name || o.label || '', value: o.code != null ? o.code : o.value }
  434 + })
  435 + } catch (e) {
  436 + this.dictData = {}
  437 + }
  438 + },
  439 + async loadRefData() {
  440 + try {
  441 + var results = await Promise.all([
  442 + this.$pCall(breedRelationshipQueryApi, { pageIndex: 1, pageSize: 1000 }),
  443 + this.$pCall(workshopQueryApi, { pageIndex: 1, pageSize: 1000 })
  444 + ])
  445 + var r1 = (results[0] && results[0].data) ? results[0].data : {}
  446 + var r2 = (results[1] && results[1].data) ? results[1].data : {}
  447 + this.productOptions = (r1.datas && Array.isArray(r1.datas)) ? r1.datas : ((r1.list && Array.isArray(r1.list)) ? r1.list : ((r1.records && Array.isArray(r1.records)) ? r1.records : []))
  448 + this.workshopOptions = (r2.datas && Array.isArray(r2.datas)) ? r2.datas : ((r2.list && Array.isArray(r2.list)) ? r2.list : ((r2.records && Array.isArray(r2.records)) ? r2.records : []))
  449 + this.fillLineNames()
  450 + } catch (e) {
  451 + this.productOptions = []
  452 + this.workshopOptions = []
  453 + }
  454 + },
  455 + $pCall(fn, params) {
  456 + try { return Promise.resolve(fn(params)) } catch (e) { return Promise.reject(e) }
  457 + },
  458 + getDictOptions(dictCode) {
  459 + var data = this.dictData[dictCode]
  460 + return (data && data.data) ? data.data : []
  461 + },
  462 + getDeliveryMethodLabel() {
  463 + var opts = this.dicOptions.DELIVERY_METHOD || []
  464 + var v = String(this.detail.deliveryMode != null ? this.detail.deliveryMode : '')
  465 + if (!v) return '请选择'
  466 + for (var i = 0; i < opts.length; i++) {
  467 + var o = opts[i]
  468 + var code = o.code != null ? String(o.code) : (o.value != null ? String(o.value) : '')
  469 + if (code === v) return o.name || o.label || v
  470 + }
  471 + return v
  472 + },
  473 + async loadDetailById(id) {
  474 + if (!id) return
  475 + try {
  476 + var res = await procurementPlanChangeFindByIdApi({ id: id })
  477 + var d = (res && res.data) ? res.data : {}
  478 + var boolFields = ['longTermOperation', 'terminalCustomer', 'priced', 'temporaryPrice', 'otherExpenseSettlement', 'commodityBuyout', 'qualityBuyout', 'quantityBuyout', 'doorDelivery', 'materialConditionEnabled']
  479 + boolFields.forEach(function (f) {
  480 + var v = d[f]
  481 + if (typeof v === 'string') {
  482 + d[f] = v === 'true' || v === '1'
  483 + } else if (typeof v === 'number') {
  484 + d[f] = v === 1
  485 + } else {
  486 + d[f] = !!v
  487 + }
  488 + })
  489 + this.materialConditionConfigs.forEach(function (mc) {
  490 + var v = d[mc.field]
  491 + if (typeof v === 'string' && v) {
  492 + d[mc.field] = v.split(',').filter(Boolean)
  493 + } else if (!Array.isArray(v)) {
  494 + d[mc.field] = []
  495 + }
  496 + })
  497 + d.pricingDeadline = this.trimDate(d.pricingDeadline)
  498 + d.deliveryDate = this.trimDate(d.deliveryDate)
  499 + this.detail = d
  500 + this.form.planId = d.procurementPlanId != null ? d.procurementPlanId : (d.planId != null ? d.planId : (d.id || ''))
  501 + this.form.planCode = d.procurementPlanCode != null ? d.procurementPlanCode : (d.planCode != null ? d.planCode : d.code)
  502 + var lines = d.lines || d.procurementPlanLineList || d.lineList || []
  503 + this.lineList = Array.isArray(lines) ? lines.slice() : []
  504 + this.buildAttachmentList()
  505 + this.fillLineNames()
  506 + } catch (e) {
  507 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  508 + }
  509 + },
  510 + trimDate(v) {
  511 + if (v == null || v === '') return ''
  512 + var s = String(v).trim()
  513 + return s.length >= 10 ? s.slice(0, 10) : s
  514 + },
  515 + buildAttachmentList() {
  516 + var ids = this.detail.attachmentFileIds
  517 + var names = this.detail.attachmentFileNames
  518 + var idArr = (typeof ids === 'string' && ids) ? ids.split(',').filter(Boolean) : (Array.isArray(ids) ? ids : [])
  519 + var nameArr = (typeof names === 'string' && names) ? names.split(',').filter(Boolean) : (Array.isArray(names) ? names : [])
  520 + var len = Math.max(idArr.length, nameArr.length)
  521 + var list = []
  522 + for (var i = 0; i < len; i++) {
  523 + list.push({ id: idArr[i] || '', name: nameArr[i] || ('附件' + (i + 1)) })
  524 + }
  525 + this.attachmentList = list
  526 + },
  527 + onPreviewAttachment(at) {
  528 + if (!at || !at.id) {
  529 + uni.showToast({ title: '无法查看', icon: 'none' })
  530 + return
  531 + }
  532 + uni.showToast({ title: '查看附件 ' + at.name, icon: 'none' })
  533 + },
  534 + fillLineNames() {
  535 + var list = Array.isArray(this.lineList) ? this.lineList : []
  536 + if (!list.length) return
  537 + var pOpts = Array.isArray(this.productOptions) ? this.productOptions : []
  538 + var wOpts = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  539 + this.lineList = list.map(function (row) {
  540 + var r = Object.assign({}, row)
  541 + var pid = r.productId != null ? String(r.productId) : ''
  542 + var wid = r.workshopId != null ? String(r.workshopId) : ''
  543 + if ((r.productName == null || String(r.productName).trim() === '') && pid) {
  544 + var p = pOpts.find(function (o) {
  545 + var id = o.id != null ? String(o.id) : (o.productId != null ? String(o.productId) : (o.value != null ? String(o.value) : ''))
  546 + return id === pid
  547 + })
  548 + if (p) {
  549 + r.productName = p.name != null ? p.name : (p.productName != null ? p.productName : (p.label != null ? p.label : ''))
  550 + }
  551 + }
  552 + if ((r.workshopName == null || String(r.workshopName).trim() === '') && wid) {
  553 + var w = wOpts.find(function (o) {
  554 + var id = o.id != null ? String(o.id) : (o.workshopId != null ? String(o.workshopId) : (o.value != null ? String(o.value) : ''))
  555 + return id === wid
  556 + })
  557 + if (w) {
  558 + r.workshopName = w.name != null ? w.name : (w.workshopName != null ? w.workshopName : (w.label != null ? w.label : ''))
  559 + }
  560 + }
  561 + return r
  562 + })
  563 + },
  564 + onLineChange(list) {
  565 + this.lineList = Array.isArray(list) ? list.slice() : []
  566 + },
  567 + toggleBool(field) {
  568 + var cur = this.detail[field]
  569 + this.$set(this.detail, field, cur !== true)
  570 + },
  571 + onNonNegativeInput(field) {
  572 + var v = String(this.detail[field] != null ? this.detail[field] : '')
  573 + v = v.replace(/[^0-9.]/g, '')
  574 + v = v.replace(/(\..*)\./g, '$1')
  575 + this.$set(this.detail, field, v)
  576 + },
  577 + onNonNegativeBlur(field, digits) {
  578 + var raw = this.detail[field]
  579 + if (raw === '' || raw === null || raw === undefined) {
  580 + this.$set(this.detail, field, '')
  581 + return
  582 + }
  583 + var val = parseFloat(raw)
  584 + if (isNaN(val)) val = 0
  585 + var m = Math.pow(10, digits)
  586 + var rounded = Math.round(val * m) / m
  587 + this.$set(this.detail, field, rounded)
  588 + },
  589 + openDeliverySheet() {
  590 + var cur = this.detail.deliveryMode != null ? this.detail.deliveryMode : ''
  591 + var match = this.deliveryOptions.find(function (o) { return String(o.value) === String(cur) })
  592 + this.deliverySheetValue = match ? match.value : ''
  593 + this.deliverySheetVisible = true
  594 + },
  595 + onDeliveryConfirm(e) {
  596 + this.$set(this.detail, 'deliveryMode', e.value != null ? e.value : '')
  597 + },
  598 + isTagSelected(field, code) {
  599 + var selected = this.detail ? this.detail[field] : undefined
  600 + if (Array.isArray(selected)) return selected.indexOf(code) >= 0
  601 + if (typeof selected === 'string' && selected) {
  602 + return selected.split(',').indexOf(code) >= 0
  603 + }
  604 + return false
  605 + },
  606 + toggleTag(field, code) {
  607 + var selected = this.detail[field]
  608 + var arr = Array.isArray(selected) ? selected.slice() : []
  609 + var idx = arr.indexOf(code)
  610 + if (idx >= 0) {
  611 + arr.splice(idx, 1)
  612 + } else {
  613 + arr.push(code)
  614 + }
  615 + this.$set(this.detail, field, arr)
  616 + },
  617 + validateForm() {
  618 + if (!this.form.planId) {
  619 + uni.showToast({ title: '请选择计划', icon: 'none' })
  620 + return false
  621 + }
  622 + var d = this.detail || {}
  623 + var textChecks = [
  624 + { key: 'supplierName', title: '供应商名称' },
  625 + { key: 'validityPeriod', title: '报价时效' },
  626 + { key: 'quoterName', title: '报价人' },
  627 + { key: 'deliveryMode', title: '交提货方式' },
  628 + { key: 'loadingFee', title: '装车费' },
  629 + { key: 'invoiceProductName', title: '开票品名' },
  630 + { key: 'deliveryDate', title: '交货日期' },
  631 + { key: 'settlementMethod', title: '结算方式' },
  632 + { key: 'settlementRatio', title: '结算比例' },
  633 + { key: 'coordinationReason', title: '协调事由' },
  634 + { key: 'invoiceTypeStatus', title: '票类状况' },
  635 + { key: 'changeReason', title: '变更事由' }
  636 + ]
  637 + for (var i = 0; i < textChecks.length; i++) {
  638 + var tc = textChecks[i]
  639 + var tv = d[tc.key]
  640 + if (tv === '' || tv === null || tv === undefined) {
  641 + uni.showToast({ title: '请填写' + tc.title, icon: 'none' })
  642 + return false
  643 + }
  644 + }
  645 + var boolChecks = [
  646 + { key: 'longTermOperation', title: '是否长单操作' },
  647 + { key: 'terminalCustomer', title: '是否终端客户' },
  648 + { key: 'priced', title: '是否未点价' },
  649 + { key: 'otherExpenseSettlement', title: '其他费用凭票到厂另行结算' },
  650 + { key: 'commodityBuyout', title: '是否通货买断' },
  651 + { key: 'qualityBuyout', title: '是否质量买断' },
  652 + { key: 'quantityBuyout', title: '是否数量买断' },
  653 + { key: 'doorDelivery', title: '是否送货上门' }
  654 + ]
  655 + if (d.priced === true) {
  656 + boolChecks.push({ key: 'temporaryPrice', title: '是否暂定价' })
  657 + var pd = d.pricingDeadline
  658 + if (pd === '' || pd === null || pd === undefined) {
  659 + uni.showToast({ title: '请选择点价截止日期', icon: 'none' })
  660 + return false
  661 + }
  662 + }
  663 + for (var j = 0; j < boolChecks.length; j++) {
  664 + var bc = boolChecks[j]
  665 + if (typeof d[bc.key] !== 'boolean') {
  666 + uni.showToast({ title: '请选择' + bc.title, icon: 'none' })
  667 + return false
  668 + }
  669 + }
  670 + var pr = this.$refs.productRel
  671 + if (pr && typeof pr.validate === 'function') {
  672 + if (!pr.validate()) return false
  673 + }
  674 + return true
  675 + },
  676 + onBack() {
  677 + uni.navigateBack({ delta: 1 })
  678 + },
  679 + joinStr(arr) {
  680 + if (Array.isArray(arr)) return arr.filter(Boolean).join(',')
  681 + if (typeof arr === 'string') return arr
  682 + return ''
  683 + },
  684 + pickId(v) {
  685 + if (v == null || v === '') return ''
  686 + if (typeof v === 'string' || typeof v === 'number') return String(v)
  687 + if (typeof v === 'object') {
  688 + return String(v.id != null ? v.id : (v.value != null ? v.value : (v.code != null ? v.code : '')))
  689 + }
  690 + return String(v)
  691 + },
  692 + boolToStr(v) {
  693 + return v === true ? 'true' : 'false'
  694 + },
  695 + buildPayload() {
  696 + var self = this
  697 + var d = this.detail || {}
  698 + var lines = Array.isArray(this.lineList) ? this.lineList.slice() : []
  699 + var workshopId = d.workshopId != null ? d.workshopId : ''
  700 + var lineList = lines.map(function (row, i) {
  701 + var r = row || {}
  702 + var itemId = r.itemId != null ? r.itemId : (r.id != null ? String(r.id) : (r._key != null ? r._key : ('line-' + Date.now() + '-' + i)))
  703 + if (!workshopId && r.workshopId != null) workshopId = r.workshopId
  704 + return {
  705 + productId: self.pickId(r.productId),
  706 + materialType: r.materialType != null ? String(r.materialType) : '',
  707 + quantity: r.quantity != null ? String(r.quantity) : '',
  708 + actualPurchasePrice: r.actualPurchasePrice != null ? String(r.actualPurchasePrice) : '',
  709 + includingShippingPrice: r.includingShippingPrice != null ? String(r.includingShippingPrice) : '',
  710 + priceListPrices: r.priceListPrices != null ? String(r.priceListPrices) : '',
  711 + coordinationRange: r.coordinationRange != null ? String(r.coordinationRange) : '',
  712 + estimatedLoss: r.estimatedLoss != null ? String(r.estimatedLoss) : '',
  713 + applicationAttrition: r.applicationAttrition != null ? String(r.applicationAttrition) : '',
  714 + paintDamageRatio: r.paintDamageRatio != null ? String(r.paintDamageRatio) : '',
  715 + workshopId: self.pickId(r.workshopId),
  716 + itemId: itemId
  717 + }
  718 + })
  719 + var body = {
  720 + id: this.id || d.id || '',
  721 + procurementPlanId: this.form.planId || d.id || '',
  722 + changeReason: d.changeReason != null ? String(d.changeReason) : '',
  723 + code: d.code != null ? String(d.code) : '',
  724 + companyShortId: this.pickId(d.companyShortId || d.companyId),
  725 + supplierId: this.pickId(d.supplierId),
  726 + purchaseDepartment: this.pickId(d.purchaseDepartment),
  727 + region: this.pickId(d.region),
  728 + date: d.date != null ? String(d.date).slice(0, 10) : '',
  729 + validityPeriod: d.validityPeriod != null ? String(d.validityPeriod) : '',
  730 + quoterId: this.pickId(d.quoterId),
  731 + workshopId: this.pickId(workshopId),
  732 + quoterName: d.quoterName != null ? String(d.quoterName) : '',
  733 + longTermOperation: this.boolToStr(d.longTermOperation),
  734 + terminalCustomer: this.boolToStr(d.terminalCustomer),
  735 + priced: this.boolToStr(d.priced),
  736 + pricingDeadline: d.pricingDeadline != null ? String(d.pricingDeadline) : '',
  737 + temporaryPrice: this.boolToStr(d.temporaryPrice),
  738 + otherExpenseSettlement: this.boolToStr(d.otherExpenseSettlement),
  739 + deliveryMode: d.deliveryMode != null ? String(d.deliveryMode) : '',
  740 + commodityBuyout: this.boolToStr(d.commodityBuyout),
  741 + qualityBuyout: this.boolToStr(d.qualityBuyout),
  742 + quantityBuyout: this.boolToStr(d.quantityBuyout),
  743 + doorDelivery: this.boolToStr(d.doorDelivery),
  744 + shippingCost: d.shippingCost != null ? String(d.shippingCost) : '',
  745 + loadingFee: d.loadingFee != null ? String(d.loadingFee) : '',
  746 + invoiceProductName: d.invoiceProductName != null ? String(d.invoiceProductName) : '',
  747 + deliveryDate: d.deliveryDate != null ? String(d.deliveryDate) : '',
  748 + settlementMethod: d.settlementMethod != null ? String(d.settlementMethod) : '',
  749 + settlementRatio: d.settlementRatio != null ? String(d.settlementRatio) : '',
  750 + priceId: this.pickId(d.priceId),
  751 + coordinationReason: d.coordinationReason != null ? String(d.coordinationReason) : '',
  752 + invoiceTypeStatus: d.invoiceTypeStatus != null ? String(d.invoiceTypeStatus) : '',
  753 + totalQuantity: d.totalQuantity != null ? String(d.totalQuantity) : '',
  754 + remark: d.remark != null ? String(d.remark) : '',
  755 + materialConditionEnabled: this.boolToStr(d.materialConditionEnabled),
  756 + lines: lineList
  757 + }
  758 + this.materialConditionConfigs.forEach(function (mc) {
  759 + body[mc.field] = self.joinStr(d[mc.field])
  760 + })
  761 + return body
  762 + },
  763 + async onSubmit() {
  764 + if (!this.validateForm()) return
  765 + var payload = this.buildPayload()
  766 + try {
  767 + await procurementPlanChangeUpdateApi(payload)
  768 + uni.showToast({ title: '保存成功', icon: 'none' })
  769 + try {
  770 + uni.setStorageSync('SCHEDULE_PLAN_LIST_NEED_REFRESH', '1')
  771 + } catch (e) {}
  772 + var self = this
  773 + setTimeout(function () {
  774 + uni.navigateTo({ url: '/pages/schedule-plan/index' })
  775 + }, 400)
  776 + } catch (e) {
  777 + uni.showToast({ title: (e && (e.msg || e.message)) ? (e.msg || e.message) : '保存失败', icon: 'none' })
  778 + }
  779 + }
  780 + }
  781 +}
  782 +</script>
  783 +
  784 +<style lang="scss" scoped>
  785 +.page {
  786 + display: flex;
  787 + flex-direction: column;
  788 + height: 100%;
  789 +}
  790 +.scroll {
  791 + flex: 1;
  792 + padding: 12rpx 0 392rpx !important;
  793 +}
  794 +.bottom-bar {
  795 + z-index: 2;
  796 + position: fixed;
  797 + left: 0;
  798 + right: 0;
  799 + bottom: 0;
  800 + padding: 32rpx;
  801 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  802 + background: #fff;
  803 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  804 + display: flex;
  805 + gap: 24rpx;
  806 + .btn {
  807 + flex: 1;
  808 + height: 80rpx;
  809 + line-height: 80rpx;
  810 + border-radius: 12rpx;
  811 + font-size: 32rpx;
  812 + }
  813 + .btn-outline {
  814 + background: #fff;
  815 + color: $theme-primary;
  816 + border: 2rpx solid $theme-primary;
  817 + }
  818 + .btn-primary {
  819 + background: $theme-primary;
  820 + color: #fff;
  821 + border: 2rpx solid $theme-primary;
  822 + }
  823 +}
  824 +
  825 +::v-deep .uni-list {
  826 + .uni-easyinput {
  827 + display: flex;
  828 + .uni-input-input {
  829 + color: rgba(0, 0, 0, 0.9);
  830 + }
  831 + }
  832 + .uni-input-placeholder { z-index: 1; }
  833 + .uni-input-input { background-color: #ffffff; }
  834 + background: transparent;
  835 + &-item {
  836 + &__extra-text { font-size: 32rpx; }
  837 + &__content-title {
  838 + font-size: 32rpx;
  839 + color: rgba(0, 0, 0, 0.9);
  840 + }
  841 + &__container {
  842 + padding: 32rpx;
  843 + .uni-easyinput {
  844 + &__placeholder-class {
  845 + font-size: 32rpx;
  846 + color: rgba(0, 0, 0, 0.4);
  847 + }
  848 + &__content {
  849 + border: none;
  850 + background-color: #ffffff !important;
  851 + &-input {
  852 + padding-left: 0 !important;
  853 + height: 48rpx;
  854 + line-height: 48rpx;
  855 + font-size: 32rpx;
  856 + }
  857 + .content-clear-icon {
  858 + font-size: 44rpx !important;
  859 + }
  860 + }
  861 + }
  862 + .item-title,
  863 + .uni-list-item__content {
  864 + flex: none;
  865 + min-height: 48rpx;
  866 + line-height: 48rpx;
  867 + font-size: 32rpx;
  868 + position: relative;
  869 + width: 240rpx;
  870 + margin-right: 32rpx;
  871 + color: rgba(0, 0, 0, 0.9);
  872 + .required {
  873 + color: red;
  874 + position: absolute;
  875 + top: 50%;
  876 + transform: translateY(-50%);
  877 + left: -16rpx;
  878 + }
  879 + }
  880 + }
  881 + &.select-item {
  882 + &.is-empty {
  883 + .uni-list-item__extra-text {
  884 + color: rgba(0, 0, 0, 0.4) !important;
  885 + }
  886 + }
  887 + &.is-filled {
  888 + .uni-list-item__extra-text {
  889 + color: rgba(0, 0, 0, 0.9) !important;
  890 + }
  891 + }
  892 + .serial-number-row {
  893 + display: flex;
  894 + align-items: center;
  895 + }
  896 + }
  897 + }
  898 +}
  899 +
  900 +.readonly-text {
  901 + color: rgba(0, 0, 0, 0.9);
  902 + font-size: 32rpx;
  903 + line-height: 48rpx;
  904 + text-align: right;
  905 + white-space: pre-wrap;
  906 + word-break: break-all;
  907 +}
  908 +.value-code {
  909 + width: 260rpx;
  910 + text-align: left;
  911 +}
  912 +
  913 +.attach-list {
  914 + flex: 1;
  915 + display: flex;
  916 + flex-direction: column;
  917 + align-items: flex-end;
  918 + gap: 8rpx;
  919 +}
  920 +.attach-item {
  921 + display: flex;
  922 + align-items: center;
  923 + gap: 16rpx;
  924 + .attach-name { font-size: 26rpx; color: rgba(0,0,0,0.9); }
  925 + .attach-view { font-size: 26rpx; color: $theme-primary; }
  926 +}
  927 +
  928 +.material-condition-section,
  929 +.remark-section {
  930 + margin-top: 20rpx;
  931 + background: #fff;
  932 +}
  933 +.section-header {
  934 + display: flex;
  935 + align-items: center;
  936 + padding: 24rpx 32rpx;
  937 + border-bottom: 1rpx solid #f0f0f0;
  938 +}
  939 +.section-title {
  940 + position: relative;
  941 + font-size: 32rpx;
  942 + color: rgba(0, 0, 0, 0.9);
  943 + font-weight: 600;
  944 + .required {
  945 + color: red;
  946 + position: absolute;
  947 + top: 50%;
  948 + transform: translateY(-50%);
  949 + left: -16rpx;
  950 + }
  951 +}
  952 +.opCollapse {
  953 + width: 32rpx;
  954 + height: 28rpx;
  955 + margin-right: 16rpx;
  956 + margin-top: 8rpx;
  957 +}
  958 +.section-content {
  959 + padding: 32rpx;
  960 +}
  961 +.form-row {
  962 + display: flex;
  963 + align-items: center;
  964 + justify-content: space-between;
  965 + margin-bottom: 24rpx;
  966 +}
  967 +.form-row:last-child { margin-bottom: 0; }
  968 +.form-label {
  969 + font-size: 32rpx;
  970 + color: rgba(0, 0, 0, 0.9);
  971 +}
  972 +.form-value {
  973 + font-size: 32rpx;
  974 + color: rgba(0, 0, 0, 0.9);
  975 +}
  976 +.toggle-link {
  977 + color: $theme-primary;
  978 +}
  979 +.material-group {
  980 + margin-top: 32rpx;
  981 +}
  982 +.material-label {
  983 + display: block;
  984 + font-size: 30rpx;
  985 + color: rgba(0, 0, 0, 0.9);
  986 + margin-bottom: 20rpx;
  987 +}
  988 +.tag-list {
  989 + display: flex;
  990 + flex-wrap: wrap;
  991 + gap: 16rpx;
  992 +}
  993 +.tag-btn {
  994 + padding: 12rpx 24rpx;
  995 + border: 1rpx solid #ddd;
  996 + border-radius: 8rpx;
  997 + font-size: 28rpx;
  998 + color: #666;
  999 + background: #fff;
  1000 +}
  1001 +.tag-btn.active {
  1002 + background: $theme-primary;
  1003 + border-color: $theme-primary;
  1004 + color: #fff;
  1005 +}
  1006 +
  1007 +::v-deep .uni-easyinput {
  1008 + width: 100%;
  1009 + &__content {
  1010 + background: #f8f8f8 !important;
  1011 + border-radius: 8rpx;
  1012 + &-input { font-size: 30rpx; }
  1013 + }
  1014 +}
  1015 +</style>
  1 +<template>
  2 + <view class="product-rel">
  3 + <view class="header">
  4 + <image class="opCollapse" src="/static/images/title.png" />
  5 + <text class="title">明细信息</text>
  6 + <view class="ops">
  7 + <image class="opAdd" @click="onAdd" src="/static/images/plus.png" />
  8 + </view>
  9 + </view>
  10 +
  11 + <view class="add-list">
  12 + <view v-for="(item, idx) in items" :key="item._key || idx" class="block">
  13 + <uni-list v-show="!item.collapsed">
  14 + <uni-list-item
  15 + class="select-item"
  16 + :class="item.productName ? 'is-filled' : 'is-empty'"
  17 + clickable
  18 + @click="openSelectSheet('product', idx)"
  19 + :rightText="item.productName || '请选择品种'"
  20 + showArrow
  21 + >
  22 + <template v-slot:body>
  23 + <view class="item-title">
  24 + <text class="required">*</text><text>品种</text>
  25 + </view>
  26 + </template>
  27 + </uni-list-item>
  28 + <uni-list-item>
  29 + <template v-slot:body>
  30 + <view class="item-title">
  31 + <text class="required">*</text><text>料型</text>
  32 + </view>
  33 + </template>
  34 + <template v-slot:footer>
  35 + <uni-easyinput v-model="item.materialType" :inputBorder="false" placeholder="请输入料型" />
  36 + </template>
  37 + </uni-list-item>
  38 + <uni-list-item>
  39 + <template v-slot:body>
  40 + <view class="item-title">
  41 + <text class="required">*</text><text>数量(吨)</text>
  42 + </view>
  43 + </template>
  44 + <template v-slot:footer>
  45 + <uni-easyinput
  46 + v-model="item.quantity"
  47 + type="digit"
  48 + :inputBorder="false"
  49 + placeholder="请输入数量"
  50 + @input="onNonNegativeInput(idx, 'quantity')"
  51 + @blur="onNonNegativeBlur(idx, 'quantity', 4)"
  52 + />
  53 + </template>
  54 + </uni-list-item>
  55 + <uni-list-item>
  56 + <template v-slot:body>
  57 + <view class="item-title">
  58 + <text class="required">*</text><text>实际采购价</text>
  59 + </view>
  60 + </template>
  61 + <template v-slot:footer>
  62 + <uni-easyinput
  63 + v-model="item.actualPurchasePrice"
  64 + type="digit"
  65 + :inputBorder="false"
  66 + placeholder="请输入实际采购价"
  67 + @input="onNonNegativeInput(idx, 'actualPurchasePrice')"
  68 + @blur="onNonNegativeBlur(idx, 'actualPurchasePrice', 2)"
  69 + />
  70 + </template>
  71 + </uni-list-item>
  72 + <uni-list-item title="含运到厂价">
  73 + <template v-slot:footer>
  74 + <text class="lock-value">{{ formatTextNumber(item.includingShippingPrice) }}</text>
  75 + </template>
  76 + </uni-list-item>
  77 + <uni-list-item title="价格表价格">
  78 + <template v-slot:footer>
  79 + <text class="lock-value">{{ formatTextNumber(item.priceListPrices) }}</text>
  80 + </template>
  81 + </uni-list-item>
  82 + <uni-list-item title="协调幅度">
  83 + <template v-slot:footer>
  84 + <text class="lock-value">{{ formatTextNumber(item.coordinationRange) }}</text>
  85 + </template>
  86 + </uni-list-item>
  87 + <uni-list-item>
  88 + <template v-slot:body>
  89 + <view class="item-title">
  90 + <text class="required">*</text><text>买断料预计损耗</text>
  91 + </view>
  92 + </template>
  93 + <template v-slot:footer>
  94 + <uni-easyinput
  95 + v-model="item.estimatedLoss"
  96 + type="digit"
  97 + :inputBorder="false"
  98 + placeholder="请输入预计损耗"
  99 + @input="onNonNegativeInput(idx, 'estimatedLoss')"
  100 + @blur="onNonNegativeBlur(idx, 'estimatedLoss', 2)"
  101 + />
  102 + </template>
  103 + </uni-list-item>
  104 + <uni-list-item>
  105 + <template v-slot:body>
  106 + <view class="item-title">
  107 + <text class="required">*</text><text>申请损耗(%)</text>
  108 + </view>
  109 + </template>
  110 + <template v-slot:footer>
  111 + <uni-easyinput
  112 + v-model="item.applicationAttrition"
  113 + type="digit"
  114 + :inputBorder="false"
  115 + placeholder="请输入申请损耗"
  116 + @input="onNonNegativeInput(idx, 'applicationAttrition')"
  117 + @blur="onNonNegativeBlur(idx, 'applicationAttrition', 2)"
  118 + />
  119 + </template>
  120 + </uni-list-item>
  121 + <uni-list-item title="选择分厂">
  122 + <template v-slot:footer>
  123 + <text class="lock-value">{{ safeText(item.workshopName) }}</text>
  124 + </template>
  125 + </uni-list-item>
  126 + </uni-list>
  127 + <uni-list v-show="item.collapsed">
  128 + <uni-list-item
  129 + class="select-item"
  130 + :class="item.productName ? 'is-filled' : 'is-empty'"
  131 + clickable
  132 + @click="openSelectSheet('product', idx)"
  133 + :rightText="item.productName || '请选择品种'"
  134 + showArrow
  135 + >
  136 + <template v-slot:body>
  137 + <view class="item-title">
  138 + <text class="required">*</text><text>品种</text>
  139 + </view>
  140 + </template>
  141 + </uni-list-item>
  142 + </uni-list>
  143 + <view class="block-ops">
  144 + <div class="del" @click="onRemove(idx)">
  145 + <image src="/static/images/delete.png" class="icon" />
  146 + 删除
  147 + </div>
  148 + <div class="toggle" @click="toggleItem(idx)">
  149 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'" class="icon" />
  150 + {{ item.collapsed ? '展开' : '收起' }}
  151 + </div>
  152 + </view>
  153 + </view>
  154 + </view>
  155 +
  156 + <SingleSelectSheet
  157 + :visible.sync="sheet.visible"
  158 + :title="sheet.title"
  159 + :options="sheet.options"
  160 + v-model="sheet.value"
  161 + @confirm="onSheetConfirm"
  162 + />
  163 + </view>
  164 +</template>
  165 +
  166 +<script>
  167 +import SingleSelectSheet from '@/components/single-select/index.vue'
  168 +
  169 +export default {
  170 + name: 'SchedulePlanProductRel',
  171 + components: { SingleSelectSheet },
  172 + props: {
  173 + mode: { type: String, default: 'edit' },
  174 + list: { type: Array, default: function () { return [] } },
  175 + max: { type: Number, default: 50 },
  176 + productOptions: { type: Array, default: function () { return [] } },
  177 + workshopOptions: { type: Array, default: function () { return [] } }
  178 + },
  179 + data() {
  180 + return {
  181 + items: [],
  182 + sheet: { visible: false, title: '请选择', options: [], value: '', idx: -1, type: '' },
  183 + _initialized: false
  184 + }
  185 + },
  186 + computed: {
  187 + productOptsComputed() {
  188 + var list = Array.isArray(this.productOptions) ? this.productOptions : []
  189 + return list.map(function (o) {
  190 + return {
  191 + label: o.label != null ? o.label : (o.name != null ? o.name : (o.productName || '')),
  192 + value: o.value != null ? o.value : (o.id != null ? o.id : o.productId)
  193 + }
  194 + })
  195 + },
  196 + workshopOptsComputed() {
  197 + var list = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  198 + return list.map(function (o) {
  199 + return {
  200 + label: o.label != null ? o.label : (o.name != null ? o.name : (o.workshopName || '')),
  201 + value: o.value != null ? o.value : (o.id != null ? o.id : o.workshopId)
  202 + }
  203 + })
  204 + }
  205 + },
  206 + watch: {
  207 + items: {
  208 + handler() {
  209 + this.emitChange() },
  210 + deep: true
  211 + },
  212 + list: {
  213 + handler() {
  214 + this._initialized = false
  215 + this.initItems()
  216 + var self = this
  217 + this.$nextTick(function () { self._initialized = true })
  218 + },
  219 + immediate: false
  220 + }
  221 + },
  222 + created() {
  223 + this.initItems()
  224 + var self = this
  225 + this.$nextTick(function () { self._initialized = true })
  226 + },
  227 + methods: {
  228 + initItems() {
  229 + var src = Array.isArray(this.list) && this.list.length > 0 ? this.list : []
  230 + if (!src.length) {
  231 + if (this.mode === 'add' || this.mode === 'edit') {
  232 + this.items = [Object.assign({}, this.defaultItem(), { _key: this.newKey(), collapsed: false })]
  233 + } else {
  234 + this.items = []
  235 + }
  236 + return
  237 + }
  238 + var existing = this.items || []
  239 + var existingMap = {}
  240 + existing.forEach(function (it) {
  241 + if (it.itemId) existingMap[it.itemId] = it
  242 + })
  243 + var self = this
  244 + this.items = src.map(function (x, i) {
  245 + var key = x && x.itemId ? x.itemId : (x && x._key ? x._key : self.newKey())
  246 + var old = existingMap[key]
  247 + var collapsed = old ? old.collapsed : (typeof x.collapsed === 'boolean' ? x.collapsed : false)
  248 + return Object.assign({}, self.defaultItem(), x, {
  249 + _key: key,
  250 + itemId: x && x.itemId ? x.itemId : key,
  251 + collapsed: collapsed
  252 + })
  253 + })
  254 + },
  255 + newKey() {
  256 + return Date.now() + '-' + Math.random().toString(16).slice(2)
  257 + },
  258 + defaultItem() {
  259 + return {
  260 + productId: '',
  261 + productName: '',
  262 + materialType: '',
  263 + quantity: '',
  264 + actualPurchasePrice: '',
  265 + includingShippingPrice: '',
  266 + priceListPrices: '',
  267 + coordinationRange: '',
  268 + estimatedLoss: '',
  269 + applicationAttrition: '',
  270 + workshopId: '',
  271 + workshopName: ''
  272 + }
  273 + },
  274 + onNonNegativeInput(idx, field) {
  275 + var it = this.items[idx]
  276 + if (!it) return
  277 + var v = String(it[field] != null ? it[field] : '')
  278 + v = v.replace(/[^0-9.]/g, '')
  279 + v = v.replace(/(\..*)\./g, '$1')
  280 + it[field] = v
  281 + this.$set(this.items, idx, it)
  282 + },
  283 + onNonNegativeBlur(idx, field, digits) {
  284 + var it = this.items[idx]
  285 + if (!it) return
  286 + var raw = it[field]
  287 + if (raw === '' || raw === null || raw === undefined) {
  288 + this.$set(this.items, idx, it)
  289 + return
  290 + }
  291 + var val = parseFloat(raw)
  292 + if (isNaN(val)) val = 0
  293 + var m = Math.pow(10, digits)
  294 + var rounded = Math.round(val * m) / m
  295 + it[field] = rounded
  296 + this.$set(this.items, idx, it)
  297 + },
  298 + onAdd() {
  299 + if (this.items.length >= this.max) {
  300 + uni.showToast({ title: '最多' + this.max + '条明细', icon: 'none' })
  301 + return
  302 + }
  303 + this.items.push(Object.assign({}, this.defaultItem(), { _key: this.newKey(), collapsed: false }))
  304 + },
  305 + onRemove(idx) {
  306 + this.items.splice(idx, 1)
  307 + if ((this.mode === 'add' || this.mode === 'edit') && this.items.length === 0) {
  308 + this.items.push(Object.assign({}, this.defaultItem(), { _key: this.newKey(), collapsed: false }))
  309 + }
  310 + },
  311 + toggleItem(idx) {
  312 + var it = this.items[idx]
  313 + if (!it) return
  314 + this.$set(it, 'collapsed', !it.collapsed)
  315 + },
  316 + openSelectSheet(type, idx) {
  317 + if (type === 'product') {
  318 + var pOpts = this.productOptsComputed
  319 + var curP = this.items[idx] && this.items[idx].productId
  320 + var mP = pOpts.find(function (o) { return String(o.value) === String(curP) })
  321 + this.sheet = { visible: true, title: '请选择品种', options: pOpts, value: mP ? mP.value : '', idx: idx, type: type }
  322 + }
  323 + },
  324 + onSheetConfirm(_ref) {
  325 + var value = _ref.value
  326 + var label = _ref.label
  327 + var idx = this.sheet.idx
  328 + var type = this.sheet.type
  329 + if (idx < 0 || !this.items[idx]) return
  330 + var it = this.items[idx]
  331 + if (type === 'product') {
  332 + this.$set(it, 'productId', value)
  333 + this.$set(it, 'productName', label)
  334 + }
  335 + },
  336 + fillLineNames() {
  337 + if (!Array.isArray(this.items) || !this.items.length) return
  338 + var pOpts = Array.isArray(this.productOptions) ? this.productOptions : []
  339 + var wOpts = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  340 + var self = this
  341 + this.items = this.items.map(function (row) {
  342 + var r = Object.assign({}, row)
  343 + var pid = r.productId != null ? String(r.productId) : ''
  344 + var wid = r.workshopId != null ? String(r.workshopId) : ''
  345 + if ((r.productName == null || String(r.productName).trim() === '') && pid) {
  346 + var p = pOpts.find(function (o) {
  347 + var id = o.id != null ? String(o.id) : (o.productId != null ? String(o.productId) : (o.value != null ? String(o.value) : ''))
  348 + return id === pid
  349 + })
  350 + if (p) {
  351 + r.productName = p.name != null ? p.name : (p.productName != null ? p.productName : (p.label != null ? p.label : ''))
  352 + }
  353 + }
  354 + if ((r.workshopName == null || String(r.workshopName).trim() === '') && wid) {
  355 + var w = wOpts.find(function (o) {
  356 + var id = o.id != null ? String(o.id) : (o.workshopId != null ? String(o.workshopId) : (o.value != null ? String(o.value) : ''))
  357 + return id === wid
  358 + })
  359 + if (w) {
  360 + r.workshopName = w.name != null ? w.name : (w.workshopName != null ? w.workshopName : (w.label != null ? w.label : ''))
  361 + }
  362 + }
  363 + return r
  364 + })
  365 + this.$nextTick(function () { self.emitChange() })
  366 + },
  367 + emitChange() {
  368 + if (!this._initialized) return
  369 + var list = (this.items || []).map(function (it) {
  370 + var c = Object.assign({}, it)
  371 + var key = c._key
  372 + delete c._key
  373 + delete c.collapsed
  374 + delete c._selected
  375 + if (!c.itemId) c.itemId = key
  376 + return c
  377 + })
  378 + this.$emit('change', list)
  379 + },
  380 + safeText(v) {
  381 + var s = v == null ? '' : String(v)
  382 + return s.trim() ? s : '-'
  383 + },
  384 + formatTextNumber(v) {
  385 + if (v == null || v === '') return '-'
  386 + var n = Number(v)
  387 + if (isNaN(n)) return String(v)
  388 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  389 + },
  390 + validate() {
  391 + var items = this.items || []
  392 + if (!items.length) {
  393 + uni.showToast({ title: '请添加明细', icon: 'none' })
  394 + return false
  395 + }
  396 + var fields = [
  397 + { key: 'productName', title: '品种' },
  398 + { key: 'materialType', title: '料型' },
  399 + { key: 'quantity', title: '数量' },
  400 + { key: 'actualPurchasePrice', title: '实际采购价' },
  401 + { key: 'estimatedLoss', title: '买断料预计损耗' },
  402 + { key: 'applicationAttrition', title: '申请损耗' }
  403 + ]
  404 + for (var i = 0; i < items.length; i++) {
  405 + var it = items[i]
  406 + for (var j = 0; j < fields.length; j++) {
  407 + var f = fields[j]
  408 + var v = it[f.key]
  409 + if (v === '' || v === null || v === undefined) {
  410 + uni.showToast({ title: '请填写第' + (i + 1) + '条明细的' + f.title, icon: 'none' })
  411 + return false
  412 + }
  413 + }
  414 + }
  415 + return true
  416 + }
  417 + }
  418 +}
  419 +</script>
  420 +
  421 +<style lang="scss" scoped>
  422 +.product-rel {
  423 + margin-top: 20rpx;
  424 + background: #fff;
  425 +}
  426 +.header {
  427 + display: flex;
  428 + align-items: center;
  429 + padding: 24rpx 32rpx;
  430 + border-bottom: 1rpx solid #f0f0f0;
  431 +}
  432 +.title {
  433 + font-size: 32rpx;
  434 + color: rgba(0, 0, 0, 0.9);
  435 + font-weight: 600;
  436 +}
  437 +.ops {
  438 + margin-left: auto;
  439 +}
  440 +.opAdd {
  441 + color: rgba(0, 0, 0, 0.6);
  442 + width: 40rpx;
  443 + height: 40rpx;
  444 +}
  445 +.opCollapse {
  446 + color: rgba(0, 0, 0, 0.6);
  447 + width: 32rpx;
  448 + height: 28rpx;
  449 + margin-right: 16rpx;
  450 + margin-top: 8rpx;
  451 +}
  452 +.block {
  453 + background: #fff;
  454 + margin-bottom: 20rpx;
  455 +}
  456 +::v-deep .uni-list-item__content {
  457 + display: flex;
  458 + justify-content: center;
  459 +}
  460 +::v-deep .uni-list {
  461 + .uni-easyinput {
  462 + display: flex;
  463 + .uni-input-input {
  464 + color: rgba(0, 0, 0, 0.9);
  465 + }
  466 + }
  467 + .uni-input-placeholder { z-index: 1; }
  468 + .uni-input-input { background-color: #ffffff; }
  469 + background: transparent;
  470 + &-item {
  471 + &__container { padding: 32rpx; }
  472 + &__content-title {
  473 + font-size: 28rpx;
  474 + color: rgba(0, 0, 0, 0.9);
  475 + }
  476 + &__extra-text { font-size: 32rpx; }
  477 + .uni-easyinput {
  478 + width: 100%;
  479 + &__placeholder-class {
  480 + font-size: 32rpx;
  481 + color: rgba(0, 0, 0, 0.4);
  482 + }
  483 + &__content {
  484 + border: none;
  485 + background-color: #ffffff !important;
  486 + display: flex;
  487 + &-input {
  488 + padding-left: 0 !important;
  489 + height: 48rpx;
  490 + line-height: 48rpx;
  491 + font-size: 32rpx;
  492 + }
  493 + }
  494 + }
  495 + &.select-item {
  496 + &.is-empty {
  497 + .uni-list-item__extra-text { color: rgba(0, 0, 0, 0.4) !important; }
  498 + }
  499 + &.is-filled {
  500 + .uni-list-item__extra-text { color: rgba(0, 0, 0, 0.9) !important; }
  501 + }
  502 + }
  503 + }
  504 +}
  505 +
  506 +::v-deep .uni-easyinput {
  507 + width: 100%;
  508 + &__content {
  509 + background: #f8f8f8 !important;
  510 + border-radius: 8rpx;
  511 + &-input { font-size: 30rpx; }
  512 + }
  513 +}
  514 +.block-ops {
  515 + display: flex;
  516 + padding: 20rpx 32rpx 20rpx;
  517 + justify-content: space-around;
  518 +}
  519 +.del {
  520 + color: #D54941;
  521 + font-size: 28rpx;
  522 + display: flex;
  523 + align-items: center;
  524 + image { width: 40rpx; height: 40rpx; }
  525 +}
  526 +.toggle {
  527 + color: $theme-primary;
  528 + font-size: 28rpx;
  529 + display: flex;
  530 + align-items: center;
  531 + image { width: 40rpx; height: 40rpx; }
  532 +}
  533 +.item-title {
  534 + position: relative;
  535 + width: 210rpx;
  536 + margin-right: 32rpx;
  537 + color: rgba(0, 0, 0, 0.9);
  538 + font-size: 32rpx;
  539 + min-height: 48rpx;
  540 + line-height: 48rpx;
  541 + .required {
  542 + color: red;
  543 + position: absolute;
  544 + top: 50%;
  545 + transform: translateY(-50%);
  546 + left: -16rpx;
  547 + }
  548 +}
  549 +.lock-value {
  550 + flex: 1;
  551 + text-align: right;
  552 + font-size: 32rpx;
  553 + color: rgba(0, 0, 0, 0.85);
  554 + padding: 8rpx 0;
  555 +}
  556 +</style>
  1 +<template>
  2 + <view class="detail-page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="section">
  5 + <text class="row company">{{ safeText(form.code) }}</text>
  6 + <view v-if="canEdit" class="row">
  7 + <text class="label required">公司名称</text>
  8 + <view class="value select-value" @click="openCompanySheet">
  9 + <text :class="form.companyShortName || form.companyName ? '' : 'placeholder'">{{ form.companyShortName || form.companyName || '请选择公司名称' }}</text>
  10 + <text class="arrow">›</text>
  11 + </view>
  12 + </view>
  13 + <view v-else class="row"><text class="label">公司简称</text><text class="value">{{ safeText(form.companyShortName || form.companyName) }}</text></view>
  14 + <view class="row"><text class="label">供应商名称</text><text class="value">{{ safeText(form.supplierName) }}</text></view>
  15 + <view class="row"><text class="label">采购处</text><text class="value">{{ safeText(form.purchaseDepartmentName || form.purchaseDepartment) }}</text></view>
  16 + <view class="row"><text class="label">区域</text><text class="value">{{ safeText(form.regionName || form.region) }}</text></view>
  17 + <view v-if="canEdit" class="row">
  18 + <text class="label required">日期</text>
  19 + <view class="value date-value">
  20 + <uni-datetime-picker type="date" v-model="form.date" return-type="string" />
  21 + </view>
  22 + </view>
  23 + <view v-else class="row"><text class="label">日期</text><text class="value">{{ formatDateOnly(form.date) }}</text></view>
  24 + <view class="row"><text class="label">报价时效</text><text class="value">{{ safeText(form.validityPeriod) }}</text></view>
  25 + <view class="row"><text class="label">报价人</text><text class="value">{{ safeText(form.quoterName) }}</text></view>
  26 + <view class="row"><text class="label">是否长单操作</text><text class="value">{{ boolText(form.longTermOperation) }}</text></view>
  27 + <view class="row"><text class="label">是否终端客户</text><text class="value">{{ boolText(form.terminalCustomer) }}</text></view>
  28 + <view class="row"><text class="label">是否未点价</text><text class="value">{{ boolText(form.priced) }}</text></view>
  29 + <view v-if="form.priced === true" class="row"><text class="label">点价截止日期</text><text class="value">{{ formatDateOnly(form.pricingDeadline) }}</text></view>
  30 + <view v-if="form.priced === true" class="row"><text class="label">是否暂定价</text><text class="value">{{ boolText(form.temporaryPrice) }}</text></view>
  31 + <view class="row"><text class="label">其他费用凭票到厂另行结算</text><text class="value">{{ boolText(form.otherExpenseSettlement) }}</text></view>
  32 + <view class="row"><text class="label">交提货方式</text><text class="value">{{ getDeliveryMethodLabel() }}</text></view>
  33 + <view class="row"><text class="label">是否通货买断</text><text class="value">{{ boolText(form.commodityBuyout) }}</text></view>
  34 + <view class="row"><text class="label">是否质量买断</text><text class="value">{{ boolText(form.qualityBuyout) }}</text></view>
  35 + <view class="row"><text class="label">是否数量买断</text><text class="value">{{ boolText(form.quantityBuyout) }}</text></view>
  36 + <view class="row"><text class="label">是否送货上门</text><text class="value">{{ boolText(form.doorDelivery) }}</text></view>
  37 + <view class="row">
  38 + <text class="label">另付运费</text>
  39 + <input v-if="canEdit" class="value line-input" v-model="form.shippingCost" type="digit" placeholder="请输入" @input="onShippingCostInput" @blur="onShippingCostBlur" />
  40 + <text v-else class="value">{{ formatNumber(form.shippingCost) }}</text>
  41 + </view>
  42 + <view class="row"><text class="label">装车费</text><text class="value">{{ formatNumber(form.loadingFee) }}</text></view>
  43 + <view class="row"><text class="label">开票品名</text><text class="value">{{ safeText(form.invoiceProductName) }}</text></view>
  44 + <view class="row"><text class="label">交货日期</text><text class="value">{{ formatDateOnly(form.deliveryDate) }}</text></view>
  45 + <view class="row"><text class="label">结算方式</text><text class="value">{{ safeText(form.settlementMethod) }}</text></view>
  46 + <view class="row"><text class="label">结算比例(%)</text><text class="value">{{ formatNumber(form.settlementRatio) }}</text></view>
  47 + <view class="row">
  48 + <text class="label" :class="{ required: canEdit }">价格表编号</text>
  49 + <view v-if="canEdit" class="value select-value" @click="openPriceTableSheet">
  50 + <text :class="priceTableCodeLabel ? '' : 'placeholder'">{{ priceTableCodeLabel || '请选择价格表编号' }}</text>
  51 + <text class="arrow">›</text>
  52 + </view>
  53 + <text v-else class="value">{{ priceTableDisplay }}</text>
  54 + </view>
  55 + <view class="row"><text class="label">协调事由</text><text class="value">{{ safeText(form.coordinationReason) }}</text></view>
  56 + <view class="row"><text class="label">票类状况</text><text class="value">{{ safeText(form.invoiceTypeStatus) }}</text></view>
  57 + <view class="row"><text class="label">数量合计</text><text class="value">{{ formatNumber(form.totalQuantity) }}</text></view>
  58 + <view class="row" v-if="attachmentList.length">
  59 + <text class="label">附件</text>
  60 + <view class="attach-list">
  61 + <view v-for="(at, idx) in attachmentList" :key="idx" class="attach-item">
  62 + <text class="attach-name">{{ at.name }}</text>
  63 + <text class="attach-view" @click.stop="onPreviewAttachment(at)">查看</text>
  64 + </view>
  65 + </view>
  66 + </view>
  67 + </view>
  68 +
  69 + <view class="title-header">
  70 + <image class="title-header_icon" src="/static/images/title.png" />
  71 + <span>明细信息</span>
  72 + </view>
  73 + <view v-if="lineListWithName.length" class="section">
  74 + <view v-for="(line, idx) in lineListWithName" :key="line.itemId || idx" class="line-block">
  75 + <view class="line-title">明细{{ idx + 1 }}</view>
  76 + <view class="row"><text class="label">品种</text><text class="value">{{ safeText(line.productName) }}</text></view>
  77 + <view class="row"><text class="label">料型</text><text class="value">{{ safeText(line.materialType) }}</text></view>
  78 + <view class="row"><text class="label">数量(吨)</text><text class="value">{{ formatNumber(line.quantity) }}</text></view>
  79 + <view class="row"><text class="label">实际采购价</text><text class="value">{{ formatNumber(line.actualPurchasePrice) }}</text></view>
  80 + <view class="row"><text class="label">含运到厂价</text><text class="value">{{ formatNumber(line.includingShippingPrice) }}</text></view>
  81 + <view class="row">
  82 + <text class="label">价格表价格</text>
  83 + <input v-if="canEdit" class="value line-input" v-model="line.priceListPrices" type="digit" placeholder="请输入" @input="onLinePriceInput(idx, $event)" @blur="onLinePriceBlur(idx)" />
  84 + <text v-else class="value">{{ formatNumber(line.priceListPrices) }}</text>
  85 + </view>
  86 + <view class="row"><text class="label">协调幅度</text><text class="value">{{ formatNumber(line.coordinationRange) }}</text></view>
  87 + <view class="row"><text class="label">买断料预计损耗</text><text class="value">{{ formatNumber(line.estimatedLoss) }}</text></view>
  88 + <view class="row"><text class="label">申请损耗(%)</text><text class="value">{{ formatNumber(line.applicationAttrition) }}</text></view>
  89 + <view class="row">
  90 + <text class="label" :class="{ required: canEditWorkshop }">选择分厂</text>
  91 + <view v-if="canEditWorkshop" class="value select-value" @click="openWorkshopSheet(idx)">
  92 + <text :class="line.workshopName ? '' : 'placeholder'">{{ line.workshopName || '请选择分厂' }}</text>
  93 + <text class="arrow">›</text>
  94 + </view>
  95 + <text v-else class="value">{{ safeText(line.workshopName) }}</text>
  96 + </view>
  97 + </view>
  98 + </view>
  99 + <view v-else class="section empty-tip">暂无明细</view>
  100 +
  101 + <view class="title-header">
  102 + <image class="title-header_icon" src="/static/images/title.png" />
  103 + <span>料质状况</span>
  104 + </view>
  105 + <view class="section">
  106 + <view class="row"><text class="label">是否启用料质状况</text><text class="value">{{ boolText(form.materialConditionEnabled) }}</text></view>
  107 + <template v-if="form.materialConditionEnabled">
  108 + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group">
  109 + <text class="material-label">{{ mc.label }}</text>
  110 + <view class="tag-list">
  111 + <view v-for="opt in getDictOptions(mc.dictCode)" :key="opt.code" class="tag-btn" :class="{ active: isTagSelected(mc.field, opt.code) }">{{ opt.name }}</view>
  112 + </view>
  113 + </view>
  114 + </template>
  115 + </view>
  116 +
  117 + <view class="title-header">
  118 + <image class="title-header_icon" src="/static/images/title.png" />
  119 + <span>备注</span>
  120 + </view>
  121 + <view class="section">
  122 + <text class="remark-text">{{ safeText(form.remark) }}</text>
  123 + </view>
  124 +
  125 + <view class="title-header">
  126 + <image class="title-header_icon" src="/static/images/title.png" />
  127 + <span>变更事由</span>
  128 + </view>
  129 + <view class="section">
  130 + <text class="remark-text">{{ safeText(form.changeReason) }}</text>
  131 + </view>
  132 + </scroll-view>
  133 +
  134 + <RelateSelectSheet
  135 + v-if="companySheetVisible"
  136 + :visible.sync="companySheetVisible"
  137 + title="选择公司"
  138 + :fetch-fn="fetchCompanyList"
  139 + :display-fields="companyDisplayFields"
  140 + :page-size="20"
  141 + :multiple="false"
  142 + row-key="id"
  143 + @confirm="onCompanyConfirm"
  144 + />
  145 + <RelateSelectSheet
  146 + v-if="priceSheetVisible"
  147 + :visible.sync="priceSheetVisible"
  148 + title="选择价格表"
  149 + :fetch-fn="fetchPriceTableList"
  150 + :display-fields="priceTableDisplayFields"
  151 + :page-size="20"
  152 + :multiple="false"
  153 + row-key="id"
  154 + @confirm="onPriceTableConfirm"
  155 + />
  156 + <SingleSelectSheet
  157 + :visible.sync="workshopSheet.visible"
  158 + title="请选择分厂"
  159 + :options="workshopSheet.options"
  160 + v-model="workshopSheet.value"
  161 + @confirm="onWorkshopConfirm"
  162 + />
  163 + </view>
  164 +</template>
  165 +<script>
  166 +import { procurementPlanChangeFindByIdApi } from '@/api/procure-manage/procurementPlan.js'
  167 +import { breedRelationshipQueryApi, workshopQueryApi, companyShortQueryApi, priceTableQueryApi, priceTableDetailApi } from '@/api/devManage.js'
  168 +import { getDicByCodes } from '@/utils/dic.js'
  169 +import { getInfo } from '@/api/login.js'
  170 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  171 +import SingleSelectSheet from '@/components/single-select/index.vue'
  172 +export default {
  173 + name: 'SchedulePlanChangeViewer',
  174 + components: { RelateSelectSheet, SingleSelectSheet },
  175 + props: { id: { type: [String, Number], default: '' } },
  176 + data() {
  177 + return {
  178 + form: {},
  179 + lineList: [],
  180 + attachmentList: [],
  181 + materialConditionConfigs: [
  182 + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' },
  183 + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' },
  184 + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' },
  185 + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' },
  186 + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' },
  187 + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' },
  188 + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' },
  189 + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' }
  190 + ],
  191 + dictData: {},
  192 + productOptions: [],
  193 + workshopOptions: [],
  194 + deliveryOptions: [],
  195 + isGyglkzg: false,
  196 + isGyglkzlglg: false,
  197 + companySheetVisible: false,
  198 + priceSheetVisible: false,
  199 + workshopSheet: { visible: false, title: '请选择分厂', options: [], value: '', idx: -1 },
  200 + companyDisplayFields: [
  201 + { label: '公司简称', field: 'companyShortName' },
  202 + { label: '公司名称', field: 'companyName' },
  203 + { label: '编码', field: 'code' }
  204 + ],
  205 + priceTableDisplayFields: [
  206 + { label: '价格表编号', field: 'priceTableCode' },
  207 + { label: '名称', field: 'priceTableName' },
  208 + { label: '日期', field: 'effectiveDate' }
  209 + ]
  210 + }
  211 + },
  212 + computed: {
  213 + isAuditPage() {
  214 + try {
  215 + var pages = getCurrentPages()
  216 + var cur = pages[pages.length - 1]
  217 + var route = cur && cur.route ? String(cur.route) : ''
  218 + return route === 'pages/flow/audit'
  219 + } catch (e) {
  220 + return false
  221 + }
  222 + },
  223 + canEdit() {
  224 + return !!this.isGyglkzg && this.isAuditPage
  225 + },
  226 + canEditWorkshop() {
  227 + return !!this.isGyglkzlglg && this.isAuditPage
  228 + },
  229 + workshopOptsComputed() {
  230 + var list = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  231 + return list.map(function (o) {
  232 + return {
  233 + label: o.label != null ? o.label : (o.name != null ? o.name : (o.workshopName || '')),
  234 + value: o.value != null ? o.value : (o.id != null ? o.id : o.workshopId)
  235 + }
  236 + })
  237 + },
  238 + shippingCostNumber() {
  239 + var v = this.form.shippingCost
  240 + if (v == null || v === '') return 0
  241 + var n = Number(v)
  242 + return isNaN(n) ? 0 : n
  243 + },
  244 + priceTableCodeLabel() {
  245 + var d = this.form || {}
  246 + var code = d.priceTableCode != null ? d.priceTableCode : ''
  247 + var name = d.priceTableName || ''
  248 + if (name || code) {
  249 + return (name ? name + (code ? ' (' + code + ')' : '') : String(code))
  250 + }
  251 + return ''
  252 + },
  253 + productMap() {
  254 + var list = Array.isArray(this.productOptions) ? this.productOptions : []
  255 + var m = {}
  256 + list.forEach(function (o) {
  257 + var key = String(o.productId || o.id || o.value || '')
  258 + if (key) m[key] = o
  259 + })
  260 + return m
  261 + },
  262 + workshopMap() {
  263 + var list = Array.isArray(this.workshopOptions) ? this.workshopOptions : []
  264 + var m = {}
  265 + list.forEach(function (o) {
  266 + var key = String(o.workshopId || o.id || o.value || '')
  267 + if (key) m[key] = o
  268 + })
  269 + return m
  270 + },
  271 + lineListWithName() {
  272 + var self = this
  273 + var list = Array.isArray(this.lineList) ? this.lineList : []
  274 + return list.map(function (row) {
  275 + if (!row.productName) {
  276 + var pid = String(row.productId || '')
  277 + var p = pid ? self.productMap[pid] : null
  278 + if (p) row.productName = p.productName || p.name || p.label || ''
  279 + }
  280 + if (!row.workshopName) {
  281 + var wid = String(row.workshopId || '')
  282 + var w = wid ? self.workshopMap[wid] : null
  283 + if (w) row.workshopName = w.workshopName || w.name || w.label || ''
  284 + }
  285 + return row
  286 + })
  287 + },
  288 + priceTableDisplay() {
  289 + var d = this.form || {}
  290 + var code = d.priceTableCode != null ? d.priceTableCode : (d.priceId != null ? d.priceId : '')
  291 + var name = d.priceTableName || ''
  292 + if (name || code) {
  293 + return (name ? name + (code ? ' (' + code + ')' : '') : String(code))
  294 + }
  295 + return '-'
  296 + }
  297 + },
  298 + created() {
  299 + this.loadDictData()
  300 + this.loadRefData()
  301 + this.getUserInfo()
  302 + },
  303 + watch: {
  304 + id: {
  305 + immediate: true,
  306 + handler(val) {
  307 + const v = (val !== undefined && val !== null) ? String(val) : ''
  308 + if (v) this.loadDetail(v)
  309 + }
  310 + }
  311 + },
  312 + methods: {
  313 + getUserInfo() {
  314 + getInfo()
  315 + .then((res) => {
  316 + this.isGyglkzg = !!(res?.roleCodes?.includes('gyglkzg') && res?.roleCodes?.includes('001'));
  317 + this.isGyglkzlglg = !!(res?.roleCodes?.includes('gyglkzlglg') && res?.roleCodes?.includes('001'));
  318 + })
  319 + .catch((err) => {
  320 + console.error('获取用户信息失败:', err);
  321 + });
  322 + },
  323 + toNumber(v) {
  324 + if (v == null || v === '') return 0
  325 + var n = Number(v)
  326 + return isNaN(n) ? 0 : n
  327 + },
  328 + toFixed(v, n) {
  329 + var num = Number(v)
  330 + if (isNaN(num)) return ''
  331 + return num.toFixed(n)
  332 + },
  333 + getInputValue(e) {
  334 + if (e && e.detail && e.detail.value !== undefined) return e.detail.value
  335 + return e
  336 + },
  337 + async loadDictData() {
  338 + var codes = [].concat(
  339 + this.materialConditionConfigs.map(function (mc) { return mc.dictCode }),
  340 + ['DELIVERY_METHOD']
  341 + )
  342 + try {
  343 + var results = await getDicByCodes(codes)
  344 + var d = results || {}
  345 + this.dictData = d
  346 + this.deliveryOptions = (d.DELIVERY_METHOD && d.DELIVERY_METHOD.data) || []
  347 + } catch (e) {
  348 + this.dictData = {}
  349 + this.deliveryOptions = []
  350 + }
  351 + },
  352 + async loadRefData() {
  353 + var self = this
  354 + try {
  355 + var results = await Promise.all([
  356 + this.pCall(breedRelationshipQueryApi, { pageIndex: 1, pageSize: 1000 }),
  357 + this.pCall(workshopQueryApi, { pageIndex: 1, pageSize: 1000 })
  358 + ])
  359 + var r1 = (results[0] && results[0].data) ? results[0].data : {}
  360 + var r2 = (results[1] && results[1].data) ? results[1].data : {}
  361 + self.productOptions = (r1.datas && Array.isArray(r1.datas)) ? r1.datas : ((r1.list && Array.isArray(r1.list)) ? r1.list : ((r1.records && Array.isArray(r1.records)) ? r1.records : []))
  362 + self.workshopOptions = (r2.datas && Array.isArray(r2.datas)) ? r2.datas : ((r2.list && Array.isArray(r2.list)) ? r2.list : ((r2.records && Array.isArray(r2.records)) ? r2.records : []))
  363 + } catch (e) {
  364 + self.productOptions = []
  365 + self.workshopOptions = []
  366 + }
  367 + },
  368 + pCall(fn, params) {
  369 + try {
  370 + return Promise.resolve(fn(params))
  371 + } catch (e) {
  372 + return Promise.reject(e)
  373 + }
  374 + },
  375 + async loadDetail(id) {
  376 + try {
  377 + var res = await procurementPlanChangeFindByIdApi({ id: id })
  378 + var d = (res && res.data) ? res.data : {}
  379 + var boolFields = ['longTermOperation', 'terminalCustomer', 'priced', 'temporaryPrice', 'otherExpenseSettlement', 'commodityBuyout', 'qualityBuyout', 'quantityBuyout', 'doorDelivery', 'materialConditionEnabled']
  380 + boolFields.forEach(function (f) {
  381 + var v = d[f]
  382 + if (typeof v === 'string') {
  383 + d[f] = v === 'true' || v === '1'
  384 + } else if (typeof v === 'number') {
  385 + d[f] = v === 1
  386 + } else {
  387 + d[f] = !!v
  388 + }
  389 + })
  390 + this.materialConditionConfigs.forEach(function (mc) {
  391 + var v = d[mc.field]
  392 + if (typeof v === 'string' && v) {
  393 + d[mc.field] = v.split(',').filter(Boolean)
  394 + } else if (!Array.isArray(v)) {
  395 + d[mc.field] = []
  396 + }
  397 + })
  398 + this.form = d
  399 + var lines = d.lines || d.procurementPlanLineList || d.lineList || []
  400 + this.lineList = Array.isArray(lines) ? lines.slice() : []
  401 + // 初始化可编辑字段
  402 + this.form.companyId = d.companyId || ''
  403 + this.form.companyShortName = d.companyShortName || d.companyName || ''
  404 + var date = d.date ? String(d.date).trim() : ''
  405 + this.form.date = date.length >= 10 ? date.slice(0, 10) : date
  406 + var sc = d.shippingCost
  407 + this.form.shippingCost = (sc != null && sc !== '') ? this.toFixed(sc, 2) : ''
  408 + this.buildAttachmentList()
  409 + this.refreshLinesFromShipping()
  410 + if (this.canEdit) {
  411 + this.loadLatestPriceTableAndApply()
  412 + }
  413 + } catch (e) {
  414 + this.form = {}
  415 + this.lineList = []
  416 + this.attachmentList = []
  417 + }
  418 + },
  419 + buildAttachmentList() {
  420 + var d = this.form || {}
  421 + var ids = d.attachmentFileIds
  422 + var names = d.attachmentFileNames
  423 + var idArr = (typeof ids === 'string' && ids) ? ids.split(',').filter(Boolean) : (Array.isArray(ids) ? ids : [])
  424 + var nameArr = (typeof names === 'string' && names) ? names.split(',').filter(Boolean) : (Array.isArray(names) ? names : [])
  425 + var len = Math.max(idArr.length, nameArr.length)
  426 + var list = []
  427 + for (var i = 0; i < len; i++) {
  428 + list.push({ id: idArr[i] || '', name: nameArr[i] || ('附件' + (i + 1)) })
  429 + }
  430 + this.attachmentList = list
  431 + },
  432 + getDictOptions(dictCode) {
  433 + var data = this.dictData[dictCode]
  434 + return (data && data.data) ? data.data : []
  435 + },
  436 + isTagSelected(field, code) {
  437 + var selected = this.form ? this.form[field] : undefined
  438 + if (Array.isArray(selected)) return selected.indexOf(code) >= 0
  439 + if (typeof selected === 'string' && selected) {
  440 + return selected.split(',').indexOf(code) >= 0
  441 + }
  442 + return false
  443 + },
  444 + getDeliveryMethodLabel() {
  445 + var opts = this.deliveryOptions || []
  446 + var v = String(this.form.deliveryMode != null ? this.form.deliveryMode : '')
  447 + if (!v) return '-'
  448 + for (var i = 0; i < opts.length; i++) {
  449 + var o = opts[i]
  450 + var code = o.code != null ? String(o.code) : (o.value != null ? String(o.value) : '')
  451 + if (code === v) return o.name || o.label || '-'
  452 + }
  453 + return v
  454 + },
  455 + boolText(v) {
  456 + if (typeof v === 'boolean') return v ? '是' : '否'
  457 + if (v === 'true' || v === '1' || v === 1) return '是'
  458 + if (v === 'false' || v === '0' || v === 0) return '否'
  459 + return '-'
  460 + },
  461 + formatDateOnly(v) {
  462 + var s = v == null ? '' : String(v).trim()
  463 + if (!s) return '-'
  464 + if (s.length >= 10) return s.slice(0, 10)
  465 + return s
  466 + },
  467 + safeText(v) {
  468 + var s = v == null ? '' : String(v)
  469 + return s.trim() ? s : '-'
  470 + },
  471 + formatNumber(v) {
  472 + if (v == null || v === '') return '-'
  473 + var n = Number(v)
  474 + if (isNaN(n)) return String(v)
  475 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  476 + },
  477 + onPreviewAttachment(at) {
  478 + if (!at || !at.id) {
  479 + uni.showToast({ title: '无法查看', icon: 'none' })
  480 + return
  481 + }
  482 + uni.showToast({ title: '查看附件 ' + at.name, icon: 'none' })
  483 + },
  484 + // ==================== 公司下拉 ====================
  485 + fetchCompanyList(args) {
  486 + var extra = (args && args.extra) ? args.extra : {}
  487 + var keyword = extra.keyword || extra.name || ''
  488 + var params = { pageIndex: args.pageIndex || 1, pageSize: args.pageSize || 20 }
  489 + if (keyword) params.name = keyword
  490 + return companyShortQueryApi(params).then(function (res) {
  491 + var d = (res && res.data) ? res.data : {}
  492 + var records = (d.datas && Array.isArray(d.datas)) ? d.datas : ((d.records && Array.isArray(d.records)) ? d.records : ((d.list && Array.isArray(d.list)) ? d.list : []))
  493 + var total = d.totalCount || d.count || (records ? records.length : 0)
  494 + var hasNext = !!d.hasNext
  495 + return { records: records, totalCount: total, hasNext: hasNext }
  496 + }).catch(function () {
  497 + return { records: [], totalCount: 0, hasNext: false }
  498 + })
  499 + },
  500 + openCompanySheet() {
  501 + this.companySheetVisible = true
  502 + },
  503 + onCompanyConfirm(e) {
  504 + var items = (e && e.items) ? e.items : []
  505 + if (!items.length) return
  506 + var row = items[0] || {}
  507 + this.form.companyId = row.id || ''
  508 + this.form.companyShortName = row.companyShortName || row.companyName || row.name || ''
  509 + },
  510 + // ==================== 价格表下拉 ====================
  511 + fetchPriceTableList(args) {
  512 + var extra = (args && args.extra) ? args.extra : {}
  513 + var keyword = extra.keyword || extra.name || ''
  514 + var params = { pageIndex: args.pageIndex || 1, pageSize: args.pageSize || 20 }
  515 + if (keyword) params.name = keyword
  516 + return priceTableQueryApi(params).then(function (res) {
  517 + var d = (res && res.data) ? res.data : {}
  518 + var records = (d.datas && Array.isArray(d.datas)) ? d.datas : ((d.records && Array.isArray(d.records)) ? d.records : ((d.list && Array.isArray(d.list)) ? d.list : []))
  519 + var total = d.totalCount || d.count || (records ? records.length : 0)
  520 + var hasNext = !!d.hasNext
  521 + return { records: records, totalCount: total, hasNext: hasNext }
  522 + }).catch(function () {
  523 + return { records: [], totalCount: 0, hasNext: false }
  524 + })
  525 + },
  526 + openPriceTableSheet() {
  527 + this.priceSheetVisible = true
  528 + },
  529 + onPriceTableConfirm(e) {
  530 + var items = (e && e.items) ? e.items : []
  531 + if (!items.length) return
  532 + this.selectPriceTableRow(items[0] || {})
  533 + },
  534 + selectPriceTableRow(row) {
  535 + this.form.priceTableId = row.id || ''
  536 + this.form.priceTableCode = row.priceTableCode || row.code || ''
  537 + this.form.priceTableName = row.priceTableName || row.name || ''
  538 + if (this.form.priceTableId) {
  539 + this.fetchPriceDetailAndApply(this.form.priceTableId)
  540 + }
  541 + },
  542 + async loadLatestPriceTableAndApply() {
  543 + var self = this
  544 + try {
  545 + var res = await priceTableQueryApi({ pageIndex: 1, pageSize: 1, orderByColumn: 'createTime desc,id desc' })
  546 + var d = (res && res.data) ? res.data : {}
  547 + var list = (d.datas && Array.isArray(d.datas)) ? d.datas : ((d.records && Array.isArray(d.records)) ? d.records : ((d.list && Array.isArray(d.list)) ? d.list : []))
  548 + if (!list || !list.length) return
  549 + self.selectPriceTableRow(list[0])
  550 + } catch (e) {
  551 + return
  552 + }
  553 + },
  554 + async fetchPriceDetailAndApply(priceId) {
  555 + if (!priceId) return
  556 + var self = this
  557 + try {
  558 + var res = await priceTableDetailApi({ id: priceId })
  559 + var d = (res && res.data) ? res.data : {}
  560 + var priceLines = self.extractPriceLines(d)
  561 + self.applyPriceLinesToLines(priceLines)
  562 + } catch (e) {
  563 + return
  564 + }
  565 + },
  566 + extractPriceLines(priceDetail) {
  567 + if (!priceDetail || typeof priceDetail !== 'object') return []
  568 + var candidates = [
  569 + priceDetail.procurementPriceLineList,
  570 + priceDetail.priceLineList,
  571 + priceDetail.priceList,
  572 + priceDetail.lineList,
  573 + priceDetail.datas,
  574 + priceDetail.records,
  575 + priceDetail.list
  576 + ]
  577 + for (var i = 0; i < candidates.length; i++) {
  578 + if (Array.isArray(candidates[i]) && candidates[i].length) return candidates[i]
  579 + }
  580 + for (var k in priceDetail) {
  581 + if (!Object.prototype.hasOwnProperty.call(priceDetail, k)) continue
  582 + if (Array.isArray(priceDetail[k])) {
  583 + var arr = priceDetail[k]
  584 + if (arr.length && arr[0] && typeof arr[0] === 'object' && (arr[0].price != null || arr[0].productId != null || arr[0].breedId != null || arr[0].varietyId != null)) {
  585 + return arr
  586 + }
  587 + }
  588 + }
  589 + return []
  590 + },
  591 + applyPriceLinesToLines(priceLines) {
  592 + if (!Array.isArray(this.lineList)) return
  593 + var priceMap = this.buildPriceMap(priceLines)
  594 + var self = this
  595 + this.lineList = this.lineList.map(function (row) {
  596 + var pid = String(row.productId || row.breedId || row.varietyId || '')
  597 + var price = null
  598 + if (pid && priceMap[pid] != null) {
  599 + price = priceMap[pid].price
  600 + } else {
  601 + var pname = (row.productName || '').trim()
  602 + if (pname) {
  603 + for (var k in priceMap) {
  604 + if (!Object.prototype.hasOwnProperty.call(priceMap, k)) continue
  605 + var meta = priceMap[k] && priceMap[k].meta ? priceMap[k].meta : null
  606 + if (meta && ((meta.productName && meta.productName === pname) || (meta.breedName && meta.breedName === pname) || (meta.varietyName && meta.varietyName === pname))) {
  607 + price = priceMap[k].price
  608 + break
  609 + }
  610 + }
  611 + }
  612 + }
  613 + if (price != null) {
  614 + row.priceListPrices = self.toFixed(price, 2)
  615 + }
  616 + return row
  617 + })
  618 + this.refreshLinesFromShipping()
  619 + },
  620 + buildPriceMap(priceLines) {
  621 + var m = {}
  622 + if (!Array.isArray(priceLines)) return m
  623 + for (var i = 0; i < priceLines.length; i++) {
  624 + var line = priceLines[i] || {}
  625 + if (line.price == null) continue
  626 + var key = String(line.productId || line.breedId || line.varietyId || line.id || ('__idx_' + i))
  627 + m[key] = { price: Number(line.price), meta: line }
  628 + }
  629 + return m
  630 + },
  631 + // ==================== 分厂选择 ====================
  632 + openWorkshopSheet(idx) {
  633 + var wOpts = this.workshopOptsComputed
  634 + var curW = this.lineList[idx] && this.lineList[idx].workshopId
  635 + var mW = wOpts.find(function (o) { return String(o.value) === String(curW) })
  636 + this.workshopSheet = { visible: true, title: '请选择分厂', options: wOpts, value: mW ? mW.value : '', idx: idx }
  637 + },
  638 + onWorkshopConfirm(_ref) {
  639 + var value = _ref.value
  640 + var label = _ref.label
  641 + var idx = this.workshopSheet.idx
  642 + if (idx < 0 || !this.lineList[idx]) return
  643 + var row = this.lineList[idx]
  644 + this.$set(row, 'workshopId', value)
  645 + this.$set(row, 'workshopName', label)
  646 + },
  647 + // ==================== 联动计算 ====================
  648 + onShippingCostInput(e) {
  649 + var s = this.getInputValue(e)
  650 + s = s == null ? '' : String(s)
  651 + s = s.replace(/[^\d.]/g, '')
  652 + var parts = s.split('.')
  653 + if (parts.length > 2) s = parts[0] + '.' + parts.slice(1).join('')
  654 + this.form.shippingCost = s
  655 + this.refreshLinesFromShipping()
  656 + },
  657 + onShippingCostBlur() {
  658 + if (this.form.shippingCost == null || this.form.shippingCost === '') {
  659 + this.refreshLinesFromShipping()
  660 + return
  661 + }
  662 + var n = Number(this.form.shippingCost)
  663 + if (isNaN(n) || n < 0) {
  664 + this.form.shippingCost = ''
  665 + } else {
  666 + this.form.shippingCost = this.toFixed(n, 2)
  667 + }
  668 + this.refreshLinesFromShipping()
  669 + },
  670 + onLinePriceInput(idx, e) {
  671 + var s = this.getInputValue(e)
  672 + s = s == null ? '' : String(s)
  673 + s = s.replace(/[^\d.]/g, '')
  674 + var parts = s.split('.')
  675 + if (parts.length > 2) s = parts[0] + '.' + parts.slice(1).join('')
  676 + var row = this.lineList[idx]
  677 + if (row) {
  678 + this.$set(row, 'priceListPrices', s)
  679 + this.recalcLine(idx)
  680 + }
  681 + },
  682 + onLinePriceBlur(idx) {
  683 + var row = this.lineList[idx]
  684 + if (!row) return
  685 + if (row.priceListPrices == null || row.priceListPrices === '') {
  686 + this.recalcLine(idx)
  687 + return
  688 + }
  689 + var n = Number(row.priceListPrices)
  690 + if (isNaN(n) || n < 0) {
  691 + this.$set(row, 'priceListPrices', '')
  692 + } else {
  693 + this.$set(row, 'priceListPrices', this.toFixed(n, 2))
  694 + }
  695 + this.recalcLine(idx)
  696 + },
  697 + refreshLinesFromShipping() {
  698 + var self = this
  699 + var sc = self.shippingCostNumber
  700 + if (!Array.isArray(self.lineList)) return
  701 + self.lineList = self.lineList.map(function (row) {
  702 + var actual = self.toNumber(row.actualPurchasePrice)
  703 + var including = actual + sc
  704 + row.includingShippingPrice = self.toFixed(including, 2)
  705 + var priceList = self.toNumber(row.priceListPrices)
  706 + var loss = self.toNumber(row.estimatedLoss)
  707 + var coord = including + loss - priceList
  708 + row.coordinationRange = self.toFixed(coord, 2)
  709 + return row
  710 + })
  711 + },
  712 + recalcLine(idx) {
  713 + var row = this.lineList[idx]
  714 + if (!row) return
  715 + var actual = this.toNumber(row.actualPurchasePrice)
  716 + var including = actual + this.shippingCostNumber
  717 + this.$set(row, 'includingShippingPrice', this.toFixed(including, 2))
  718 + var priceList = this.toNumber(row.priceListPrices)
  719 + var loss = this.toNumber(row.estimatedLoss)
  720 + var coord = including + loss - priceList
  721 + this.$set(row, 'coordinationRange', this.toFixed(coord, 2))
  722 + },
  723 + // ==================== 审核提交 ====================
  724 + checkForm() {
  725 + if (!this.canEdit) return true
  726 + if (!this.form.companyId) {
  727 + uni.showToast({ title: '请选择公司名称', icon: 'none' })
  728 + return false
  729 + }
  730 + if (!this.form.date) {
  731 + uni.showToast({ title: '请选择日期', icon: 'none' })
  732 + return false
  733 + }
  734 + if (!this.form.priceTableId) {
  735 + uni.showToast({ title: '请选择价格表编号', icon: 'none' })
  736 + return false
  737 + }
  738 + if (this.canEditWorkshop) {
  739 + var list = Array.isArray(this.lineList) ? this.lineList : []
  740 + for (var i = 0; i < list.length; i++) {
  741 + if (!list[i].workshopId) {
  742 + uni.showToast({ title: '请选择明细' + (i + 1) + '的分厂', icon: 'none' })
  743 + return false
  744 + }
  745 + }
  746 + }
  747 + return true
  748 + },
  749 + getFormValues() {
  750 + var self = this
  751 + var lines = Array.isArray(this.lineList) ? this.lineList : []
  752 + return {
  753 + companyId: this.form.companyId || '',
  754 + companyShortName: this.form.companyShortName || this.form.companyName || '',
  755 + date: this.form.date || '',
  756 + shippingCost: this.form.shippingCost != null && this.form.shippingCost !== '' ? this.toNumber(this.form.shippingCost) : null,
  757 + priceTableId: this.form.priceTableId || '',
  758 + priceTableCode: this.form.priceTableCode || '',
  759 + lineList: lines.map(function (r) {
  760 + return {
  761 + itemId: r.itemId || r.id || '',
  762 + workshopId: r.workshopId || '',
  763 + workshopName: r.workshopName || '',
  764 + priceListPrices: self.toNumber(r.priceListPrices),
  765 + includingShippingPrice: self.toNumber(r.includingShippingPrice),
  766 + coordinationRange: self.toNumber(r.coordinationRange)
  767 + }
  768 + })
  769 + }
  770 + }
  771 + }
  772 +}
  773 +</script>
  774 +<style lang="scss" scoped>
  775 +.detail-page {
  776 + background: #f3f3f3;
  777 +}
  778 +.scroll {
  779 + flex: 1;
  780 + padding: 8rpx 0 48rpx;
  781 +}
  782 +.title-header {
  783 + display: flex;
  784 + align-items: center;
  785 + padding: 24rpx 32rpx 12rpx;
  786 + background-color: #ffffff;
  787 + .title-header_icon {
  788 + width: 36rpx;
  789 + height: 36rpx;
  790 + margin-right: 12rpx;
  791 + }
  792 + span {
  793 + font-size: 34rpx;
  794 + font-weight: 600;
  795 + color: rgba(0, 0, 0, 0.9);
  796 + }
  797 +}
  798 +.section {
  799 + padding: 32rpx;
  800 + background: #fff;
  801 + margin-bottom: 20rpx;
  802 +}
  803 +.row {
  804 + display: flex;
  805 + margin-bottom: 28rpx;
  806 + align-items: center;
  807 + &:last-child { margin-bottom: 0; }
  808 + &.company {
  809 + font-size: 36rpx;
  810 + font-weight: 600;
  811 + color: rgba(0, 0, 0, 0.9);
  812 + padding-top: 8rpx;
  813 + line-height: 50rpx;
  814 + }
  815 + .label {
  816 + max-width: 420rpx;
  817 + margin-right: 20rpx;
  818 + line-height: 32rpx;
  819 + font-size: 28rpx;
  820 + color: rgba(0, 0, 0, 0.6);
  821 + &.required {
  822 + position: relative;
  823 + &::before {
  824 + content: '*';
  825 + color: #D54941;
  826 + margin-right: 6rpx;
  827 + }
  828 + }
  829 + }
  830 + .value {
  831 + flex: 1;
  832 + line-height: 32rpx;
  833 + font-size: 28rpx;
  834 + color: rgba(0, 0, 0, 0.9);
  835 + text-align: right;
  836 + word-break: break-all;
  837 + &.placeholder {
  838 + color: rgba(0, 0, 0, 0.4);
  839 + }
  840 + }
  841 +}
  842 +.select-value {
  843 + display: flex;
  844 + align-items: center;
  845 + justify-content: flex-end;
  846 + .arrow {
  847 + margin-left: 8rpx;
  848 + color: rgba(0, 0, 0, 0.4);
  849 + font-size: 30rpx;
  850 + }
  851 +}
  852 +.date-value {
  853 + ::v-deep .uni-datetime-picker {
  854 + display: inline-flex;
  855 + justify-content: flex-end;
  856 + .uni-datetime-picker__btn {
  857 + padding: 0;
  858 + border: none;
  859 + background: transparent;
  860 + font-size: 28rpx;
  861 + color: rgba(0, 0, 0, 0.9);
  862 + }
  863 + }
  864 +}
  865 +.line-input {
  866 + border-bottom: 1rpx solid #ddd;
  867 + padding: 4rpx 0;
  868 + text-align: right;
  869 + height: 40rpx;
  870 + min-height: 40rpx;
  871 + font-size: 28rpx;
  872 +}
  873 +.line-block {
  874 + padding-bottom: 24rpx;
  875 + margin-bottom: 24rpx;
  876 + border-bottom: 1rpx solid #f0f0f0;
  877 + &:last-child {
  878 + padding-bottom: 0;
  879 + margin-bottom: 0;
  880 + border-bottom: none;
  881 + }
  882 +}
  883 +.line-title {
  884 + font-size: 30rpx;
  885 + font-weight: 600;
  886 + color: rgba(0, 0, 0, 0.9);
  887 + margin-bottom: 20rpx;
  888 +}
  889 +.attach-list {
  890 + flex: 1;
  891 + display: flex;
  892 + flex-direction: column;
  893 + align-items: flex-end;
  894 + gap: 8rpx;
  895 +}
  896 +.attach-item {
  897 + display: flex;
  898 + align-items: center;
  899 + gap: 16rpx;
  900 + .attach-name {
  901 + font-size: 26rpx;
  902 + color: rgba(0, 0, 0, 0.9);
  903 + }
  904 + .attach-view {
  905 + font-size: 26rpx;
  906 + color: $theme-primary;
  907 + }
  908 +}
  909 +.material-group {
  910 + margin-top: 32rpx;
  911 + &:first-child { margin-top: 0; }
  912 +}
  913 +.material-label {
  914 + display: block;
  915 + font-size: 28rpx;
  916 + color: rgba(0, 0, 0, 0.6);
  917 + margin-bottom: 20rpx;
  918 +}
  919 +.tag-list {
  920 + display: flex;
  921 + flex-wrap: wrap;
  922 + gap: 16rpx;
  923 +}
  924 +.tag-btn {
  925 + padding: 12rpx 24rpx;
  926 + border: 1rpx solid #ddd;
  927 + border-radius: 8rpx;
  928 + font-size: 28rpx;
  929 + color: #999;
  930 + background: #f5f5f5;
  931 +}
  932 +.tag-btn.active {
  933 + background: $theme-primary;
  934 + border-color: $theme-primary;
  935 + color: #fff;
  936 +}
  937 +.remark-text {
  938 + font-size: 28rpx;
  939 + color: rgba(0, 0, 0, 0.9);
  940 + line-height: 1.6;
  941 + white-space: pre-wrap;
  942 +}
  943 +.empty-tip {
  944 + text-align: center;
  945 + font-size: 28rpx;
  946 + color: rgba(0, 0, 0, 0.4);
  947 + padding: 24rpx 0;
  948 +}
  949 +</style>
@@ -2,6 +2,7 @@ import CustomerDevelopViewer from '@/pages/dev_manage/viewer.vue' @@ -2,6 +2,7 @@ import CustomerDevelopViewer from '@/pages/dev_manage/viewer.vue'
2 import CustomerCreditViewer from '@/pages/credit_manage/viewer.vue' 2 import CustomerCreditViewer from '@/pages/credit_manage/viewer.vue'
3 import DomesticCustomerCreditViewer from '@/pages/domestic_trade/domesticCreditViewer.vue' 3 import DomesticCustomerCreditViewer from '@/pages/domestic_trade/domesticCreditViewer.vue'
4 import SalesmanQuotationViewer from '@/pages/sales-price/salesmanQuotationViewer.vue' 4 import SalesmanQuotationViewer from '@/pages/sales-price/salesmanQuotationViewer.vue'
  5 +import SchedulePlanChangeViewer from '@/pages/schedule-plan/schedulePlanChangeViewer.vue'
5 import ForeignTradeCreditViewer from '@/pages/foreign_trade/foreignTradeCreditViewer.vue' 6 import ForeignTradeCreditViewer from '@/pages/foreign_trade/foreignTradeCreditViewer.vue'
6 import RetailViewer from '@/pages/contract_retail/retailViewer.vue' 7 import RetailViewer from '@/pages/contract_retail/retailViewer.vue'
7 import ForeignStdViewer from '@/pages/contract_foreign_std/foreignStdViewer.vue' 8 import ForeignStdViewer from '@/pages/contract_foreign_std/foreignStdViewer.vue'
@@ -31,6 +32,7 @@ export default function registerComponents(Vue) { @@ -31,6 +32,7 @@ export default function registerComponents(Vue) {
31 Vue.component('CustomerCreditViewer', CustomerCreditViewer) 32 Vue.component('CustomerCreditViewer', CustomerCreditViewer)
32 Vue.component('DomesticCustomerCreditViewer', DomesticCustomerCreditViewer) 33 Vue.component('DomesticCustomerCreditViewer', DomesticCustomerCreditViewer)
33 Vue.component('SalesmanQuotationViewer', SalesmanQuotationViewer) 34 Vue.component('SalesmanQuotationViewer', SalesmanQuotationViewer)
  35 + Vue.component('SchedulePlanChangeViewer', SchedulePlanChangeViewer)
34 Vue.component('ForeignTradeCreditViewer', ForeignTradeCreditViewer) 36 Vue.component('ForeignTradeCreditViewer', ForeignTradeCreditViewer)
35 Vue.component('RetailViewer', RetailViewer) 37 Vue.component('RetailViewer', RetailViewer)
36 Vue.component('StandardContractViewer', StandardContractViewer) 38 Vue.component('StandardContractViewer', StandardContractViewer)
@@ -26,6 +26,12 @@ export const getSysFlowComponentPath = (bizFlag) => { @@ -26,6 +26,12 @@ export const getSysFlowComponentPath = (bizFlag) => {
26 case 'SALESMAN_QUOTATION_EDIT': // 业务员报价单 26 case 'SALESMAN_QUOTATION_EDIT': // 业务员报价单
27 componentPath = 'SalesmanQuotationViewer'; // 业务员报价单-审批 27 componentPath = 'SalesmanQuotationViewer'; // 业务员报价单-审批
28 break; 28 break;
  29 + case 'PLAN_CHANGE': // 采购计划变更单
  30 + componentPath = 'SchedulePlanChangeViewer'; // 采购计划变更单-审批详情
  31 + break;
  32 + case 'PLAN_CHANGE_EDIT': // 采购计划变更单
  33 + componentPath = 'SchedulePlanChangeViewer'; // 采购计划变更单-审批
  34 + break;
29 case 'CUSTOMER_CREDIT_OUT': // 客户资信(外贸) 35 case 'CUSTOMER_CREDIT_OUT': // 客户资信(外贸)
30 componentPath = 'ForeignTradeCreditViewer'; // 客户资信(外贸)-审批详情 36 componentPath = 'ForeignTradeCreditViewer'; // 客户资信(外贸)-审批详情
31 break; 37 break;