Commit dc8188edf843054efa2739406c85e64bd5ee3d29

Authored by gesilong
1 parent 45e3bea4

commit: 加工标准合同联调

  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <uni-list-item title="编号">
  6 + <template v-slot:footer>
  7 + <uni-easyinput v-model="form.code" :inputBorder="false" disabled />
  8 + </template>
  9 + </uni-list-item>
  10 +
  11 + <uni-list-item class="select-item" :class="form.supplier ? 'is-filled' : 'is-empty'" clickable
  12 + @click="openSheet('supplier')" :rightText="displayLabel('supplierName')" showArrow>
  13 + <template v-slot:body>
  14 + <view class="item-title"><text class="required">*</text><text>承揽方</text></view>
  15 + </template>
  16 + </uni-list-item>
  17 +
  18 + <uni-list-item class="select-item" :class="form.buyer ? 'is-filled' : 'is-empty'" clickable
  19 + @click="openRelate('buyer')" :rightText="form.buyerName || '请选择定作方'" showArrow>
  20 + <template v-slot:body>
  21 + <view class="item-title"><text class="required">*</text><text>定作方</text></view>
  22 + </template>
  23 + </uni-list-item>
  24 +
  25 + <uni-list-item title="订货日期">
  26 + <template v-slot:footer>
  27 + <uni-datetime-picker type="date" v-model="form.orderDate" />
  28 + </template>
  29 + </uni-list-item>
  30 +
  31 + <uni-list-item title="单位">
  32 + <template v-slot:footer>
  33 + <uni-easyinput v-model="form.unit" :inputBorder="false" disabled />
  34 + </template>
  35 + </uni-list-item>
  36 + <uni-list-item class="select-item" :class="form.workshopId ? 'is-filled' : 'is-empty'" clickable
  37 + @click="openSheet('workshopId')" :rightText="displayLabel('workshopIdName')" showArrow>
  38 + <template v-slot:body>
  39 + <view class="item-title"><text class="required">*</text><text>生产厂</text></view>
  40 + </template>
  41 + </uni-list-item>
  42 + <!-- <ProductRel mode="add" :orderDateBase="form.orderDate" @change="onProductsChange" :options="productList" :rawToProdRatioList="rawToProdRatioList" /> -->
  43 + <uni-list-item title="合计人民币金额(大写)">
  44 + <template v-slot:footer>
  45 + <uni-easyinput v-model="form.totalAmountCapital" placeholder="自动计算" :inputBorder="false" disabled />
  46 + </template>
  47 + </uni-list-item>
  48 + <uni-list-item title="交付定金、数额、时间">
  49 + <template v-slot:footer>
  50 + <uni-easyinput v-model="form.depositInfo" placeholder="请输入交付定金、数额、时间" :inputBorder="false" />
  51 + </template>
  52 + </uni-list-item>
  53 + <uni-list-item title="包装要求">
  54 + <template v-slot:footer>
  55 + <uni-easyinput v-model="form.packagingRequirements" placeholder="请输入包装要求"
  56 + :inputBorder="false" />
  57 + </template>
  58 + </uni-list-item>
  59 + <uni-list-item title="付款方式、付款期限">
  60 + <template v-slot:footer>
  61 + <uni-easyinput v-model="form.paymentTerms" placeholder="请输入付款方式、付款期限" :inputBorder="false" />
  62 + </template>
  63 + </uni-list-item>
  64 + <uni-list-item title="运输方式">
  65 + <template v-slot:footer>
  66 + <uni-easyinput v-model="form.transportMode" placeholder="请输入运输方式" :inputBorder="false" />
  67 + </template>
  68 + </uni-list-item>
  69 +
  70 + <uni-list-item class="select-item" :class="(Array.isArray(form.destinationId) && form.destinationId.length) ? 'is-filled' : 'is-empty'" clickable
  71 + @click="openCitySelector" :rightText="form.destinationLabel || '请选择'" showArrow>
  72 + <template v-slot:body>
  73 + <view class="item-title"><text>目的地</text></view>
  74 + <CitySelector ref="citySelectorRef" v-model="form.destinationId" @change="onCityChange" />
  75 + </template>
  76 + </uni-list-item>
  77 + <uni-list-item class="select-item" :class="form.includesPackagingFeeName ? 'is-filled' : 'is-empty'" clickable
  78 + @click="openSheet('includesPackagingFee')" :rightText="form.includesPackagingFeeName || '请选择'" showArrow>
  79 + <template v-slot:body>
  80 + <view class="item-title"><text>单价中是否已包含包装费</text></view>
  81 + </template>
  82 + </uni-list-item>
  83 + <uni-list-item class="select-item" :class="form.includesTransportFeeName ? 'is-filled' : 'is-empty'" clickable
  84 + @click="openSheet('includesTransportFee')" :rightText="form.includesTransportFeeName || '请选择'" showArrow>
  85 + <template v-slot:body>
  86 + <view class="item-title"><text>单价中是否已包含运费</text></view>
  87 + </template>
  88 + </uni-list-item>
  89 + <uni-list-item title="定作方指定收货人">
  90 + <template v-slot:footer>
  91 + <uni-easyinput v-model="form.designatedConsignee" placeholder="请输入定作方指定收货人"
  92 + :inputBorder="false" />
  93 + </template>
  94 + </uni-list-item>
  95 + <view class="group">
  96 + <view class="group-title">特别条款要求</view>
  97 + <view class="radio-list">
  98 + <view v-for="(opt, i) in specialTermsList" :key="'cr-' + i" class="radio-item"
  99 + @click="onRadioSelect('specialTerms', 'specialTermsName', opt)">
  100 + <view :class="['radio', { checked: form.specialTerms === opt.value }]" />
  101 + <text class="label">{{ opt.label }}</text>
  102 + </view>
  103 + </view>
  104 + </view>
  105 + <view class="group">
  106 + <view class="group-title">执行标准</view>
  107 + <view class="radio-list">
  108 + <view v-for="(opt, i) in executionStandardList" :key="'es-' + i" class="radio-item"
  109 + @click="onRadioSelect('executionStandard', 'executionStandardName', opt)">
  110 + <view :class="['radio', { checked: form.executionStandard === opt.value }]" />
  111 + <text class="label">{{ opt.label }}</text>
  112 + </view>
  113 + </view>
  114 + </view>
  115 + <uni-list-item v-if="form.executionStandard === 'OTHER'" title="其他">
  116 + <template v-slot:footer>
  117 + <uni-easyinput v-model="form.executionStandardRemarks" placeholder="请输入其他标准备注"
  118 + :inputBorder="false" />
  119 + </template>
  120 + </uni-list-item>
  121 + <uni-list-item title="特别说明">
  122 + <template v-slot:footer>
  123 + <uni-easyinput v-model="form.specialInstructions" placeholder="请输入特别说明" :inputBorder="false" />
  124 + </template>
  125 + </uni-list-item>
  126 + <uni-list-item title="备注">
  127 + <template v-slot:footer>
  128 + <uni-easyinput v-model="form.remarks" placeholder="请输入备注" :inputBorder="false" />
  129 + </template>
  130 + </uni-list-item>
  131 + <view class="quality">
  132 + <image class="opCollapse" src="/static/images/title.png" />
  133 + <text class="title">具体质量要求</text>
  134 + </view>
  135 + <uni-list-item title="件重条头">
  136 + <template v-slot:footer>
  137 + <uni-easyinput v-model="form.pieceWeightHead" placeholder="请输入" :inputBorder="false" />
  138 + </template>
  139 + </uni-list-item>
  140 + <uni-list-item title="表面">
  141 + <template v-slot:footer>
  142 + <uni-easyinput v-model="form.surface" placeholder="请输入" :inputBorder="false" />
  143 + </template>
  144 + </uni-list-item>
  145 + <uni-list-item title="公差">
  146 + <template v-slot:footer>
  147 + <uni-easyinput v-model="form.tolerance" placeholder="请输入" :inputBorder="false" />
  148 + </template>
  149 + </uni-list-item>
  150 + <uni-list-item title="性能">
  151 + <template v-slot:footer>
  152 + <uni-easyinput v-model="form.performance" placeholder="请输入" :inputBorder="false" />
  153 + </template>
  154 + </uni-list-item>
  155 + <uni-list-item title="成分">
  156 + <template v-slot:footer>
  157 + <uni-easyinput v-model="form.component" placeholder="请输入" :inputBorder="false" />
  158 + </template>
  159 + </uni-list-item>
  160 + <uni-list-item title="包装">
  161 + <template v-slot:footer>
  162 + <uni-easyinput v-model="form.packaging" placeholder="请输入" :inputBorder="false" />
  163 + </template>
  164 + </uni-list-item>
  165 + </uni-list>
  166 +
  167 + </scroll-view>
  168 + <view class="footer">
  169 + <div class="total">
  170 + <div class="total-text">
  171 + 合计
  172 + </div>
  173 + <div class="total-item">
  174 + <div class="total-item-text">
  175 + 数量
  176 + </div>
  177 + <div class="total-item-price">
  178 + {{ (sumQuantity || 0).toFixed(2) }}t
  179 + </div>
  180 + </div>
  181 + <div class="total-item">
  182 + <div class="total-item-text">
  183 + 不含税金额
  184 + </div>
  185 + <div class="total-item-price text-red">
  186 + ¥{{ (sumAmountExcl || 0).toFixed(2) }}
  187 + </div>
  188 + </div>
  189 + <div class="total-item">
  190 + <div class="total-item-text">
  191 + 总金额
  192 + </div>
  193 + <div class="total-item-price text-red">
  194 + ¥{{ (sumTotal || 0).toFixed(2) }}
  195 + </div>
  196 + </div>
  197 + </div>
  198 + <button class="btn submit" type="primary" @click="onSubmit">提交</button>
  199 + </view>
  200 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  201 + v-model="sheet.value" @confirm="onSheetConfirm" />
  202 + <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
  203 + :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
  204 + :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
  205 + </view>
  206 +
  207 +</template>
  208 +<script>
  209 +import SingleSelectSheet from '@/components/single-select/index.vue'
  210 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  211 +//import ProductRel from './productRel.vue'
  212 +import CitySelector from '@/components/city-selector/index.vue'
  213 +import { getRetailCodeApi, createContractApi } from '@/api/contract'
  214 +import { getDicByCodes } from '@/utils/dic'
  215 +import { formatCurrencyToChinese } from '@/utils/common'
  216 +import { workshopQueryApi } from '@/api/devManage'
  217 +
  218 +export default {
  219 + name: 'AddContractProcess',
  220 + components: { SingleSelectSheet, RelateSelectSheet, /* ProductRel, */ CitySelector },
  221 + data() {
  222 + return {
  223 + form: {
  224 + code: '',
  225 + supplier: '',
  226 + supplierName: '',
  227 + buyer: '',
  228 + buyerName: '',
  229 + orderDate: '',
  230 + designatedConsignee: '',
  231 + specialTerms: '',
  232 + specialTermsName: '',
  233 + executionStandard: '',
  234 + executionStandardName: '',
  235 + executionStandardRemarks: '',
  236 + includesPackagingFee: false,
  237 + includesPackagingFeeName: '',
  238 + includesTransportFee: false,
  239 + includesTransportFeeName: '',
  240 + unit: '元、公斤、元/公斤',
  241 + totalAmountCapital: '',
  242 + destinationId: [],
  243 + destinationLabel: '',
  244 + workshopIdName: '',
  245 + workshopId: '',
  246 + },
  247 + supplierList: [],
  248 + specialTermsList: [],
  249 + executionStandardList: [],
  250 + yesNoList: [{ label: '是', value: true }, { label: '否', value: false }],
  251 + sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  252 + relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' },
  253 + sumQuantity: 0,
  254 + sumAmountExcl: 0,
  255 + sumTotal: 0,
  256 + productLineList: [],
  257 + productList: [],
  258 + rawToProdRatioList: [],
  259 + }
  260 + },
  261 + created() {
  262 + this.loadSuppliers()
  263 + this.loadExtraOptions()
  264 + this.initCode()
  265 + this.form.orderDate = this.formatDate(new Date())
  266 + this.$nextTick(() => {
  267 + if (Array.isArray(this.form.destinationId) && this.form.destinationId.length) {
  268 + this.initDestinationLabel()
  269 + }
  270 + })
  271 + },
  272 + methods: {
  273 + onProductsChange(products) {
  274 + const list = Array.isArray(products) ? products : []
  275 + const sumQ = list.reduce((acc, it) => acc + (parseFloat(it.quantity) || 0), 0)
  276 + const sumE = list.reduce((acc, it) => acc + (parseFloat(it.amountExcludingTax) || 0), 0)
  277 + const sumT = list.reduce((acc, it) => acc + (parseFloat(it.totalAmount) || 0), 0)
  278 + this.sumQuantity = sumQ
  279 + this.sumAmountExcl = sumE
  280 + this.sumTotal = sumT
  281 + this.form.totalAmountCapital = formatCurrencyToChinese(sumT)
  282 + this.productLineList = list
  283 + },
  284 + formatDate(d) {
  285 + const y = d.getFullYear()
  286 + const m = String(d.getMonth() + 1).padStart(2, '0')
  287 + const day = String(d.getDate()).padStart(2, '0')
  288 + return `${y}-${m}-${day}`
  289 + },
  290 + async initCode() {
  291 + try {
  292 + const res = await getRetailCodeApi()
  293 + const code = (res && res.data) ? res.data : ''
  294 + this.form.code = code
  295 + } catch (e) { this.form.code = '' }
  296 + },
  297 + async loadSuppliers() {
  298 + try {
  299 + const results = await getDicByCodes(['SUPPLIER'])
  300 + const items = results && results.SUPPLIER && results.SUPPLIER.data ? results.SUPPLIER.data : []
  301 + this.supplierList = items.map(it => ({ label: it.name, value: it.code }))
  302 + } catch (e) { this.supplierList = [] }
  303 + },
  304 + async loadExtraOptions() {
  305 + try {
  306 + const results = await getDicByCodes(['CONDITIONS_REQUIRED', 'APPLICABLE_STANDARD', 'CONTRACT_PRODUCT','RAW_TO_PROD_RATIO'])
  307 + const c1 = results && results.CONDITIONS_REQUIRED && results.CONDITIONS_REQUIRED.data ? results.CONDITIONS_REQUIRED.data : []
  308 + const c2 = results && results.APPLICABLE_STANDARD && results.APPLICABLE_STANDARD.data ? results.APPLICABLE_STANDARD.data : []
  309 + const c3 = results && results.CONTRACT_PRODUCT && results.CONTRACT_PRODUCT.data ? results.CONTRACT_PRODUCT.data : []
  310 + const c4 = results && results.RAW_TO_PROD_RATIO && results.RAW_TO_PROD_RATIO.data ? results.RAW_TO_PROD_RATIO.data : []
  311 + this.specialTermsList = c1.map(it => ({ label: it.name, value: it.code }))
  312 + this.executionStandardList = c2.map(it => ({ label: it.name, value: it.code }))
  313 + this.productList = c3.map(it => ({ label: it.name, value: it.code }))
  314 + this.rawToProdRatioList = c4.map(it => ({ label: it.name, value: it.code }))
  315 + } catch (e) {
  316 + this.specialTermsList = []
  317 + this.executionStandardList = []
  318 + this.productList = []
  319 + this.rawToProdRatioList = []
  320 + }
  321 + },
  322 + displayLabel(field) {
  323 + const m = this.form
  324 + const map = { supplierName: '请选择承揽方', buyerName: '请选择定作方', workshopIdName: '请选择生产厂' }
  325 + const val = m[field]
  326 + return val ? String(val) : map[field]
  327 + },
  328 + openCitySelector() {
  329 + this.$refs.citySelectorRef && this.$refs.citySelectorRef.open()
  330 + },
  331 + async initDestinationLabel() {
  332 + const comp = this.$refs.citySelectorRef
  333 + if (comp && typeof comp.getLabel === 'function') {
  334 + const label = await comp.getLabel()
  335 + this.form.destinationLabel = label || ''
  336 + }
  337 + },
  338 + onCityChange(payload) {
  339 + const label = payload && payload.label != null ? payload.label : ''
  340 + this.form.destinationLabel = label
  341 + },
  342 + async openSheet(field) {
  343 + const setSheet = (title, options) => {
  344 + const current = this.form[field]
  345 + const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
  346 + this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
  347 + }
  348 + if (field === 'workshopId') {
  349 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  350 + const _data = res.data || {}
  351 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  352 + const opts = (list || []).map(it => ({
  353 + label: it.name ,
  354 + value: it.id
  355 + }))
  356 + setSheet('生产厂', opts)
  357 + } else if (field === 'supplier') {
  358 + setSheet('承揽方', this.supplierList)
  359 + } else if (field === 'includesPackagingFee') {
  360 + setSheet('单价中是否已包含包装费', this.yesNoList)
  361 + } else if (field === 'includesTransportFee') {
  362 + setSheet('单价中是否已包含运费', this.yesNoList)
  363 + }
  364 + },
  365 + onSheetConfirm({ value, label }) {
  366 + const field = this.sheet.field
  367 + if (!field) return
  368 + const v = (value === undefined || value === null) ? '' : value
  369 + this.form[field] = v
  370 + this.form[field + 'Name'] = label || ''
  371 + },
  372 + openRelate(fieldKey) {
  373 + let config = {}
  374 + if (fieldKey === 'buyer') {
  375 + config = { title: '定作方', source: 'customer', rowKey: 'id', multiple: false, display: [{ label: '姓名', field: 'name' }, { label: '编号', field: 'code' }, { label: '状态', field: 'available', format: v => (v ? '启用' : '停用') }] }
  376 + }
  377 + const selectedKeys = this.form[fieldKey] ? [this.form[fieldKey]] : []
  378 + this.sheet.visible = false
  379 + this.relate = { ...this.relate, title: config.title, source: config.source, display: config.display, multiple: config.multiple, rowKey: config.rowKey, selectedKeys, fieldKey }
  380 + this.$nextTick(() => { this.relate.visible = true })
  381 + },
  382 + onRelateConfirm({ items }) {
  383 + const _fieldKey = this.relate.fieldKey
  384 + const first = (items && items.length > 0) ? items[0] : null
  385 + this.form[_fieldKey] = (first && first.id) ? first.id : ''
  386 + this.form[_fieldKey + 'Name'] = (first && first.name) ? first.name : ''
  387 + },
  388 + onRadioSelect(field, nameField, opt) {
  389 + const val = opt && opt.value != null ? opt.value : ''
  390 + const label = opt && opt.label != null ? opt.label : ''
  391 + this.form[field] = val
  392 + this.form[nameField] = label
  393 + if (field === 'executionStandard' && val !== 'OTHER') {
  394 + this.form.executionStandardRemarks = ''
  395 + }
  396 + },
  397 + async onSubmit() {
  398 + if (!this.validateRequired()) return
  399 + const confirmRes = await new Promise(resolve => {
  400 + uni.showModal({ title: '提示', content: '确定新增经销未锁规合同吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  401 + })
  402 + if (!(confirmRes && confirmRes.confirm)) return
  403 + const clean = (obj) => {
  404 + const out = {}
  405 + Object.keys(obj || {}).forEach(k => {
  406 + const v = obj[k]
  407 + const isEmptyString = typeof v === 'string' && v.trim() === ''
  408 + const isUndef = v === undefined || v === null
  409 + const isNaNNumber = typeof v === 'number' && isNaN(v)
  410 + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v
  411 + })
  412 + return out
  413 + }
  414 + const lines = (this.productLineList || []).map(it => clean(it))
  415 + const { destinationLabel, destinationId, ...formForSubmit } = this.form;
  416 + // 区id
  417 + const destination = destinationId && destinationId.length > 0 ? destinationId[destinationId.length - 1] : '';
  418 + const payload = clean({
  419 + ...formForSubmit,
  420 + destination,
  421 + type: 'PROCESS_STD_AGMT',
  422 + sumQuantity: this.sumQuantity,
  423 + sumAmountExcl: this.sumAmountExcl,
  424 + sumTotal: this.sumTotal,
  425 + contractDistributorLineList: lines
  426 + })
  427 + console.log('onSubmit__payload', payload)
  428 +
  429 + try {
  430 + await createContractApi(payload)
  431 + uni.showToast({ title: '新增成功', icon: 'none' })
  432 + setTimeout(() => { uni.redirectTo({ url: '/pages/contract_process/index' }) }, 400)
  433 + } catch (e) {
  434 + uni.showToast({ title: '提交失败', icon: 'none' })
  435 + }
  436 + },
  437 + validateRequired() {
  438 + const checks = [
  439 + { key: 'code', label: '编号' },
  440 + { key: 'supplier', label: '承揽方' },
  441 + { key: 'buyer', label: '定作方' },
  442 + { key: 'orderDate', label: '订货日期' },
  443 + { key: 'workshopId', label: '生产厂' },
  444 + ]
  445 + for (const it of checks) {
  446 + const val = this.form[it.key]
  447 + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '') || (typeof val === 'number' && isNaN(val)))
  448 + if (empty) { uni.showToast({ title: `请先选择${it.label}`, icon: 'none' }); return false }
  449 + }
  450 + if (!Array.isArray(this.productLineList) || this.productLineList.length === 0) {
  451 + uni.showToast({ title: '请至少添加一条产品明细', icon: 'none' }); return false
  452 + }
  453 + for (const [idx, it] of this.productLineList.entries()) {
  454 + if (!it.productName || !it.quantity || !it.unitPrice) {
  455 + uni.showToast({ title: `第${idx + 1}条明细未完整填写`, icon: 'none' }); return false
  456 + }
  457 + }
  458 + return true
  459 + }
  460 + }
  461 +}
  462 +
  463 +</script>
  464 +<style lang="scss" scoped>
  465 +.total {
  466 + .total-text {
  467 + font-weight: 600;
  468 + font-size: 32rpx;
  469 + color: rgba(0, 0, 0, 0.9);
  470 + padding-bottom: 28rpx;
  471 + border-bottom: 2rpx solid #E7E7E7;
  472 + }
  473 + .total-item {
  474 + display: flex;
  475 + align-items: center;
  476 + .total-item-text {
  477 + font-weight: 400;
  478 + font-size: 28rpx;
  479 + color: rgba(0, 0, 0, 0.6);
  480 + line-height: 32rpx;
  481 + width: 240rpx;
  482 + padding: 24rpx 0;
  483 + }
  484 + .total-item-price {
  485 + font-weight: 600;
  486 + font-size: 32rpx;
  487 + color: rgba(0, 0, 0, 0.9);
  488 + line-height: 32rpx;
  489 + }
  490 + .text-red {
  491 + color: #D54941;
  492 + }
  493 + }
  494 +
  495 +}
  496 +.page {
  497 + display: flex;
  498 + flex-direction: column;
  499 + height: 100%;
  500 +}
  501 +
  502 +.scroll {
  503 + flex: 1;
  504 + padding: 12rpx 0 480rpx !important;
  505 +}
  506 +
  507 +.footer {
  508 + z-index: 2;
  509 + position: fixed;
  510 + left: 0;
  511 + right: 0;
  512 + bottom: 0;
  513 + padding: 32rpx;
  514 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  515 + background: #fff;
  516 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  517 +
  518 + .btn {
  519 + height: 80rpx;
  520 + line-height: 80rpx;
  521 + border-radius: 12rpx;
  522 + font-size: 32rpx;
  523 + }
  524 +
  525 + .submit {
  526 + background: $theme-primary;
  527 + color: #fff;
  528 + }
  529 +}
  530 +
  531 +.group {
  532 + background: #fff;
  533 + padding: 24rpx 32rpx;
  534 + margin-bottom: 20rpx;
  535 +}
  536 +
  537 +.group-title {
  538 + color: rgba(0, 0, 0, 0.9);
  539 + font-size: 32rpx;
  540 + padding-bottom: 16rpx;
  541 +}
  542 +
  543 +.radio-list {
  544 + display: flex;
  545 + flex-direction: column;
  546 +}
  547 +
  548 +.radio-item {
  549 + display: flex;
  550 + align-items: center;
  551 + padding: 24rpx 0;
  552 + border-bottom: 1rpx solid #f0f0f0;
  553 +}
  554 +
  555 +.radio-item:last-child {
  556 + border-bottom: none;
  557 +}
  558 +
  559 +.radio {
  560 + width: 32rpx;
  561 + height: 32rpx;
  562 + border-radius: 50%;
  563 + border: 2rpx solid #DCDCDC;
  564 + margin-right: 20rpx;
  565 + box-sizing: border-box;
  566 +}
  567 +
  568 +.radio.checked {
  569 + background: $theme-primary;
  570 + border-color: $theme-primary;
  571 +}
  572 +
  573 +.radio-item .label {
  574 + font-size: 32rpx;
  575 + color: rgba(0, 0, 0, 0.9);
  576 +}
  577 +
  578 +.scroll {
  579 + flex: 1;
  580 + padding: 12rpx 0 160rpx;
  581 +}
  582 +
  583 +.quality {
  584 + background-color: #fff;
  585 + display: flex;
  586 + align-items: center;
  587 + padding: 24rpx 32rpx;
  588 + border-bottom: 1rpx solid #f0f0f0;
  589 + margin-top: 20rpx;
  590 +
  591 + .title {
  592 + font-size: 32rpx;
  593 + color: rgba(0, 0, 0, 0.9);
  594 + font-weight: 600;
  595 + }
  596 +
  597 + .opCollapse {
  598 + color: rgba(0, 0, 0, 0.6);
  599 + width: 24rpx;
  600 + height: 24rpx;
  601 + margin-right: 16rpx;
  602 + margin-top: 8rpx;
  603 + }
  604 +}
  605 +
  606 +::v-deep .uni-list {
  607 + .uni-easyinput {
  608 + display: flex;
  609 +
  610 + .uni-input-input {
  611 + color: rgba(0, 0, 0, 0.9);
  612 + }
  613 + }
  614 +
  615 + .uni-input-placeholder {
  616 + z-index: 1;
  617 + }
  618 +
  619 + .uni-input-input {
  620 + background-color: #ffffff;
  621 + }
  622 +
  623 + background: transparent;
  624 +
  625 + &-item {
  626 + &__extra-text {
  627 + font-size: 32rpx;
  628 + }
  629 +
  630 + &__content-title {
  631 + font-size: 32rpx;
  632 + color: rgba(0, 0, 0, 0.9);
  633 + }
  634 +
  635 + &__container {
  636 + padding: 32rpx;
  637 +
  638 + .uni-easyinput {
  639 + &__placeholder-class {
  640 + font-size: 32rpx;
  641 + color: rgba(0, 0, 0, 0.4);
  642 + }
  643 +
  644 + &__content {
  645 + border: none;
  646 + background-color: #ffffff !important;
  647 +
  648 + &-input {
  649 + padding-left: 0 !important;
  650 + height: 48rpx;
  651 + line-height: 48rpx;
  652 + font-size: 32rpx;
  653 + }
  654 +
  655 + .content-clear-icon {
  656 + font-size: 44rpx !important;
  657 + }
  658 + }
  659 + }
  660 +
  661 + .item-title,
  662 + .uni-list-item__content {
  663 + flex: none;
  664 + min-height: 48rpx;
  665 + line-height: 48rpx;
  666 + font-size: 32rpx;
  667 + position: relative;
  668 + width: 162rpx;
  669 + margin-right: 32rpx;
  670 + color: rgba(0, 0, 0, 0.9);
  671 +
  672 + .required {
  673 + color: red;
  674 + position: absolute;
  675 + top: 50%;
  676 + transform: translateY(-50%);
  677 + left: -16rpx;
  678 + }
  679 + }
  680 + }
  681 +
  682 + &.select-item {
  683 + &.is-empty {
  684 + .uni-list-item__extra-text {
  685 + color: rgba(0, 0, 0, 0.4) !important;
  686 + }
  687 + }
  688 +
  689 + &.is-filled {
  690 + .uni-list-item__extra-text {
  691 + color: rgba(0, 0, 0, 0.9) !important;
  692 + }
  693 + }
  694 + }
  695 +
  696 + &.mgb10 {
  697 + margin-bottom: 20rpx;
  698 + }
  699 + }
  700 +}
  701 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row customer">{{ detail.code }}</text>
  7 + <view class="row"><text class="label">承揽方</text><text class="value">{{ detail.supplierName || '-'
  8 + }}</text></view>
  9 + <view class="row"><text class="label">定作方</text><text class="value">{{ detail.buyerName || '-'
  10 + }}</text></view>
  11 + <view class="row"><text class="label">订货日期</text><text class="value">{{ detail.orderDate }}</text>
  12 + </view>
  13 + <view class="row"><text class="label">单位</text><text class="value">{{ detail.unit }}</text></view>
  14 + <view class="row"><text class="label">生产厂</text><text class="value">{{ detail.workshopName || '-'
  15 + }}</text></view>
  16 + </view>
  17 +
  18 + <view class="section1">
  19 + <ProductRel mode="view" :list="productList" />
  20 + </view>
  21 +
  22 + <view class="section">
  23 + <view class="row"><text class="label">合计人民币金额(大写)</text><text class="value">{{
  24 + detail.totalAmountCapital || '-' }}</text></view>
  25 + <view class="row"><text class="label">交付定金、数额、时间</text><text class="value">{{ detail.depositInfo ||
  26 + '-' }}</text></view>
  27 + <view class="row"><text class="label">包装要求</text><text class="value">{{ detail.packagingRequirements
  28 + || '-' }}</text></view>
  29 + <view class="row"><text class="label">付款方式、付款期限</text><text class="value">{{ detail.paymentTerms ||
  30 + '-' }}</text></view>
  31 + <view class="row"><text class="label">运输方式</text><text class="value">{{ detail.transportMode || '-'
  32 + }}</text></view>
  33 + <view class="row"><text class="label">目的地</text><text class="value">{{ detail.destinationLabel || '-'
  34 + }}</text></view>
  35 + <view class="row"><text class="label">单价中是否已包含包装费</text><text class="value">{{
  36 + detail.includesPackagingFeeName || '-' }}</text></view>
  37 + <view class="row"><text class="label">单价中是否已包含运费</text><text class="value">{{
  38 + detail.includesTransportFeeName || '-' }}</text></view>
  39 + <view class="row"><text class="label">需方指定收货人</text><text class="value">{{
  40 + detail.designatedConsignee || '-' }}</text></view>
  41 +
  42 + <view class="row"><text class="label">特别条款要求</text><text class="value">{{ detail.specialTermsName ||
  43 + '-' }}</text></view>
  44 + <view class="row"><text class="label">执行标准</text><text class="value">{{ detail.executionStandardName
  45 + || '-' }}</text></view>
  46 + <view class="row" v-if="detail.executionStandard === 'OTHER'"><text class="label">其他</text><text
  47 + class="value">{{ detail.executionStandardRemarks || '-' }}</text></view>
  48 + <view class="row"><text class="label">特别说明</text><text class="value">{{ detail.specialInstructions
  49 + || '-' }}</text></view>
  50 + <view class="row"><text class="label">备注</text><text class="value">{{ detail.remarks || '-'
  51 + }}</text></view>
  52 + </view>
  53 +
  54 + <view class="section">
  55 + <view class="row"><text class="label">规范性合同</text><text class="value">{{ detail.standardFileName || '-'
  56 + }}</text></view>
  57 + <view class="row"><text class="label">合同是否规范</text><text class="value">{{ detail.standardStandardized ? '是' : '否'
  58 + }}</text></view>
  59 + </view>
  60 +
  61 + <view class="section">
  62 + <text class="row customer">具体质量要求</text>
  63 + <view class="row"><text class="label">件重条头</text><text class="value">{{ detail.pieceWeightHead ||
  64 + '-' }}</text></view>
  65 + <view class="row"><text class="label">表面</text><text class="value">{{ detail.surface || '-'
  66 + }}</text></view>
  67 + <view class="row"><text class="label">公差</text><text class="value">{{ detail.tolerance || '-'
  68 + }}</text></view>
  69 + <view class="row"><text class="label">性能</text><text class="value">{{ detail.performance || '-'
  70 + }}</text></view>
  71 + <view class="row"><text class="label">成分</text><text class="value">{{ detail.component || '-'
  72 + }}</text></view>
  73 + <view class="row"><text class="label">包装</text><text class="value">{{ detail.packaging || '-'
  74 + }}</text></view>
  75 + </view>
  76 + </view>
  77 + </scroll-view>
  78 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
  79 + </view>
  80 +</template>
  81 +
  82 +<script>
  83 +import { getContractApi, deleteContractApi } from '@/api/contract'
  84 +import ProductRel from './productRel.vue'
  85 +import DetailButtons from '@/components/detail-buttons/index.vue'
  86 +
  87 +export default {
  88 + name: 'ContractProcessDetail',
  89 + components: { ProductRel, DetailButtons },
  90 + data() {
  91 + return {
  92 + id: '',
  93 + detail: {
  94 + code: '',
  95 + supplier: '',
  96 + supplierName: '',
  97 + buyer: '',
  98 + buyerName: '',
  99 + orderDate: '',
  100 + unit: '',
  101 + workshopId: '',
  102 + workshopName: '',
  103 + designatedConsignee: '',
  104 + specialTerms: '',
  105 + specialTermsName: '',
  106 + executionStandard: '',
  107 + executionStandardName: '',
  108 + executionStandardRemarks: '',
  109 + includesPackagingFee: false,
  110 + includesPackagingFeeName: '',
  111 + includesTransportFee: false,
  112 + includesTransportFeeName: '',
  113 + totalAmountCapital: '',
  114 + depositInfo: '',
  115 + packagingRequirements: '',
  116 + paymentTerms: '',
  117 + transportMode: '',
  118 + destinationId: '',
  119 + destinationLabel: '',
  120 + specialInstructions: '',
  121 + remarks: '',
  122 + pieceWeightHead: '',
  123 + surface: '',
  124 + tolerance: '',
  125 + performance: '',
  126 + component: '',
  127 + packaging: ''
  128 + },
  129 + productList: [],
  130 + buttons: [{
  131 + text: '编辑',
  132 + visible: true,
  133 + variant: 'outline',
  134 + event: 'edit',
  135 + },
  136 + {
  137 + text: '删除',
  138 + visible: true,
  139 + variant: 'outline',
  140 + event: 'delete',
  141 + style: {
  142 + color: '#D54941',
  143 + border: '1px solid #D54941'
  144 + }
  145 + },
  146 + {
  147 + text: '上传合同附件',
  148 + visible: true,
  149 + variant: 'outline',
  150 + event: 'upload'
  151 + },
  152 + {
  153 + text: '审核',
  154 + visible: true,
  155 + variant: 'primary',
  156 + event: 'audit'
  157 + },
  158 + {
  159 + text: '审核详情',
  160 + visible: true,
  161 + variant: 'primary',
  162 + event: 'auditDetail'
  163 + },
  164 + ],
  165 + }
  166 + },
  167 + computed: {
  168 + displayButtons() {
  169 + const s = this.detail && this.detail.status || ''
  170 + const t = this.detail.standardApproved || ''
  171 + return [
  172 + { ...this.buttons[0]},
  173 + // { ...this.buttons[0], visible: (s === 'DRAFT') },
  174 + { ...this.buttons[1], visible: (s === 'DRAFT') },
  175 + { ...this.buttons[2], visible: (s !== 'DELETED' && t !== 'AUDIT' && t !== 'PASS') },
  176 + { ...this.buttons[3], visible: (s === 'STANDARD' && t === 'AUDIT') },
  177 + { ...this.buttons[4], visible: (s === 'STANDARD') }
  178 + ]
  179 + }
  180 + },
  181 + onLoad(options) {
  182 + const id = options && options.id ? options.id : ''
  183 + this.id = id
  184 + this.loadDetail()
  185 + },
  186 + methods: {
  187 + onDelete() {
  188 + uni.showModal({
  189 + title: '确认删除',
  190 + content: '确认删除该合同吗?',
  191 + success: (res) => {
  192 + if (res.confirm) {
  193 + deleteContractApi(this.id).then(() => {
  194 + uni.showToast({
  195 + title: '删除成功',
  196 + icon: 'success'
  197 + })
  198 + setTimeout(() => {
  199 + uni.navigateTo({
  200 + url: '/pages/contract_retail/index'
  201 + })
  202 + }, 500)
  203 + }).catch(() => {
  204 + uni.showToast({
  205 + title: '删除失败',
  206 + icon: 'error'
  207 + })
  208 + })
  209 + }
  210 + }
  211 + })
  212 + },
  213 + onEdit() {
  214 + const id = this.detail.id || this.detail.code || ''
  215 + const query = id ? ('?id=' + encodeURIComponent(id)) : ''
  216 + uni.navigateTo({
  217 + url: '/pages/contract_process/modify' + query
  218 + })
  219 + },
  220 + handleButtonClick(btn) {
  221 + if (!btn || btn.disabled) return
  222 + if (typeof btn.onClick === 'function') return btn.onClick(this.detail, btn.params)
  223 + const e = btn.event || ''
  224 + if (e === 'edit') return this.onEdit(btn && btn.params)
  225 + if (e === 'delete') return this.onDelete(btn && btn.params)
  226 + // if (e === 'upload') return this.onUpload(btn && btn.params)
  227 + // if (e === 'audit') return this.onAudit(btn && btn.params)
  228 + // if (e === 'auditDetail') return this.onAuditDetail(btn && btn.params)
  229 + },
  230 + async loadDetail() {
  231 + if (!this.id) return
  232 + try {
  233 + const res = await getContractApi(this.id)
  234 + const data = res && res.data ? res.data : {}
  235 + const includesPackagingFeeName = data.includesPackagingFeeName || (data.includesPackagingFee ? '是' : '否')
  236 + const includesTransportFeeName = data.includesTransportFeeName || (data.includesTransportFee ? '是' : '否')
  237 + this.detail = {
  238 + ...this.detail,
  239 + ...data,
  240 + includesPackagingFeeName, includesTransportFeeName,
  241 + destinationId: data.provinceId && data.cityId && data.districtId ? [data.provinceId, data.cityId, data.districtId] : '',
  242 + destinationLabel: data.provinceName && data.cityName && data.districtName ? `${data.provinceName} / ${data.cityName} / ${data.districtName}` : '',
  243 + }
  244 + const lines = Array.isArray(data.contractDistributorLineList) ? data.contractDistributorLineList : []
  245 + this.productList = lines
  246 + } catch (e) {
  247 + this.detail = { ...this.detail }
  248 + this.productList = []
  249 + }
  250 + }
  251 + }
  252 +}
  253 +</script>
  254 +
  255 +<style lang="scss" scoped>
  256 +.page {
  257 + display: flex;
  258 + flex-direction: column;
  259 + height: 100%;
  260 +}
  261 +
  262 +.scroll {
  263 + flex: 1;
  264 + padding: 8rpx 0 144rpx;
  265 +}
  266 +
  267 +.detail-page {
  268 + background: #f3f3f3;
  269 +}
  270 +
  271 +.section {
  272 + padding: 32rpx;
  273 + background: #fff;
  274 + margin-bottom: 20rpx;
  275 +}
  276 +.section1 {
  277 + background: #fff;
  278 + margin-bottom: 20rpx;
  279 +}
  280 +
  281 +.row {
  282 + display: flex;
  283 + margin-bottom: 20rpx;
  284 + align-items: center;
  285 +}
  286 +
  287 +.row:last-child {
  288 + margin-bottom: 0;
  289 +}
  290 +
  291 +.label {
  292 + width: 280rpx;
  293 + color: rgba(0, 0, 0, 0.6);
  294 + font-size: 28rpx;
  295 +}
  296 +
  297 +.value {
  298 + flex: 1;
  299 + text-align: right;
  300 + color: rgba(0, 0, 0, 0.9);
  301 + font-size: 28rpx;
  302 +}
  303 +
  304 +.customer {
  305 + font-weight: 600;
  306 + font-size: 36rpx;
  307 + color: rgba(0, 0, 0, 0.9);
  308 + padding-bottom: 12rpx;
  309 +}
  310 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <uni-list-item title="编号">
  6 + <template v-slot:footer>
  7 + <uni-easyinput v-model="form.code" :inputBorder="false" disabled />
  8 + </template>
  9 + </uni-list-item>
  10 +
  11 + <uni-list-item class="select-item" :class="form.supplier ? 'is-filled' : 'is-empty'" clickable
  12 + @click="openSheet('supplier')" :rightText="displayLabel('supplierName')" showArrow>
  13 + <template v-slot:body>
  14 + <view class="item-title"><text class="required">*</text><text>承揽方</text></view>
  15 + </template>
  16 + </uni-list-item>
  17 +
  18 + <uni-list-item class="select-item" :class="form.buyer ? 'is-filled' : 'is-empty'" clickable
  19 + @click="openRelate('buyer')" :rightText="form.buyerName || '请选择定作方'" showArrow>
  20 + <template v-slot:body>
  21 + <view class="item-title"><text class="required">*</text><text>定作方</text></view>
  22 + </template>
  23 + </uni-list-item>
  24 +
  25 + <uni-list-item class="select-item" :class="form.workshopId ? 'is-filled' : 'is-empty'" clickable
  26 + @click="openSheet('workshopId')" :rightText="form.workshopName || '请选择生产厂'" showArrow>
  27 + <template v-slot:body>
  28 + <view class="item-title"><text class="required">*</text><text>生产厂</text></view>
  29 + </template>
  30 + </uni-list-item>
  31 +
  32 + <uni-list-item title="订货日期">
  33 + <template v-slot:footer>
  34 + <uni-datetime-picker type="date" v-model="form.orderDate" />
  35 + </template>
  36 + </uni-list-item>
  37 +
  38 + <uni-list-item title="单位">
  39 + <template v-slot:footer>
  40 + <uni-easyinput v-model="form.unit" :inputBorder="false" disabled />
  41 + </template>
  42 + </uni-list-item>
  43 +
  44 + <ProductRel mode="add" :orderDateBase="form.orderDate" :list="productLineList" @change="onProductsChange" :options="productList" />
  45 +
  46 + <uni-list-item title="合计人民币金额(大写)">
  47 + <template v-slot:footer>
  48 + <uni-easyinput v-model="form.totalAmountCapital" placeholder="自动计算" :inputBorder="false"
  49 + disabled />
  50 + </template>
  51 + </uni-list-item>
  52 + <uni-list-item title="交付定金、数额、时间">
  53 + <template v-slot:footer>
  54 + <uni-easyinput v-model="form.depositInfo" placeholder="请输入交付定金、数额、时间" :inputBorder="false" />
  55 + </template>
  56 + </uni-list-item>
  57 + <uni-list-item title="包装要求">
  58 + <template v-slot:footer>
  59 + <uni-easyinput v-model="form.packagingRequirements" placeholder="请输入包装要求"
  60 + :inputBorder="false" />
  61 + </template>
  62 + </uni-list-item>
  63 + <uni-list-item title="付款方式、付款期限">
  64 + <template v-slot:footer>
  65 + <uni-easyinput v-model="form.paymentTerms" placeholder="请输入付款方式、付款期限" :inputBorder="false" />
  66 + </template>
  67 + </uni-list-item>
  68 + <uni-list-item title="运输方式">
  69 + <template v-slot:footer>
  70 + <uni-easyinput v-model="form.transportMode" placeholder="请输入运输方式" :inputBorder="false" />
  71 + </template>
  72 + </uni-list-item>
  73 + <uni-list-item class="select-item" :class="(Array.isArray(form.destinationId) && form.destinationId.length) ? 'is-filled' : 'is-empty'" clickable
  74 + @click="openCitySelector" :rightText="form.destinationLabel || '请选择'" showArrow>
  75 + <template v-slot:body>
  76 + <view class="item-title"><text>目的地</text></view>
  77 + <CitySelector ref="citySelectorRef" v-model="form.destinationId" @change="onCityChange" />
  78 + </template>
  79 + </uni-list-item>
  80 +
  81 + <uni-list-item class="select-item" :class="form.includesPackagingFeeName ? 'is-filled' : 'is-empty'"
  82 + clickable @click="openSheet('includesPackagingFee')"
  83 + :rightText="form.includesPackagingFeeName || '请选择'" showArrow>
  84 + <template v-slot:body>
  85 + <view class="item-title"><text>单价中是否已包含包装费</text></view>
  86 + </template>
  87 + </uni-list-item>
  88 + <uni-list-item class="select-item" :class="form.includesTransportFeeName ? 'is-filled' : 'is-empty'"
  89 + clickable @click="openSheet('includesTransportFee')"
  90 + :rightText="form.includesTransportFeeName || '请选择'" showArrow>
  91 + <template v-slot:body>
  92 + <view class="item-title"><text>单价中是否已包含运费</text></view>
  93 + </template>
  94 + </uni-list-item>
  95 + <uni-list-item title="需方指定收货人">
  96 + <template v-slot:footer>
  97 + <uni-easyinput v-model="form.designatedConsignee" placeholder="请输入需方指定收货人"
  98 + :inputBorder="false" />
  99 + </template>
  100 + </uni-list-item>
  101 +
  102 + <view class="group">
  103 + <view class="group-title">特别条款要求</view>
  104 + <view class="radio-list">
  105 + <view v-for="(opt, i) in specialTermsList" :key="'cr-' + i" class="radio-item"
  106 + @click="onRadioSelect('specialTerms', 'specialTermsName', opt)">
  107 + <view :class="['radio', { checked: form.specialTerms === opt.value }]" />
  108 + <text class="label">{{ opt.label }}</text>
  109 + </view>
  110 + </view>
  111 + </view>
  112 + <view class="group">
  113 + <view class="group-title">执行标准</view>
  114 + <view class="radio-list">
  115 + <view v-for="(opt, i) in executionStandardList" :key="'es-' + i" class="radio-item"
  116 + @click="onRadioSelect('executionStandard', 'executionStandardName', opt)">
  117 + <view :class="['radio', { checked: form.executionStandard === opt.value }]" />
  118 + <text class="label">{{ opt.label }}</text>
  119 + </view>
  120 + </view>
  121 + </view>
  122 + <uni-list-item v-if="form.executionStandard === 'OTHER'" title="其他">
  123 + <template v-slot:footer>
  124 + <uni-easyinput v-model="form.executionStandardRemarks" placeholder="请输入其他标准备注"
  125 + :inputBorder="false" />
  126 + </template>
  127 + </uni-list-item>
  128 + <uni-list-item title="特别说明">
  129 + <template v-slot:footer>
  130 + <uni-easyinput v-model="form.specialInstructions" placeholder="请输入特别说明" :inputBorder="false" />
  131 + </template>
  132 + </uni-list-item>
  133 + <uni-list-item title="备注">
  134 + <template v-slot:footer>
  135 + <uni-easyinput v-model="form.remarks" placeholder="请输入备注" :inputBorder="false" />
  136 + </template>
  137 + </uni-list-item>
  138 +
  139 + <view class="quality">
  140 + <image class="opCollapse" src="/static/images/title.png" />
  141 + <text class="title">具体质量要求</text>
  142 + </view>
  143 + <uni-list-item title="件重条头">
  144 + <template v-slot:footer>
  145 + <uni-easyinput v-model="form.pieceWeightHead" placeholder="请输入" :inputBorder="false" />
  146 + </template>
  147 + </uni-list-item>
  148 + <uni-list-item title="表面">
  149 + <template v-slot:footer>
  150 + <uni-easyinput v-model="form.surface" placeholder="请输入" :inputBorder="false" />
  151 + </template>
  152 + </uni-list-item>
  153 + <uni-list-item title="公差">
  154 + <template v-slot:footer>
  155 + <uni-easyinput v-model="form.tolerance" placeholder="请输入" :inputBorder="false" />
  156 + </template>
  157 + </uni-list-item>
  158 + <uni-list-item title="性能">
  159 + <template v-slot:footer>
  160 + <uni-easyinput v-model="form.performance" placeholder="请输入" :inputBorder="false" />
  161 + </template>
  162 + </uni-list-item>
  163 + <uni-list-item title="成分">
  164 + <template v-slot:footer>
  165 + <uni-easyinput v-model="form.component" placeholder="请输入" :inputBorder="false" />
  166 + </template>
  167 + </uni-list-item>
  168 + <uni-list-item title="包装">
  169 + <template v-slot:footer>
  170 + <uni-easyinput v-model="form.packaging" placeholder="请输入" :inputBorder="false" />
  171 + </template>
  172 + </uni-list-item>
  173 + </uni-list>
  174 + </scroll-view>
  175 + <view class="footer">
  176 + <div class="total">
  177 + <div class="total-text">合计</div>
  178 + <div class="total-item">
  179 + <div class="total-item-text">数量</div>
  180 + <div class="total-item-price">{{ (sumQuantity || 0).toFixed(2) }}t</div>
  181 + </div>
  182 + <div class="total-item">
  183 + <div class="total-item-text">不含税金额</div>
  184 + <div class="total-item-price text-red">¥{{ (sumAmountExcl || 0).toFixed(2) }}</div>
  185 + </div>
  186 + <div class="total-item">
  187 + <div class="total-item-text">总金额</div>
  188 + <div class="total-item-price text-red">¥{{ (sumTotal || 0).toFixed(2) }}</div>
  189 + </div>
  190 + </div>
  191 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
  192 + </view>
  193 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  194 + v-model="sheet.value" @confirm="onSheetConfirm" />
  195 + <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
  196 + :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
  197 + :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
  198 + </view>
  199 +</template>
  200 +
  201 +<script>
  202 +import SingleSelectSheet from '@/components/single-select/index.vue'
  203 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  204 +import ProductRel from './productRel.vue'
  205 +import CitySelector from '@/components/city-selector/index.vue'
  206 +import { getContractApi, updateContractApi } from '@/api/contract'
  207 +import { getDicByCodes } from '@/utils/dic'
  208 +import { formatCurrencyToChinese } from '@/utils/common'
  209 +import { workshopQueryApi } from '@/api/devManage'
  210 +export default {
  211 + name: 'ModifyContractProcess',
  212 + components: { SingleSelectSheet, RelateSelectSheet, ProductRel, CitySelector },
  213 + data() {
  214 + return {
  215 + id: '',
  216 + form: {
  217 + id: '',
  218 + code: '',
  219 + supplier: '',
  220 + supplierName: '',
  221 + buyer: '',
  222 + buyerName: '',
  223 + workshopId: '',
  224 + workshopName: '',
  225 + orderDate: '',
  226 + designatedConsignee: '',
  227 + specialTerms: '',
  228 + specialTermsName: '',
  229 + executionStandard: '',
  230 + executionStandardName: '',
  231 + executionStandardRemarks: '',
  232 + includesPackagingFee: false,
  233 + includesPackagingFeeName: '',
  234 + includesTransportFee: false,
  235 + includesTransportFeeName: '',
  236 + unit: '元、公斤、元/公斤',
  237 + totalAmountCapital: '',
  238 + depositInfo: '',
  239 + packagingRequirements: '',
  240 + paymentTerms: '',
  241 + transportMode: '',
  242 + destinationId: [],
  243 + destinationLabel: '',
  244 + specialInstructions: '',
  245 + remarks: '',
  246 + pieceWeightHead: '',
  247 + surface: '',
  248 + tolerance: '',
  249 + performance: '',
  250 + component: '',
  251 + packaging: ''
  252 + },
  253 + supplierList: [],
  254 + specialTermsList: [],
  255 + executionStandardList: [],
  256 + yesNoList: [{ label: '是', value: true }, { label: '否', value: false }],
  257 + sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  258 + relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' },
  259 + sumQuantity: 0,
  260 + sumAmountExcl: 0,
  261 + sumTotal: 0,
  262 + productLineList: [],
  263 + newProductLineList: [],
  264 + productList: [],
  265 + rawToProdRatioList: [],
  266 + }
  267 + },
  268 + onLoad(query) {
  269 + this.id = (query && query.id) ? query.id : ''
  270 + },
  271 + created() {
  272 + this.loadSuppliers()
  273 + this.loadExtraOptions()
  274 + this.loadDetail()
  275 + this.$nextTick(() => {
  276 + if (Array.isArray(this.form.destinationId) && this.form.destinationId.length) {
  277 + this.initDestinationLabel()
  278 + }
  279 + })
  280 + },
  281 + methods: {
  282 + async loadDetail() {
  283 + if (!this.id) return
  284 + try {
  285 + const res = await getContractApi(this.id)
  286 + const data = res && res.data ? res.data : {}
  287 + const includesPackagingFeeName = data.includesPackagingFeeName || (data.includesPackagingFee ? '是' : '否')
  288 + const includesTransportFeeName = data.includesTransportFeeName || (data.includesTransportFee ? '是' : '否')
  289 + const m = { ...data, includesPackagingFeeName, includesTransportFeeName }
  290 + this.form = {
  291 + ...this.form,
  292 + id: m.id || '',
  293 + code: m.code || '',
  294 + supplier: m.supplier || '',
  295 + supplierName: m.supplierName || '',
  296 + buyer: m.buyer || (m.customer && m.customer.id) || '',
  297 + buyerName: m.buyerName || (m.customer && m.customer.name) || '',
  298 + orderDate: m.orderDate || '',
  299 + designatedConsignee: m.designatedConsignee || '',
  300 + specialTerms: m.specialTerms || '',
  301 + specialTermsName: m.specialTermsName || '',
  302 + executionStandard: m.executionStandard || '',
  303 + executionStandardName: m.executionStandardName || '',
  304 + executionStandardRemarks: m.executionStandardRemarks || '',
  305 + includesPackagingFee: !!m.includesPackagingFee,
  306 + includesPackagingFeeName,
  307 + includesTransportFee: !!m.includesTransportFee,
  308 + includesTransportFeeName,
  309 + unit: m.unit || this.form.unit,
  310 + totalAmountCapital: m.totalAmountCapital || '',
  311 + depositInfo: m.depositInfo || '',
  312 + packagingRequirements: m.packagingRequirements || '',
  313 + paymentTerms: m.paymentTerms || '',
  314 + transportMode: m.transportMode || '',
  315 + destinationId: (m.provinceId && m.cityId && m.districtId) ? [m.provinceId, m.cityId, m.districtId] : (Array.isArray(m.destinationId) ? m.destinationId : []),
  316 + destinationLabel: (m.provinceName && m.cityName && m.districtName) ? `${m.provinceName} / ${m.cityName} / ${m.districtName}` : (m.destinationLabel || ''),
  317 + specialInstructions: m.specialInstructions || '',
  318 + remarks: m.remarks || '',
  319 + pieceWeightHead: m.pieceWeightHead || '',
  320 + surface: m.surface || '',
  321 + tolerance: m.tolerance || '',
  322 + performance: m.performance || '',
  323 + component: m.component || '',
  324 + packaging: m.packaging || '',
  325 + workshopId: m.workshopId || '',
  326 + workshopName: m.workshopName || '',
  327 + }
  328 + const lines = Array.isArray(m.contractDistributorLineList) ? m.contractDistributorLineList : []
  329 + this.productLineList = lines
  330 + this.onProductsChange(lines)
  331 + } catch (e) { }
  332 + },
  333 + async initDestinationLabel() {
  334 + const comp = this.$refs.citySelectorRef
  335 + if (comp && typeof comp.getLabel === 'function') {
  336 + const label = await comp.getLabel()
  337 + this.form.destinationLabel = label || ''
  338 + }
  339 + },
  340 + openCitySelector() {
  341 + this.$refs.citySelectorRef && this.$refs.citySelectorRef.open()
  342 + },
  343 + onCityChange(payload) {
  344 + const label = payload && payload.label != null ? payload.label : ''
  345 + this.form.destinationLabel = label
  346 + },
  347 + onProductsChange(products) {
  348 + const list = Array.isArray(products) ? products : []
  349 + this.newProductLineList = list
  350 + const sumQ = list.reduce((acc, it) => acc + (parseFloat(it.quantity) || 0), 0)
  351 + const sumE = list.reduce((acc, it) => acc + (parseFloat(it.amountExcludingTax) || 0), 0)
  352 + const sumT = list.reduce((acc, it) => acc + (parseFloat(it.totalAmount) || 0), 0)
  353 + this.sumQuantity = sumQ
  354 + this.sumAmountExcl = sumE
  355 + this.sumTotal = sumT
  356 + this.form.totalAmountCapital = formatCurrencyToChinese(sumT)
  357 + },
  358 + async loadSuppliers() {
  359 + try {
  360 + const results = await getDicByCodes(['SUPPLIER'])
  361 + const items = results && results.SUPPLIER && results.SUPPLIER.data ? results.SUPPLIER.data : []
  362 + this.supplierList = items.map(it => ({ label: it.name, value: it.code }))
  363 + } catch (e) { this.supplierList = [] }
  364 + },
  365 + async loadExtraOptions() {
  366 + try {
  367 + const results = await getDicByCodes(['CONDITIONS_REQUIRED', 'APPLICABLE_STANDARD', 'CONTRACT_PRODUCT','RAW_TO_PROD_RATIO'])
  368 + const c1 = results && results.CONDITIONS_REQUIRED && results.CONDITIONS_REQUIRED.data ? results.CONDITIONS_REQUIRED.data : []
  369 + const c2 = results && results.APPLICABLE_STANDARD && results.APPLICABLE_STANDARD.data ? results.APPLICABLE_STANDARD.data : []
  370 + const c3 = results && results.CONTRACT_PRODUCT && results.CONTRACT_PRODUCT.data ? results.CONTRACT_PRODUCT.data : []
  371 + const c4 = results && results.RAW_TO_PROD_RATIO && results.RAW_TO_PROD_RATIO.data ? results.RAW_TO_PROD_RATIO.data : []
  372 + this.rawToProdRatioList = c4.map(it => ({ label: it.name, value: it.code }))
  373 + this.specialTermsList = c1.map(it => ({ label: it.name, value: it.code }))
  374 + this.executionStandardList = c2.map(it => ({ label: it.name, value: it.code }))
  375 + this.productList = c3.map(it => ({ label: it.name, value: it.code }))
  376 + } catch (e) {
  377 + this.specialTermsList = []
  378 + this.executionStandardList = []
  379 + this.productList = []
  380 + this.rawToProdRatioList = []
  381 + }
  382 + },
  383 + displayLabel(field) {
  384 + const m = this.form
  385 + const map = { supplierName: '请选择承揽方', buyerName: '请选择定作方', workshopName: '请选择生产厂' }
  386 + const val = m[field]
  387 + return val ? String(val) : map[field]
  388 + },
  389 + async openSheet(field) {
  390 + const setSheet = (title, options) => {
  391 + const current = this.form[field]
  392 + const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
  393 + this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
  394 + }
  395 + if (field === 'workshopId') {
  396 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  397 + const _data = res.data || {}
  398 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  399 + const opts = (list || []).map(it => ({
  400 + label: it.name ,
  401 + value: it.id
  402 + }))
  403 + setSheet('生产厂', opts)
  404 + } else if (field === 'supplier') {
  405 + setSheet('承揽方', this.supplierList)
  406 + } else if (field === 'includesPackagingFee') {
  407 + setSheet('单价中是否已包含包装费', this.yesNoList)
  408 + } else if (field === 'includesTransportFee') {
  409 + setSheet('单价中是否已包含运费', this.yesNoList)
  410 + }
  411 + },
  412 + onSheetConfirm({ value, label }) {
  413 + const field = this.sheet.field
  414 + if (!field) return
  415 + const v = (value === undefined || value === null) ? '' : value
  416 + this.form[field] = v
  417 + this.form[field + 'Name'] = label || ''
  418 + },
  419 + openRelate(fieldKey) {
  420 + let config = {}
  421 + if (fieldKey === 'buyer') {
  422 + config = { title: '定作方', source: 'customer', rowKey: 'id', multiple: false, display: [{ label: '姓名', field: 'name' }, { label: '编号', field: 'code' }, { label: '状态', field: 'available', format: v => (v ? '启用' : '停用') }] }
  423 + }
  424 + const selectedKeys = this.form[fieldKey] ? [this.form[fieldKey]] : []
  425 + this.sheet.visible = false
  426 + this.relate = { ...this.relate, title: config.title, source: config.source, display: config.display, multiple: config.multiple, rowKey: config.rowKey, selectedKeys, fieldKey }
  427 + this.$nextTick(() => { this.relate.visible = true })
  428 + },
  429 + onRelateConfirm({ items }) {
  430 + const _fieldKey = this.relate.fieldKey
  431 + const first = (items && items.length > 0) ? items[0] : null
  432 + this.form[_fieldKey] = (first && first.id) ? first.id : ''
  433 + this.form[_fieldKey + 'Name'] = (first && first.name) ? first.name : ''
  434 + },
  435 + onRadioSelect(field, nameField, opt) {
  436 + const val = opt && opt.value != null ? opt.value : ''
  437 + const label = opt && opt.label != null ? opt.label : ''
  438 + this.form[field] = val
  439 + this.form[nameField] = label
  440 + if (field === 'executionStandard' && val !== 'OTHER') {
  441 + this.form.executionStandardRemarks = ''
  442 + }
  443 + },
  444 + validateRequired() {
  445 + const checks = [
  446 + { key: 'code', label: '编号' },
  447 + { key: 'supplier', label: '承揽方' },
  448 + { key: 'buyer', label: '定作方' },
  449 + { key: 'orderDate', label: '订货日期' },
  450 + { key: 'workshopId', label: '生产厂' },
  451 + ]
  452 + for (const it of checks) {
  453 + const val = this.form[it.key]
  454 + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '') || (typeof val === 'number' && isNaN(val)))
  455 + if (empty) { uni.showToast({ title: `请先选择${it.label}`, icon: 'none' }); return false }
  456 + }
  457 + if (!Array.isArray(this.productLineList) || this.productLineList.length === 0) {
  458 + uni.showToast({ title: '请至少添加一条产品明细', icon: 'none' }); return false
  459 + }
  460 + for (const [idx, it] of this.productLineList.entries()) {
  461 + if (!it.productName || !it.quantity || !it.unitPrice) {
  462 + uni.showToast({ title: `第${idx + 1}条明细未完整填写`, icon: 'none' }); return false
  463 + }
  464 + }
  465 + return true
  466 + },
  467 + async onSubmit() {
  468 + console.log('onSubmit__payload', payload)
  469 + if (!this.validateRequired()) return
  470 + const confirmRes = await new Promise(resolve => {
  471 + uni.showModal({ title: '提示', content: '确定保存当前经销未锁规合同吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  472 + })
  473 + if (!(confirmRes && confirmRes.confirm)) return
  474 + const clean = (obj) => {
  475 + const out = {}
  476 + Object.keys(obj || {}).forEach(k => {
  477 + const v = obj[k]
  478 + const isEmptyString = typeof v === 'string' && v.trim() === ''
  479 + const isUndef = v === undefined || v === null
  480 + const isNaNNumber = typeof v === 'number' && isNaN(v)
  481 + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v
  482 + })
  483 + return out
  484 + }
  485 + const lines = (this.newProductLineList || []).map(it => clean(it))
  486 + const { destinationLabel, destinationId, ...formForSubmit } = this.form;
  487 + const destination = destinationId && destinationId.length > 0 ? destinationId[destinationId.length - 1] : '';
  488 + const payload = clean({
  489 + ...formForSubmit,
  490 + id: this.form.id,
  491 + destination,
  492 + type: 'PROCESS_STD_AGMT',
  493 + sumQuantity: this.sumQuantity,
  494 + sumAmountExcl: this.sumAmountExcl,
  495 + sumTotal: this.sumTotal,
  496 + contractDistributorLineList: lines
  497 + })
  498 + try {
  499 + await updateContractApi(payload)
  500 + uni.showToast({ title: '保存成功', icon: 'none' })
  501 + setTimeout(() => { uni.redirectTo({ url: '/pages/contract_process/index' }) }, 400)
  502 + } catch (e) {
  503 + uni.showToast({ title: '提交失败', icon: 'none' })
  504 + }
  505 + }
  506 + }
  507 +}
  508 +</script>
  509 +
  510 +<style lang="scss" scoped>
  511 +.total {
  512 + .total-text {
  513 + font-weight: 600;
  514 + font-size: 32rpx;
  515 + color: rgba(0, 0, 0, 0.9);
  516 + padding-bottom: 28rpx;
  517 + border-bottom: 2rpx solid #E7E7E7;
  518 + }
  519 +
  520 + .total-item {
  521 + display: flex;
  522 + align-items: center;
  523 + }
  524 +
  525 + .total-item-text {
  526 + font-weight: 400;
  527 + font-size: 28rpx;
  528 + color: rgba(0, 0, 0, 0.6);
  529 + line-height: 32rpx;
  530 + width: 240rpx;
  531 + padding: 24rpx 0;
  532 + }
  533 +
  534 + .total-item-price {
  535 + font-weight: 600;
  536 + font-size: 32rpx;
  537 + color: rgba(0, 0, 0, 0.9);
  538 + line-height: 32rpx;
  539 + }
  540 +
  541 + .text-red {
  542 + color: #D54941;
  543 + }
  544 +}
  545 +
  546 +.page {
  547 + display: flex;
  548 + flex-direction: column;
  549 + height: 100%;
  550 +}
  551 +
  552 +.scroll {
  553 + flex: 1;
  554 + padding: 12rpx 0 480rpx !important;
  555 +}
  556 +
  557 +.footer {
  558 + z-index: 2;
  559 + position: fixed;
  560 + left: 0;
  561 + right: 0;
  562 + bottom: 0;
  563 + padding: 32rpx;
  564 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  565 + background: #fff;
  566 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  567 +}
  568 +
  569 +.footer .btn {
  570 + height: 80rpx;
  571 + line-height: 80rpx;
  572 + border-radius: 12rpx;
  573 + font-size: 32rpx;
  574 +}
  575 +
  576 +.footer .submit {
  577 + background: $theme-primary;
  578 + color: #fff;
  579 +}
  580 +
  581 +.group {
  582 + background: #fff;
  583 + padding: 24rpx 32rpx;
  584 + margin-bottom: 20rpx;
  585 +}
  586 +
  587 +.group-title {
  588 + color: rgba(0, 0, 0, 0.9);
  589 + font-size: 32rpx;
  590 + padding-bottom: 16rpx;
  591 +}
  592 +
  593 +.radio-list {
  594 + display: flex;
  595 + flex-direction: column;
  596 +}
  597 +.quality {
  598 + background-color: #fff;
  599 + display: flex;
  600 + align-items: center;
  601 + padding: 24rpx 32rpx;
  602 + border-bottom: 1rpx solid #f0f0f0;
  603 + margin-top: 20rpx;
  604 +
  605 + .title {
  606 + font-size: 32rpx;
  607 + color: rgba(0, 0, 0, 0.9);
  608 + font-weight: 600;
  609 + }
  610 +
  611 + .opCollapse {
  612 + color: rgba(0, 0, 0, 0.6);
  613 + width: 24rpx;
  614 + height: 24rpx;
  615 + margin-right: 16rpx;
  616 + margin-top: 8rpx;
  617 + }
  618 +}
  619 +
  620 +.radio-item {
  621 + display: flex;
  622 + align-items: center;
  623 + padding: 24rpx 0;
  624 + border-bottom: 1rpx solid #f0f0f0;
  625 +}
  626 +
  627 +.radio-item:last-child {
  628 + border-bottom: none;
  629 +}
  630 +
  631 +.radio {
  632 + width: 32rpx;
  633 + height: 32rpx;
  634 + border-radius: 50%;
  635 + border: 2rpx solid #DCDCDC;
  636 + margin-right: 20rpx;
  637 + box-sizing: border-box;
  638 +}
  639 +
  640 +.radio.checked {
  641 + background: $theme-primary;
  642 + border-color: $theme-primary;
  643 +}
  644 +
  645 +.radio-item .label {
  646 + font-size: 32rpx;
  647 + color: rgba(0, 0, 0, 0.9);
  648 +}
  649 +
  650 +::v-deep .uni-list {
  651 + background: transparent;
  652 +}
  653 +
  654 +::v-deep .uni-list .uni-easyinput .uni-input-input {
  655 + color: rgba(0, 0, 0, 0.9);
  656 +}
  657 +
  658 +::v-deep .uni-list .uni-input-placeholder {
  659 + z-index: 1;
  660 +}
  661 +
  662 +::v-deep .uni-list .uni-input-input {
  663 + background-color: #ffffff;
  664 +}
  665 +
  666 +::v-deep .uni-list-item__extra-text {
  667 + font-size: 32rpx;
  668 +}
  669 +
  670 +::v-deep .uni-list-item__content-title {
  671 + font-size: 32rpx;
  672 + color: rgba(0, 0, 0, 0.9);
  673 +}
  674 +
  675 +::v-deep .uni-list-item__container {
  676 + padding: 32rpx;
  677 +}
  678 +
  679 +::v-deep .uni-list-item__container .uni-easyinput__placeholder-class {
  680 + font-size: 32rpx;
  681 + color: rgba(0, 0, 0, 0.4);
  682 +}
  683 +
  684 +::v-deep .uni-list-item__container .uni-easyinput__content {
  685 + border: none;
  686 + background-color: #ffffff !important;
  687 +}
  688 +
  689 +::v-deep .uni-list-item__container .uni-easyinput__content-input {
  690 + padding-left: 0 !important;
  691 + height: 48rpx;
  692 + line-height: 48rpx;
  693 + font-size: 32rpx;
  694 +}
  695 +
  696 +::v-deep .uni-list-item__container .uni-easyinput__content .content-clear-icon {
  697 + font-size: 44rpx !important;
  698 +}
  699 +
  700 +::v-deep .uni-list-item__container .item-title,
  701 +::v-deep .uni-list-item__container .uni-list-item__content {
  702 + flex: none;
  703 + min-height: 48rpx;
  704 + line-height: 48rpx;
  705 + font-size: 32rpx;
  706 + position: relative;
  707 + width: 162rpx;
  708 + margin-right: 32rpx;
  709 + color: rgba(0, 0, 0, 0.9);
  710 +}
  711 +
  712 +::v-deep .uni-list-item__container .item-title .required {
  713 + color: red;
  714 + position: absolute;
  715 + top: 50%;
  716 + transform: translateY(-50%);
  717 + left: -16rpx;
  718 +}
  719 +
  720 +::v-deep .uni-list-item.select-item.is-empty .uni-list-item__extra-text {
  721 + color: rgba(0, 0, 0, 0.4) !important;
  722 +}
  723 +
  724 +::v-deep .uni-list-item.select-item.is-filled .uni-list-item__extra-text {
  725 + color: rgba(0, 0, 0, 0.9) !important;
  726 +}
  727 +::v-deep .uni-easyinput {
  728 + &__placeholder-class {
  729 + font-size: 32rpx;
  730 + color: rgba(0, 0, 0, 0.4);
  731 + }
  732 +
  733 + &__content {
  734 + border: none;
  735 + background-color: #ffffff !important;
  736 + height: 100%;
  737 + &-input {
  738 + padding-left: 0 !important;
  739 + height: 48rpx;
  740 + line-height: 48rpx;
  741 + font-size: 32rpx;
  742 + }
  743 +
  744 + .content-clear-icon {
  745 + font-size: 44rpx !important;
  746 + }
  747 + }
  748 + }
  749 +
  750 +</style>
\ No newline at end of file
... ...
  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'" 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 +
  13 + </view>
  14 + </view>
  15 +
  16 + <view v-if="mode === 'add'" class="add-list">
  17 + <view v-for="(item, idx) in items" :key="idx" class="block">
  18 + <uni-list v-show="item.collapsed">
  19 + <uni-list-item class="select-item" :class="item.rawProductName ? 'is-filled' : 'is-empty'" clickable @click="openProductSheet(idx)" :rightText="item.rawProductName || '请选择原材料名称'" showArrow>
  20 + <template v-slot:body>
  21 + <view class="item-title"><text>原材料名称</text></view>
  22 + </template>
  23 + </uni-list-item>
  24 + <uni-list-item title="原材料牌号">
  25 + <template v-slot:footer>
  26 + <uni-easyinput v-model="item.rawProductGrade" :inputBorder="false" placeholder="请输入原材料牌号" />
  27 + </template>
  28 + </uni-list-item>
  29 + <uni-list-item title="行业">
  30 + <template v-slot:footer>
  31 + <uni-easyinput v-model="item.industry" :inputBorder="false" placeholder="请输入行业名称" />
  32 + </template>
  33 + </uni-list-item>
  34 + </uni-list>
  35 + <uni-list v-show="!item.collapsed">
  36 + <uni-list-item class="select-item" :class="item.rawProductName ? 'is-filled' : 'is-empty'" clickable @click="openProductSheet(idx)" :rightText="item.rawProductName || '请选择原材料名称'" showArrow>
  37 + <template v-slot:body>
  38 + <view class="item-title"><text>原材料名称</text></view>
  39 + </template>
  40 + </uni-list-item>
  41 + <uni-list-item title="原材料牌号">
  42 + <template v-slot:footer>
  43 + <uni-easyinput v-model="item.rawProductGrade" :inputBorder="false" placeholder="请输入原材料牌号" />
  44 + </template>
  45 + </uni-list-item>
  46 + <uni-list-item title="行业">
  47 + <template v-slot:footer>
  48 + <uni-easyinput v-model="item.industry" :inputBorder="false" placeholder="请输入行业名称" />
  49 + </template>
  50 + </uni-list-item>
  51 + <uni-list-item title="品质">
  52 + <template v-slot:footer>
  53 + <uni-easyinput v-model="item.quality" :inputBorder="false" placeholder="请输入品质" />
  54 + </template>
  55 + </uni-list-item>
  56 + <uni-list-item title="原材料提供时间">
  57 + <template v-slot:footer>
  58 + <uni-easyinput v-model="item.supplyTime" :inputBorder="false" placeholder="请输入原材料提供时间" />
  59 + </template>
  60 + </uni-list-item>
  61 + <uni-list-item class="select-item" :class="item.rawToProdRatioName ? 'is-filled' : 'is-empty'" clickable @click="openRawToProdRatioSheet(idx)" :rightText="item.rawToProdRatioName || '请选择原材料到产品的转换比例'" showArrow>
  62 + <template v-slot:body>
  63 + <view class="item-title"><text>原材料到产品的转换比例</text></view>
  64 + </template>
  65 + </uni-list-item>
  66 + <uni-list-item title="原材料到产品的转换比例备注">
  67 + <template v-slot:footer>
  68 + <uni-easyinput v-model="item.materialProductRatioRemarks" :disabled="item.materialProductRatio !== '10'" :inputBorder="false" placeholder="请输入原材料到产品的转换比例备注" />
  69 + </template>
  70 + </uni-list-item>
  71 +
  72 + <uni-list-item title="厚度">
  73 + <template v-slot:footer>
  74 + <uni-easyinput v-model="item.thickness" :inputBorder="false" placeholder="请输入厚度" />
  75 + </template>
  76 + </uni-list-item>
  77 + <uni-list-item title="厚度公差(单项+)">
  78 + <template v-slot:footer>
  79 + <uni-easyinput v-model="item.thicknessTolPos" :inputBorder="false"
  80 + placeholder="请输入厚度公差(单项+)" />
  81 + </template>
  82 + </uni-list-item>
  83 + <uni-list-item title="厚度公差(单项-)">
  84 + <template v-slot:footer>
  85 + <uni-easyinput v-model="item.thicknessTolNeg" :inputBorder="false"
  86 + placeholder="请输入厚度公差(单项-)" />
  87 + </template>
  88 + </uni-list-item>
  89 + <uni-list-item title="宽度">
  90 + <template v-slot:footer>
  91 + <uni-easyinput v-model="item.width" :inputBorder="false" placeholder="请输入宽度" />
  92 + </template>
  93 + </uni-list-item>
  94 + <uni-list-item title="宽度公差(单项+)">
  95 + <template v-slot:footer>
  96 + <uni-easyinput v-model="item.widthTolPos" :inputBorder="false" placeholder="请输入宽度公差(单项+)" />
  97 + </template>
  98 + </uni-list-item>
  99 + <uni-list-item title="宽度公差(单项-)">
  100 + <template v-slot:footer>
  101 + <uni-easyinput v-model="item.widthTolNeg" :inputBorder="false" placeholder="请输入宽度公差(单项-)" />
  102 + </template>
  103 + </uni-list-item>
  104 + <uni-list-item title="长度">
  105 + <template v-slot:footer>
  106 + <uni-easyinput v-model="item.length" :inputBorder="false" placeholder="请输入长度" />
  107 + </template>
  108 + </uni-list-item>
  109 + <uni-list-item title="长度公差(单项+)">
  110 + <template v-slot:footer>
  111 + <uni-easyinput v-model="item.lengthTolPos" :inputBorder="false"
  112 + placeholder="请输入长度公差(单项+)" />
  113 + </template>
  114 + </uni-list-item>
  115 + <uni-list-item title="长度公差(单项-)">
  116 + <template v-slot:footer>
  117 + <uni-easyinput v-model="item.lengthTolNeg" :inputBorder="false"
  118 + placeholder="请输入长度公差(单项-)" />
  119 + </template>
  120 + </uni-list-item>
  121 + <uni-list-item title="定做物数量">
  122 + <template v-slot:footer>
  123 + <uni-easyinput v-model="item.productQuantity" type="number" :inputBorder="false" placeholder="请输入数量" @input="onImmediateChange(idx)" @blur="onNumberBlur(idx, 'productQuantity', 0)" />
  124 + </template>
  125 + </uni-list-item>
  126 + <uni-list-item title="加工费单价">
  127 + <template v-slot:footer>
  128 + <uni-easyinput v-model="item.unitPrice" type="number" :inputBorder="false" placeholder="请输入单价" @input="onImmediateChange(idx)" @blur="onNumberBlur(idx, 'unitPrice', 0)" />
  129 + </template>
  130 + </uni-list-item>
  131 + <uni-list-item title="不含税金额">
  132 + <template v-slot:footer>
  133 + <uni-easyinput v-model="item.amountExcludingTax" type="number" :inputBorder="false" disabled placeholder="自动计算" />
  134 + </template>
  135 + </uni-list-item>
  136 + <uni-list-item title="总金额">
  137 + <template v-slot:footer>
  138 + <uni-easyinput v-model="item.totalAmount" type="number" :inputBorder="false" disabled placeholder="自动计算" />
  139 + </template>
  140 + </uni-list-item>
  141 + <uni-list-item title="发货日期">
  142 + <template v-slot:footer>
  143 + <uni-datetime-picker type="date" v-model="item.orderDate" @change="onDateChange(idx, $event)" />
  144 + </template>
  145 + </uni-list-item>
  146 + </uni-list>
  147 + <view class="block-ops">
  148 + <div class="del" @click="onRemove(idx)">
  149 + <image src="/static/images/delete.png" class="icon" />
  150 + 删除
  151 + </div>
  152 + <div class="toggle" @click="toggleItem(idx)">
  153 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'" class="icon" />
  154 + {{ item.collapsed ? '展开' : '收起' }}</div>
  155 + </view>
  156 + </view>
  157 + </view>
  158 +
  159 + <view v-else class="view-list" v-show="!collapsedView">
  160 + <view v-for="(item, idx) in items" :key="'v-' + idx" class="card">
  161 + <view class="row"><text class="label">原材料名称</text><text class="value">{{ item.rawProductName }}</text></view>
  162 + <view class="row"><text class="label">原材料牌号</text><text class="value">{{ item.rawProductGrade }}</text></view>
  163 + <view class="row"><text class="label">原材料提供时间</text><text class="value">{{ item.rawProductProvideDate }}</text></view>
  164 + <view class="row"><text class="label">原材料到产品的转换比例</text><text class="value">{{ item.rawToProdRatioName }}</text></view>
  165 + <view class="row"><text class="label">行业</text><text class="value">{{ item.industry }}</text></view>
  166 + <view class="row"><text class="label">品质</text><text class="value">{{ item.quality }}</text></view>
  167 + <view class="row"><text class="label">规格</text><text class="value">{{ item.specDisplay }}</text></view>
  168 + <view class="row"><text class="label">定做物数量</text><text class="value">{{ item.productQuantity }}</text></view>
  169 + <view class="row"><text class="label">加工费单价</text><text class="value">{{ formatCurrency(item.unitPrice)
  170 + }}</text>
  171 + </view>
  172 + <view class="row"><text class="label">不含税金额</text><text class="value">{{
  173 + formatCurrency(item.amountExcludingTax)
  174 + }}</text></view>
  175 + <view class="row"><text class="label">总金额</text><text class="value">{{ formatCurrency(item.totalAmount)
  176 + }}</text></view>
  177 + <view class="row"><text class="label">发货日期</text><text class="value">{{ item.orderDate }}</text></view>
  178 + </view>
  179 + </view>
  180 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value" @confirm="onProductConfirm" />
  181 + </view>
  182 +</template>
  183 +<script>
  184 +import SingleSelectSheet from '@/components/single-select/index.vue'
  185 +export default {
  186 + name: 'ProductRel',
  187 + props: {
  188 + mode: { type: String, default: 'add' },
  189 + list: { type: Array, default: () => [] },
  190 + max: { type: Number, default: 8 },
  191 + orderDateBase: { type: String, default: '' },
  192 + options: { type: Array, default: () => [] },
  193 + rawToProdRatioList: { type: Array, default: () => [] }
  194 + },
  195 + components: { SingleSelectSheet },
  196 + data() {
  197 + return {
  198 + items: [],
  199 + collapsedView: false,
  200 + sheet: { visible: false, title: '请选择产品', options: [], value: '', idx: -1, mode: '' }
  201 + }
  202 + },
  203 + computed: {
  204 + selectOptions() {
  205 + const list = Array.isArray(this.options) ? this.options : []
  206 + return list.map(o => ({
  207 + label: o.label != null ? o.label : (o.text != null ? o.text : (o.name != null ? o.name : '')),
  208 + value: o.value != null ? o.value : (o.id != null ? o.id : o.rawProductId)
  209 + }))
  210 + }
  211 + ,
  212 + ratioOptions() {
  213 + const list = Array.isArray(this.rawToProdRatioList) ? this.rawToProdRatioList : []
  214 + return list.map(o => ({
  215 + label: o.label != null ? o.label : (o.text != null ? o.text : (o.name != null ? o.name : '')),
  216 + value: o.value != null ? o.value : (o.id != null ? o.id : o.code)
  217 + }))
  218 + }
  219 + },
  220 + watch: {
  221 + items: {
  222 + handler() { this.emitChange() },
  223 + deep: true
  224 + },
  225 + list: {
  226 + handler(v) {
  227 + // const arr = Array.isArray(v) ? v : []
  228 + // this.items = arr.map(x => ({ ...this.defaultItem(), ...x, collapsed: true }))
  229 + this.items = v.map(x => ({ ...this.defaultItem(), ...x, collapsed: true }))
  230 + console.log('v', v)
  231 + },
  232 + deep: true
  233 + }
  234 + },
  235 + created() {
  236 + const init = Array.isArray(this.list) && this.list.length > 0 ? this.list.map(v => ({ ...this.defaultItem(), ...v, collapsed: true })) : [{ ...this.defaultItem(), collapsed: false }]
  237 + this.items = init
  238 + this.recalculateAll()
  239 + },
  240 + methods: {
  241 + defaultItem() {
  242 + return { rawProductId: '', rawProductName: '', rawProductGrade: '', industry: '',materialProductRatioRemarks:'', supplyTime: '存料加工', materialProductRatio: '', rawToProdRatioName: '', quality: '', thickness: '', thicknessTolPos: '', thicknessTolNeg: '', width: '', widthTolPos: '', widthTolNeg: '', length: '', lengthTolPos: '', lengthTolNeg: '', productQuantity: '', unitPrice: '', amountExcludingTax: 0, totalAmount: 0, orderDate: '' }
  243 + },
  244 + onImmediateChange(idx) {
  245 + this.$nextTick(() => this.recalculate(idx))
  246 + },
  247 + toNumber(val) {
  248 + if (typeof val === 'number') return isNaN(val) ? 0 : val
  249 + const n = parseFloat(String(val).replace(/[^0-9.\-]/g, ''))
  250 + return isNaN(n) ? 0 : n
  251 + },
  252 + round(val, digits = 2) {
  253 + const n = Number(val)
  254 + if (isNaN(n)) return 0
  255 + const m = Math.pow(10, digits)
  256 + return Math.round(n * m) / m
  257 + },
  258 + onNumberBlur(idx, field, digits) {
  259 + const it = this.items[idx]
  260 + if (!it) return
  261 + const raw = it[field]
  262 + // 如果为空则保持为空,不自动置为0,仅重新计算依赖字段
  263 + if (raw === '' || raw === null || raw === undefined) {
  264 + this.$set(this.items, idx, it)
  265 + this.recalculate(idx)
  266 + return
  267 + }
  268 + const num = this.toNumber(raw)
  269 + const rounded = this.round(num, digits)
  270 + it[field] = rounded
  271 + this.$set(this.items, idx, it)
  272 + this.recalculate(idx)
  273 + },
  274 + formatCurrency(val) {
  275 + if (val == null || val === '') return ''
  276 + const num = Number(val)
  277 + const pre = isNaN(num) ? '' : '¥'
  278 + const fixed = isNaN(num) ? String(val) : num.toFixed(2)
  279 + return `${pre}${fixed}`
  280 + },
  281 + specOf(item) {
  282 + const t = [item.thickness, item.thicknessTolPos, item.thicknessTolNeg].filter(Boolean).join('/')
  283 + const w = [item.width, item.widthTolPos, item.widthTolNeg].filter(Boolean).join('/')
  284 + const l = [item.length, item.lengthTolPos, item.lengthTolNeg].filter(Boolean).join('/')
  285 + return [t, w, l].filter(Boolean).join(' × ')
  286 + },
  287 + openProductSheet(idx) {
  288 + const opts = this.selectOptions
  289 + const current = this.items[idx] && this.items[idx].rawProductId
  290 + const match = opts.find(o => String(o.value) === String(current))
  291 + this.sheet = { ...this.sheet, visible: true, title: '请选择原材料名称', options: opts, idx, value: match ? match.value : '', mode: 'product' }
  292 + },
  293 + openRawToProdRatioSheet(idx) {
  294 + const opts = this.ratioOptions
  295 + const current = this.items[idx] && this.items[idx].materialProductRatio
  296 + const match = opts.find(o => String(o.value) === String(current))
  297 + this.sheet = { ...this.sheet, visible: true, title: '请选择原材料到产品的转换比例', options: opts, idx, value: match ? match.value : '', mode: 'ratio' }
  298 + },
  299 + onProductConfirm({ value, label }) {
  300 + const idx = this.sheet.idx
  301 + const it = this.items[idx]
  302 + if (!it) { this.sheet.visible = false; return }
  303 + if (this.sheet.mode === 'ratio') {
  304 + it.materialProductRatio = value
  305 + it.rawToProdRatioName = label || ''
  306 + } else {
  307 + it.rawProductId = value
  308 + it.rawProductName = label || ''
  309 + }
  310 + this.$set(this.items, idx, it)
  311 + this.sheet.visible = false
  312 + this.emitChange()
  313 + },
  314 + recalculate(idx) {
  315 + const TAX_RATE = 0.13
  316 + const it = this.items[idx]
  317 + if (!it) return
  318 + const qty = this.toNumber(it.productQuantity)
  319 + const price = this.toNumber(it.unitPrice)
  320 + const total = this.round(qty * price, 2)
  321 + const excl = this.round(total / (1 + TAX_RATE), 2)
  322 + const next = { ...it, totalAmount: total, amountExcludingTax: excl }
  323 + this.$set(this.items, idx, next)
  324 + },
  325 + recalculateAll() {
  326 + for (let i = 0; i < this.items.length; i++) this.recalculate(i)
  327 + },
  328 + onAdd() {
  329 + if (this.items.length >= this.max) return uni.showToast({ title: `最多添加${this.max}个`, icon: 'none' })
  330 + const obj = this.defaultItem()
  331 + obj.collapsed = true
  332 + this.items.push(obj)
  333 + this.emitChange()
  334 + },
  335 + onRemove(idx) {
  336 + this.items.splice(idx, 1)
  337 + this.emitChange()
  338 + },
  339 + toggleItem(idx) {
  340 + const it = this.items[idx]
  341 + if (!it) return
  342 + it.collapsed = !it.collapsed
  343 + this.$set(this.items, idx, it)
  344 + },
  345 + emitChange() {
  346 + const out = this.items.map(it => ({ ...it, specDisplay: this.specOf(it) }))
  347 + this.$emit('input', out)
  348 + this.$emit('update:value', out)
  349 + this.$emit('change', out)
  350 + },
  351 + onDateChange(idx, e) {
  352 + const it = this.items[idx]
  353 + if (!it) return
  354 + const val = typeof e === 'string' ? e : (e && e.detail && e.detail.value) ? e.detail.value : it.orderDate
  355 + const dateStr = String(val).slice(0, 10)
  356 + const base = this.orderDateBase ? new Date(this.orderDateBase) : null
  357 + const d = new Date(dateStr)
  358 + if (base && !isNaN(d.getTime()) && d.getTime() < base.getTime()) {
  359 + uni.showToast({ title: '发货日期不得早于订货日期', icon: 'none' })
  360 + it.orderDate = this.orderDateBase
  361 + } else {
  362 + it.orderDate = dateStr
  363 + }
  364 + this.$set(this.items, idx, it)
  365 + },
  366 + toggleViewCollapse() {
  367 + this.collapsedView = !this.collapsedView
  368 + }
  369 + }
  370 +}
  371 +</script>
  372 +<style lang="scss" scoped>
  373 +.product-rel {
  374 + margin-top: 10px;
  375 +}
  376 +
  377 +.header {
  378 + background-color: #fff;
  379 + display: flex;
  380 + align-items: center;
  381 + padding: 24rpx 32rpx;
  382 + border-bottom: 1rpx solid #f0f0f0;
  383 +}
  384 +
  385 +.dot {
  386 + width: 16rpx;
  387 + height: 16rpx;
  388 + background: #3D48A3;
  389 + border-radius: 50%;
  390 + margin-right: 12rpx;
  391 +}
  392 +
  393 +.title {
  394 + font-size: 32rpx;
  395 + color: rgba(0, 0, 0, 0.9);
  396 + font-weight: 600;
  397 +}
  398 +
  399 +.ops {
  400 + margin-left: auto;
  401 +}
  402 +.op {
  403 + color: $theme-primary;
  404 + font-size: 28rpx;
  405 + margin-left: 8rpx;
  406 +}
  407 +.op1 {
  408 + display: flex;
  409 + align-items: center;
  410 +}
  411 +
  412 +.opAdd {
  413 + color: rgba(0, 0, 0, 0.6);
  414 + width: 40rpx;
  415 + height: 40rpx;
  416 +}
  417 +
  418 +.opCollapse {
  419 + color: rgba(0, 0, 0, 0.6);
  420 + width: 24rpx;
  421 + height: 24rpx;
  422 + margin-right: 16rpx;
  423 + margin-top: 8rpx;
  424 +}
  425 +
  426 +
  427 +.block {
  428 + background: #fff;
  429 + margin-bottom: 20rpx;
  430 +}
  431 +::v-deep .uni-list-item__content {
  432 + display: flex;
  433 + justify-content: center;
  434 +}
  435 +::v-deep .uni-list {
  436 + background: transparent;
  437 +
  438 + &-item {
  439 + &__container {
  440 + padding: 32rpx;
  441 + }
  442 +
  443 + &__content-title {
  444 + font-size: 28rpx;
  445 + color: rgba(0, 0, 0, 0.9);
  446 + }
  447 +
  448 + &__extra-text {
  449 + font-size: 32rpx;
  450 + }
  451 +
  452 + .uni-easyinput {
  453 + width: 100%;
  454 +
  455 + &__placeholder-class {
  456 + font-size: 32rpx;
  457 + color: rgba(0, 0, 0, 0.4);
  458 + }
  459 +
  460 + &__content {
  461 + border: none;
  462 + display: flex;
  463 + &-input {
  464 + padding-left: 0 !important;
  465 + height: 48rpx;
  466 + line-height: 48rpx;
  467 + font-size: 32rpx;
  468 + }
  469 + }
  470 +
  471 + .uni-input-placeholder {
  472 + // z-index: 2;
  473 + }
  474 + }
  475 + }
  476 +}
  477 +
  478 +.block-ops {
  479 + display: flex;
  480 + padding: 20rpx 32rpx 20rpx;
  481 + justify-content: space-around;
  482 +}
  483 +
  484 +.del {
  485 + color: #D54941;
  486 + font-size: 28rpx;
  487 + display: flex;
  488 + align-items: center;
  489 + image {
  490 + width: 40rpx;
  491 + height: 40rpx;
  492 + }
  493 +}
  494 +
  495 +.toggle {
  496 + color: $theme-primary;
  497 + font-size: 28rpx;
  498 + display: flex;
  499 + align-items: center;
  500 + image {
  501 + width: 40rpx;
  502 + height: 40rpx;
  503 + }
  504 +}
  505 +
  506 +.view-list {
  507 + padding: 24rpx 32rpx;
  508 + background: #ffffff;
  509 +}
  510 +
  511 +.card {
  512 + background: #f3f3f3;
  513 + border-radius: 16rpx;
  514 + padding: 24rpx;
  515 + margin-bottom: 20rpx;
  516 +}
  517 +
  518 +.row {
  519 + display: flex;
  520 + margin-bottom: 20rpx;
  521 +}
  522 +
  523 +.row:last-child {
  524 + margin-bottom: 0;
  525 +}
  526 +
  527 +.label {
  528 + width: 120rpx;
  529 + color: rgba(0, 0, 0, 0.6);
  530 + font-size: 28rpx;
  531 +}
  532 +
  533 +.value {
  534 + flex: 1;
  535 + text-align: right;
  536 + color: rgba(0, 0, 0, 0.9);
  537 + font-size: 28rpx;
  538 +}
  539 +</style>
... ...