Commit 767e055083fc6f2c409b2cd7350addb55e429c92

Authored by gesilong
1 parent 11e3a55f

commit:计划单开发

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