Commit fa376fb0d059b0b4c30175d1d7e0e9bbf10b79f8

Authored by 史婷婷
1 parent e8bcf6a6

feat: 产品试样结果跟踪单 -列表&筛选

  1 +import request from '@/utils/request'
  2 +import { ContentTypeEnum } from '@/utils/httpEnum';
  3 +
  4 +const baseUrl = '/sample/tracking';
  5 +// 查询列表
  6 +export function queryApi(params) {
  7 + return request({
  8 + url: baseUrl + `/query`,
  9 + method: 'get',
  10 + params
  11 + })
  12 +}
  13 +
  14 +// 根据ID查询详情数据
  15 +export function getDetailApi(id) {
  16 + return request({
  17 + url: baseUrl,
  18 + method: 'get',
  19 + params: { id }
  20 + })
  21 +}
  22 +
  23 +
  24 +// 修改保存
  25 +export function updateApi(params) {
  26 + return request({
  27 + url: baseUrl,
  28 + method: 'put',
  29 + data: params,
  30 + contentType: ContentTypeEnum.JSON
  31 + })
  32 +}
... ...
... ... @@ -808,6 +808,14 @@
808 808 "navigationBarBackgroundColor": "#ffffff",
809 809 "navigationBarTextStyle": "black"
810 810 }
  811 + },
  812 + {
  813 + "path": "pages/follow_up_form/index",
  814 + "style": {
  815 + "navigationBarTitleText": "产品试样结果跟踪单",
  816 + "navigationBarBackgroundColor": "#ffffff",
  817 + "navigationBarTextStyle": "black"
  818 + }
811 819 }
812 820 ],
813 821 "subPackages": [
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view v-if="isEditAll">
  6 + <view class="section">
  7 + <uni-list-item title="订货单位" class="no-border-top">
  8 + <template v-slot:footer>
  9 + <view class="readonly-text">{{ form.orderingUnitName }}</view>
  10 + </template>
  11 + </uni-list-item>
  12 + <uni-list-item class="select-item" :class="form.sampleTypeName ? 'is-filled' : 'is-empty'"
  13 + clickable @click="openSheet('sampleType')" :rightText="form.sampleTypeName || '请选择'"
  14 + showArrow>
  15 + <template v-slot:body>
  16 + <view class="item-title"><text>试样种类</text></view>
  17 + </template>
  18 + </uni-list-item>
  19 + <uni-list-item class="select-item" :class="form.customerTypeName ? 'is-filled' : 'is-empty'"
  20 + clickable @click="openSheet('customerType')" :rightText="form.customerTypeName || '请选择'"
  21 + showArrow>
  22 + <template v-slot:body>
  23 + <view class="item-title"><text>客户类型</text></view>
  24 + </template>
  25 + </uni-list-item>
  26 + <uni-list-item title="生产厂">
  27 + <template v-slot:footer>
  28 + <view class="readonly-text">{{ form.workshopName }}</view>
  29 + </template>
  30 + </uni-list-item>
  31 + <uni-list-item title="所属品种">
  32 + <template v-slot:footer>
  33 + <view class="readonly-text">{{ form.belongingBreed }}</view>
  34 + </template>
  35 + </uni-list-item>
  36 + <uni-list-item title="原供货同行">
  37 + <template v-slot:footer>
  38 + <uni-easyinput v-model="form.originalSupplierPeer" placeholder="请输入原供货同行"
  39 + :inputBorder="false" />
  40 + </template>
  41 + </uni-list-item>
  42 + </view>
  43 +
  44 + <!-- 产品 -->
  45 + <view class="section2">
  46 + <!-- mode="add" 允许编辑 -->
  47 + <Product mode="add" :list="initPurchaseOrderLineList" @change="purchaseOrderLineListChange"
  48 + :provideSamplesOptions="provideSamplesOptions"
  49 + :clearParametersOptions="clearParametersOptions" />
  50 + </view>
  51 +
  52 + <view class="section3">
  53 + <view class="view-total">
  54 + <view class="head">合计</view>
  55 + <view class="row">
  56 + <view class="row2">
  57 + <text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text>
  58 + </view>
  59 + </view>
  60 + </view>
  61 + </view>
  62 +
  63 + <view class="section2">
  64 +
  65 + <uni-list-item class="select-item"
  66 + :class="form.sampleQuantityRegulationName ? 'is-filled' : 'is-empty'" clickable
  67 + @click="openSheet('sampleQuantityRegulation')"
  68 + :rightText="form.sampleQuantityRegulationName || '请选择'" showArrow>
  69 + <template v-slot:body>
  70 + <view class="item-title"><text>本次试样数量是否超规定</text></view>
  71 + </template>
  72 + </uni-list-item>
  73 +
  74 + <uni-list-item class="select-item"
  75 + :class="form.specificationQuantityRegulationName ? 'is-filled' : 'is-empty'" clickable
  76 + @click="openSheet('specificationQuantityRegulation')"
  77 + :rightText="form.specificationQuantityRegulationName || '请选择'" showArrow>
  78 + <template v-slot:body>
  79 + <view class="item-title"><text>试样规格个数是否超规定</text></view>
  80 + </template>
  81 + </uni-list-item>
  82 +
  83 + <uni-list-item title="试样次数">
  84 + <template v-slot:footer>
  85 + <uni-easyinput type="textarea" v-model="form.sampleFrequency" placeholder="请输入试样次数"
  86 + :inputBorder="false" />
  87 + </template>
  88 + </uni-list-item>
  89 +
  90 + <uni-list-item title="前期不合格描述">
  91 + <template v-slot:footer>
  92 + <uni-easyinput type="textarea" v-model="form.earlyNonconformityDescription"
  93 + placeholder="请输入前期不合格描述" :inputBorder="false" />
  94 + </template>
  95 + </uni-list-item>
  96 +
  97 + </view>
  98 + </view>
  99 + <view v-else class="detail-section">
  100 + <view class="section">
  101 + <text class="row company">{{ form.orderingUnitName }}</text>
  102 + <view :class="['status', `status_${form.status}`]" />
  103 + <view class="row"><text class="label">试样种类</text><text class="value">{{ form.sampleTypeName
  104 + }}</text></view>
  105 + <view class="row"><text class="label">客户类型</text><text class="value">{{ form.customerTypeName
  106 + }}</text></view>
  107 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName
  108 + }}</text></view>
  109 + <view class="row"><text class="label">所属品种</text><text class="value">{{ form.belongingBreed
  110 + }}</text></view>
  111 + <view class="row"><text class="label">原供货同行</text><text class="value">{{
  112 + form.originalSupplierPeer }}</text>
  113 + </view>
  114 + </view>
  115 +
  116 + <!-- 产品 -->
  117 + <view class="section2">
  118 + <Product mode="view" :list="form.productSampleConfirmationSlipDetailList" />
  119 + </view>
  120 +
  121 + <view class="section3">
  122 + <view class="view-total">
  123 + <view class="head">合计</view>
  124 + <view class="row">
  125 + <view class="row2">
  126 + <text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text>
  127 + </view>
  128 + </view>
  129 + </view>
  130 + </view>
  131 +
  132 + <view class="section">
  133 + <view class="row"><text class="label">数量</text><text class="value">{{ form.totalQuantity
  134 + }}</text></view>
  135 + <view class="row"><text class="label">本次试样数量是否超规定</text><text class="value">{{
  136 + form.sampleQuantityRegulation ?
  137 + '是' : '否' }}</text></view>
  138 + <view class="row"><text class="label">试样规格个数是否超规定</text><text class="value">{{
  139 + form.specificationQuantityRegulation ? '是' : '否' }}</text></view>
  140 + <view class="row"><text class="label">试样次数</text><text class="value">{{ form.sampleFrequency
  141 + }}</text></view>
  142 + <uni-list-item v-if="isEditEarlyNonconformityDescription" title="前期不合格描述">
  143 + <template v-slot:footer>
  144 + <uni-easyinput type="textarea" v-model="form.earlyNonconformityDescription"
  145 + placeholder="请输入前期不合格描述" :inputBorder="false" />
  146 + </template>
  147 + </uni-list-item>
  148 + <view v-else class="row"><text class="label">前期不合格描述</text><text class="value">{{
  149 + form.earlyNonconformityDescription
  150 + }}</text></view>
  151 + </view>
  152 + </view>
  153 +
  154 +
  155 + </view>
  156 + </scroll-view>
  157 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  158 + v-model="sheet.value" @confirm="onSheetConfirm" />
  159 + </view>
  160 +</template>
  161 +
  162 +<script>
  163 +import { getDetailApi } from '@/api/confirmation_form.js'
  164 +import Product from './product.vue'
  165 +import {
  166 + getDicByCodes
  167 +} from '@/utils/dic'
  168 +import SingleSelectSheet from '@/components/single-select/index.vue'
  169 +// 办事处内勤:可编辑 (编辑时的所有字段)
  170 +// 办事处主管:只能编辑 :前期不合格描述
  171 +// 其他人 :只能查看
  172 +export default {
  173 + name: 'ConfirmationFormApprove',
  174 + components: { Product, SingleSelectSheet },
  175 + props: { id: { type: [String, Number], default: '' } },
  176 + data() {
  177 + return {
  178 + form: {},
  179 + initPurchaseOrderLineList: [],
  180 + roleCodes: [],
  181 + canEditSupplementary: false,
  182 + isEditAll: false,
  183 + isEditEarlyNonconformityDescription: false,
  184 + sheet: { visible: false, title: '请选择', options: [], value: '', field: '' },
  185 + dicOptions: {
  186 + ENTERPRISE_TYPE: [], // 客户类型
  187 + SAMPLE_TYPE: [], // 试样种类
  188 + CLEAR_PARAMETERS: [], // 是否提供明确参数
  189 + PROVIDE_SAMPLES: [], // 是否提供样品
  190 + },
  191 + enterpriseTypeLocal: [], // 客户类型
  192 + sampleTypeLocal: [], // 试样种类
  193 + provideSamplesOptions: [], // 是否提供样品
  194 + clearParametersOptions: [], // 是否提供明确参数
  195 + }
  196 + },
  197 + computed: {
  198 + },
  199 + watch: {
  200 + id: {
  201 + immediate: true,
  202 + handler(val) {
  203 + const v = (val !== undefined && val !== null) ? String(val) : ''
  204 + if (v) {
  205 + this.loadDetail(v);
  206 + this.loadAllDicData()
  207 + this.getRoleInfo();
  208 + }
  209 + }
  210 + }
  211 + },
  212 + onLoad(query) {
  213 +
  214 + },
  215 + methods: {
  216 + getRoleInfo() {
  217 + this.roleCodes = this.$store.getters.roleCodes || [];
  218 + console.log('roleCodes', this.roleCodes)
  219 + // 办事处内勤:可编辑 (编辑时的所有字段) bscnq
  220 + this.isEditAll = this.roleCodes.includes('bscnq');
  221 + // 办事处主管:只能编辑 :前期不合格描述 bsczg
  222 + this.isEditEarlyNonconformityDescription = this.roleCodes.includes('bsczg');
  223 + },
  224 + async loadDetail(id) {
  225 + try {
  226 + const res = await getDetailApi(id)
  227 + const m = res.data || {}
  228 + const next = { ...this.form, ...m }
  229 + // 确保ID存在
  230 + next.id = m.id || id
  231 + next.sampleQuantityRegulationName = m.sampleQuantityRegulation === true ? '是' : '否'
  232 + next.specificationQuantityRegulationName = m.specificationQuantityRegulation === true ? '是' : '否'
  233 + // 映射列表
  234 + // 注意:详情返回的是 productSampleConfirmationSlipDetailList,需要赋值给 initPurchaseOrderLineList 以便 Product 组件初始化
  235 + // 且需要处理字段兼容性,确保 Product 组件能正确显示和编辑
  236 + const lines = Array.isArray(m.productSampleConfirmationSlipDetailList) ? m.productSampleConfirmationSlipDetailList.map(x => ({
  237 + ...x,
  238 + // 确保 Product 组件需要的字段存在
  239 + // Product组件使用: quantity(需发), shippedQuantity(实发), supplementaryQuantity(需求补货), salesPrice(单价)
  240 + // 详情接口返回的字段应该已经包含了这些,如果有差异需要在此处转换
  241 + // 注意:add.vue中 onRelateConfirm 做了映射,这里是回显,通常直接使用即可
  242 + })) : []
  243 +
  244 + this.form = next;
  245 + this.initPurchaseOrderLineList = lines;
  246 + } catch (e) {
  247 + this.form = {}
  248 + }
  249 + },
  250 + loadAllDicData() {
  251 + const dicCodes = ['ENTERPRISE_TYPE', 'SAMPLE_TYPE', 'CLEAR_PARAMETERS', 'PROVIDE_SAMPLES']
  252 + return getDicByCodes(dicCodes).then(results => {
  253 + this.dicOptions.ENTERPRISE_TYPE = results.ENTERPRISE_TYPE.data || []
  254 + this.dicOptions.SAMPLE_TYPE = results.SAMPLE_TYPE.data || []
  255 + this.dicOptions.CLEAR_PARAMETERS = results.CLEAR_PARAMETERS.data || []
  256 + this.dicOptions.PROVIDE_SAMPLES = results.PROVIDE_SAMPLES.data || []
  257 +
  258 + this.enterpriseTypeLocal = (this.dicOptions.ENTERPRISE_TYPE || []).map(it => ({
  259 + value: it.code,
  260 + label: it.name
  261 + }))
  262 + this.sampleTypeLocal = (this.dicOptions.SAMPLE_TYPE || []).map(it => ({
  263 + value: it.code,
  264 + label: it.name
  265 + }))
  266 + this.provideSamplesOptions = (this.dicOptions.PROVIDE_SAMPLES || []).map(it => ({
  267 + value: it.code,
  268 + label: it.name
  269 + }))
  270 + this.clearParametersOptions = (this.dicOptions.CLEAR_PARAMETERS || []).map(it => ({
  271 + value: it.code,
  272 + label: it.name
  273 + }))
  274 + }).catch(() => {
  275 + this.dicOptions = {
  276 + ENTERPRISE_TYPE: [], // 客户类型
  277 + SAMPLE_TYPE: [], // 试样种类
  278 + CLEAR_PARAMETERS: [], // 是否提供明确参数
  279 + PROVIDE_SAMPLES: [], // 是否提供样品
  280 + }
  281 + this.enterpriseTypeLocal = []
  282 + this.sampleTypeLocal = []
  283 + this.provideSamplesOptions = []
  284 + this.clearParametersOptions = []
  285 + })
  286 + },
  287 + calculateSummary(list) {
  288 + const summary = (list || []).reduce((acc, it) => {
  289 + const qty = Number(it.supplementaryQuantity) || 0
  290 + const shipped = Number(it.shippedQuantity) || 0
  291 + const orderQty = Number(it.quantity) || 0
  292 + acc.totalSupplementaryQuantity += qty
  293 + acc.totalShippedQuantity += shipped
  294 + acc.totalQuantity += orderQty
  295 + return acc
  296 + }, { totalQuantity: 0, totalShippedQuantity: 0, totalSupplementaryQuantity: 0 })
  297 +
  298 + const fixedTotalQuantity = Number(summary.totalQuantity.toFixed(2))
  299 + const fixedTotalShippedQuantity = Number(summary.totalShippedQuantity.toFixed(2))
  300 + const fixedTotalSupplementaryQuantity = Number(summary.totalSupplementaryQuantity.toFixed(2))
  301 + this.form.totalQuantity = fixedTotalQuantity
  302 + this.form.totalShippedQuantity = fixedTotalShippedQuantity
  303 + this.form.totalSupplementaryQuantity = fixedTotalSupplementaryQuantity
  304 + },
  305 + purchaseOrderLineListChange(data) {
  306 + const list = Array.isArray(data) ? data : []
  307 + this.form.productSampleConfirmationSlipDetailList = list
  308 + },
  309 + getFormValues() {
  310 + delete this.form.status;
  311 + const m = this.form || {}
  312 + return JSON.parse(JSON.stringify(m))
  313 + },
  314 + // 审批-通过:校验表单
  315 + checkForm() {
  316 + return true
  317 + },
  318 + openSheet(field) {
  319 + let options = []
  320 + let title = ''
  321 + let value = ''
  322 +
  323 + if (field === 'sampleType') {
  324 + title = '试样种类'
  325 + options = this.sampleTypeLocal
  326 + value = this.form.sampleType
  327 + } else if (field === 'customerType') {
  328 + title = '客户类型'
  329 + options = this.enterpriseTypeLocal
  330 + value = this.form.customerType
  331 + } else if (field === 'sampleQuantityRegulation') {
  332 + title = '本次试样数量是否超规定'
  333 + options = [{ value: true, label: '是' }, { value: false, label: '否' }]
  334 + value = this.form.sampleQuantityRegulation
  335 + } else if (field === 'specificationQuantityRegulation') {
  336 + title = '试样规格个数是否超规定'
  337 + options = [{ value: true, label: '是' }, { value: false, label: '否' }]
  338 + value = this.form.specificationQuantityRegulation
  339 + }
  340 +
  341 + const match = options.find(o => String(o.value) === String(value))
  342 + this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
  343 + },
  344 + onSheetConfirm({ value, label }) {
  345 + const field = this.sheet.field
  346 + this.form[field] = value
  347 + this.form[field + 'Name'] = label
  348 + this.sheet.visible = false
  349 + },
  350 + }
  351 +}
  352 +</script>
  353 +
  354 +<style lang="scss" scoped>
  355 +.page {
  356 + display: flex;
  357 + flex-direction: column;
  358 + height: 100vh;
  359 +}
  360 +
  361 +.scroll {
  362 + flex: 1;
  363 + background: #f3f3f3;
  364 +}
  365 +
  366 +.detail-page {
  367 + padding-bottom: 150rpx;
  368 +}
  369 +
  370 +.section {
  371 + padding: 32rpx;
  372 + background: #fff;
  373 + margin-bottom: 20rpx;
  374 + position: relative;
  375 +
  376 +}
  377 +
  378 +.row {
  379 + display: flex;
  380 + margin-bottom: 28rpx;
  381 +
  382 + &:last-child {
  383 + margin-bottom: 0;
  384 + }
  385 +
  386 + &.company {
  387 + font-size: 36rpx;
  388 + font-weight: 600;
  389 + color: rgba(0, 0, 0, 0.9);
  390 + padding-top: 10rpx;
  391 + margin-bottom: 32rpx;
  392 + line-height: 50rpx;
  393 + }
  394 +
  395 + .label {
  396 + width: 240rpx;
  397 + line-height: 32rpx;
  398 + font-size: 28rpx;
  399 + color: rgba(0, 0, 0, 0.6);
  400 + }
  401 +
  402 + .value {
  403 + flex: 1;
  404 + line-height: 32rpx;
  405 + font-size: 28rpx;
  406 + color: rgba(0, 0, 0, 0.9);
  407 + text-align: right;
  408 + word-break: break-all;
  409 + }
  410 +}
  411 +
  412 +.title-header {
  413 + background-color: #fff;
  414 + display: flex;
  415 + align-items: center;
  416 + padding: 32rpx 32rpx 22rpx;
  417 + border-bottom: 1rpx dashed #f0f0f0;
  418 +
  419 + &_icon {
  420 + width: 32rpx;
  421 + height: 28rpx;
  422 + margin-right: 16rpx;
  423 + }
  424 +
  425 + span {
  426 + color: rgba(0, 0, 0, 0.9);
  427 + font-size: 32rpx;
  428 + line-height: 44rpx;
  429 + font-weight: 600;
  430 + }
  431 +}
  432 +
  433 +.section3 {
  434 + padding: 0 32rpx;
  435 + background-color: #fff;
  436 + margin-bottom: 20rpx;
  437 +}
  438 +
  439 +.view-total {
  440 + padding: 20rpx 0;
  441 +
  442 + .head {
  443 + font-size: 32rpx;
  444 + font-weight: 600;
  445 + line-height: 50rpx;
  446 + color: rgba(0, 0, 0, 0.9);
  447 + padding-bottom: 16rpx;
  448 + margin-bottom: 24rpx;
  449 + border-bottom: 1px dashed #E7E7E7;
  450 + }
  451 +
  452 + .row {
  453 + display: flex;
  454 + margin-bottom: 24rpx;
  455 + line-height: 32rpx;
  456 +
  457 + .row2 {
  458 + width: 50%;
  459 + }
  460 +
  461 + .label {
  462 + width: 180rpx;
  463 + margin-right: 14rpx;
  464 + color: rgba(0, 0, 0, 0.6);
  465 + font-size: 28rpx;
  466 + }
  467 +
  468 + .value {
  469 + flex: 1;
  470 + color: rgba(0, 0, 0, 0.9);
  471 + font-size: 28rpx;
  472 + white-space: pre-wrap;
  473 + word-break: break-all;
  474 + }
  475 + }
  476 +}
  477 +
  478 +::v-deep .no-border-top {
  479 + .uni-list--border::after {
  480 + background-color: transparent;
  481 + }
  482 +}
  483 +
  484 +.detail-section {
  485 +
  486 + .section {
  487 + padding: 32rpx;
  488 + background: #fff;
  489 + margin-bottom: 20rpx;
  490 + position: relative;
  491 +
  492 +
  493 + }
  494 +
  495 + .row {
  496 + display: flex;
  497 + margin-bottom: 28rpx;
  498 +
  499 + &:last-child {
  500 + margin-bottom: 0;
  501 + }
  502 +
  503 + &.company {
  504 + font-size: 36rpx;
  505 + font-weight: 600;
  506 + color: rgba(0, 0, 0, 0.9);
  507 + padding-top: 10rpx;
  508 + margin-bottom: 32rpx;
  509 + line-height: 50rpx;
  510 + }
  511 +
  512 + .label {
  513 + max-width: 400rpx;
  514 + margin-right: 20rpx;
  515 + line-height: 32rpx;
  516 + font-size: 28rpx;
  517 + color: rgba(0, 0, 0, 0.6);
  518 + }
  519 +
  520 + .value {
  521 + flex: 1;
  522 + line-height: 32rpx;
  523 + font-size: 28rpx;
  524 + color: rgba(0, 0, 0, 0.9);
  525 + text-align: right;
  526 + word-break: break-all;
  527 + }
  528 + }
  529 +
  530 + .title-header {
  531 + background-color: #fff;
  532 + display: flex;
  533 + align-items: center;
  534 + padding: 32rpx 32rpx 22rpx;
  535 + border-bottom: 1rpx dashed #f0f0f0;
  536 +
  537 + &_icon {
  538 + width: 32rpx;
  539 + height: 28rpx;
  540 + margin-right: 16rpx;
  541 + }
  542 +
  543 + span {
  544 + color: rgba(0, 0, 0, 0.9);
  545 + font-size: 32rpx;
  546 + line-height: 44rpx;
  547 + font-weight: 600;
  548 + }
  549 + }
  550 +
  551 + .section2 {
  552 + background: #f1f1f1;
  553 + }
  554 +
  555 +
  556 + .section3 {
  557 + padding: 0 32rpx;
  558 + background-color: #fff;
  559 + margin: 20rpx 0;
  560 + }
  561 +
  562 + .view-total {
  563 + padding-bottom: 20rpx;
  564 +
  565 + .head {
  566 + font-size: 32rpx;
  567 + font-weight: 600;
  568 + line-height: 50rpx;
  569 + color: rgba(0, 0, 0, 0.9);
  570 + padding-bottom: 16rpx;
  571 + margin-bottom: 24rpx;
  572 + border-bottom: 1px dashed #E7E7E7;
  573 + }
  574 +
  575 + .row {
  576 + display: flex;
  577 + margin-bottom: 24rpx;
  578 + line-height: 32rpx;
  579 +
  580 + .row2 {
  581 + width: 50%;
  582 + }
  583 +
  584 + .label {
  585 + width: 180rpx;
  586 + margin-right: 14rpx;
  587 + color: rgba(0, 0, 0, 0.6);
  588 + font-size: 28rpx;
  589 + }
  590 +
  591 + .value {
  592 + flex: 1;
  593 + color: rgba(0, 0, 0, 0.9);
  594 + font-size: 28rpx;
  595 + white-space: pre-wrap;
  596 + word-break: break-all;
  597 + }
  598 + }
  599 + }
  600 +}
  601 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row company">{{ form.orderingUnitName }}</text>
  7 + <view :class="['status', `status_${form.status}`]" />
  8 + <view class="row"><text class="label">试样种类</text><text class="value">{{ form.sampleTypeName }}</text></view>
  9 + <view class="row"><text class="label">客户类型</text><text class="value">{{ form.customerTypeName }}</text></view>
  10 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName }}</text></view>
  11 + <view class="row"><text class="label">所属品种</text><text class="value">{{ form.belongingBreed }}</text></view>
  12 + <view class="row"><text class="label">原供货同行</text><text class="value">{{ form.originalSupplierPeer }}</text>
  13 + </view>
  14 + </view>
  15 +
  16 + <!-- 产品 -->
  17 + <view class="section2">
  18 + <Product mode="view" :list="form.productSampleConfirmationSlipDetailList" />
  19 + </view>
  20 +
  21 + <view class="section3">
  22 + <view class="view-total">
  23 + <view class="head">合计</view>
  24 + <view class="row">
  25 + <view class="row2">
  26 + <text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text>
  27 + </view>
  28 + </view>
  29 + </view>
  30 + </view>
  31 +
  32 + <view class="section">
  33 + <view class="row"><text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text></view>
  34 + <view class="row"><text class="label">本次试样数量是否超规定</text><text class="value">{{ form.sampleQuantityRegulation ?
  35 + '是' : '否' }}</text></view>
  36 + <view class="row"><text class="label">试样规格个数是否超规定</text><text class="value">{{
  37 + form.specificationQuantityRegulation ? '是' : '否' }}</text></view>
  38 + <view class="row"><text class="label">试样次数</text><text class="value">{{ form.sampleFrequency }}</text></view>
  39 + <view class="row"><text class="label">前期不合格描述</text><text class="value">{{ form.earlyNonconformityDescription
  40 + }}</text></view>
  41 + </view>
  42 + </view>
  43 + </scroll-view>
  44 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
  45 + </view>
  46 +</template>
  47 +
  48 +<script>
  49 +import { getDetailApi } from '@/api/confirmation_form.js'
  50 +import Product from './product.vue'
  51 +import DetailButtons from '@/components/detail-buttons/index.vue'
  52 +
  53 +export default {
  54 + name: 'ConfirmationFormDetail',
  55 + components: { Product, DetailButtons },
  56 + data() {
  57 + return {
  58 + form: {},
  59 + buttons: [
  60 + { text: '编辑', visible: true, variant: 'outline', event: 'edit' },
  61 + { text: '审核详情', visible: true, variant: 'outline', event: 'auditDetail' },
  62 + { text: '审核', visible: true, variant: 'primary', event: 'audit' },
  63 + ]
  64 + }
  65 + },
  66 + computed: {
  67 + statusFlags() {
  68 + const m = this.form || {}
  69 + const e = String(m.status || '')
  70 + // showAudit 这个字段就是后端自己判断的 制单人所在区域 所有人 权限
  71 + return {
  72 + canEdit: !e && m.showAudit || false,
  73 + canAudit: e === 'AUDIT' && m.showExamine || false,
  74 + canAuditDetail: !!e || false,
  75 + }
  76 + },
  77 + displayButtons() {
  78 + const f = this.statusFlags
  79 + return [
  80 + { ...this.buttons[0], visible: f.canEdit }, // 不需要额外的角色权限
  81 + { ...this.buttons[1], visible: f.canAuditDetail && this.$auth.hasPermi('sample-order:confirmation-form:review') },
  82 + { ...this.buttons[2], visible: f.canAudit && this.$auth.hasPermi('sample-order:confirmation-form:approve') },
  83 + ]
  84 + }
  85 + },
  86 + onLoad(query) {
  87 + const id = (query && (query.id || query.code)) || ''
  88 + if (id) this.loadDetail(id)
  89 + },
  90 + methods: {
  91 + async loadDetail(id) {
  92 + try {
  93 + const res = await getDetailApi(id)
  94 + this.form = res.data || {}
  95 + } catch (e) {
  96 + this.form = {}
  97 + }
  98 + },
  99 + handleButtonClick(btn) {
  100 + if (!btn || btn.disabled) return
  101 + const map = {
  102 + edit: () => this.onEdit(),
  103 + auditDetail: () => this.onAuditDetail(),
  104 + audit: () => this.onAudit(),
  105 + }
  106 + const fn = map[btn.event]
  107 + if (typeof fn === 'function') fn()
  108 + },
  109 + onEdit() {
  110 + const id = this.form.id || this.form.code
  111 + if (id) uni.navigateTo({ url: `/pages/confirmation_form/modify?id=${id}` })
  112 + },
  113 + onAuditDetail() {
  114 + uni.setStorageSync('sourceBusinessId', this.form.id)
  115 + uni.navigateTo({ url: '/pages/flow/audit_detail' })
  116 + },
  117 + onAudit() {
  118 + uni.setStorageSync('sourceBusinessId', this.form.id)
  119 + uni.navigateTo({ url: '/pages/flow/audit' })
  120 + },
  121 +
  122 + }
  123 +}
  124 +</script>
  125 +
  126 +<style lang="scss" scoped>
  127 +.page {
  128 + display: flex;
  129 + flex-direction: column;
  130 + height: 100vh;
  131 +}
  132 +
  133 +.scroll {
  134 + flex: 1;
  135 + background: #f3f3f3;
  136 +}
  137 +
  138 +.detail-page {
  139 + padding-bottom: 150rpx;
  140 +}
  141 +
  142 +.section {
  143 + padding: 32rpx;
  144 + background: #fff;
  145 + margin-bottom: 20rpx;
  146 + position: relative;
  147 +
  148 + .status {
  149 + position: absolute;
  150 + top: 16rpx;
  151 + right: 52rpx;
  152 + width: 180rpx;
  153 + height: 146rpx;
  154 + background-repeat: no-repeat;
  155 + background-size: 100% 100%;
  156 + background-position: center;
  157 +
  158 + &_AUDIT {
  159 + background-image: url('~@/static/images/dev_manage/status_1.png');
  160 + }
  161 +
  162 + &_PASS {
  163 + background-image: url('~@/static/images/dev_manage/status_2.png');
  164 + }
  165 +
  166 + &_REFUSE {
  167 + background-image: url('~@/static/images/dev_manage/status_3.png');
  168 + }
  169 +
  170 + &_CANCEL {
  171 + background-image: url('~@/static/images/dev_manage/status_4.png');
  172 + }
  173 +
  174 + }
  175 +}
  176 +
  177 +.row {
  178 + display: flex;
  179 + margin-bottom: 28rpx;
  180 +
  181 + &:last-child {
  182 + margin-bottom: 0;
  183 + }
  184 +
  185 + &.company {
  186 + font-size: 36rpx;
  187 + font-weight: 600;
  188 + color: rgba(0, 0, 0, 0.9);
  189 + padding-top: 10rpx;
  190 + margin-bottom: 32rpx;
  191 + line-height: 50rpx;
  192 + }
  193 +
  194 + .label {
  195 + max-width: 400rpx;
  196 + margin-right: 20rpx;
  197 + line-height: 32rpx;
  198 + font-size: 28rpx;
  199 + color: rgba(0, 0, 0, 0.6);
  200 + }
  201 +
  202 + .value {
  203 + flex: 1;
  204 + line-height: 32rpx;
  205 + font-size: 28rpx;
  206 + color: rgba(0, 0, 0, 0.9);
  207 + text-align: right;
  208 + word-break: break-all;
  209 + }
  210 +}
  211 +
  212 +.title-header {
  213 + background-color: #fff;
  214 + display: flex;
  215 + align-items: center;
  216 + padding: 32rpx 32rpx 22rpx;
  217 + border-bottom: 1rpx dashed #f0f0f0;
  218 +
  219 + &_icon {
  220 + width: 32rpx;
  221 + height: 28rpx;
  222 + margin-right: 16rpx;
  223 + }
  224 +
  225 + span {
  226 + color: rgba(0, 0, 0, 0.9);
  227 + font-size: 32rpx;
  228 + line-height: 44rpx;
  229 + font-weight: 600;
  230 + }
  231 +}
  232 +
  233 +.section2 {
  234 + background: #f1f1f1;
  235 +}
  236 +
  237 +
  238 +.section3 {
  239 + padding: 0 32rpx;
  240 + background-color: #fff;
  241 + margin: 20rpx 0;
  242 +}
  243 +
  244 +.view-total {
  245 + padding-bottom: 20rpx;
  246 +
  247 + .head {
  248 + font-size: 32rpx;
  249 + font-weight: 600;
  250 + line-height: 50rpx;
  251 + color: rgba(0, 0, 0, 0.9);
  252 + padding-bottom: 16rpx;
  253 + margin-bottom: 24rpx;
  254 + border-bottom: 1px dashed #E7E7E7;
  255 + }
  256 +
  257 + .row {
  258 + display: flex;
  259 + margin-bottom: 24rpx;
  260 + line-height: 32rpx;
  261 +
  262 + .row2 {
  263 + width: 50%;
  264 + }
  265 +
  266 + .label {
  267 + width: 180rpx;
  268 + margin-right: 14rpx;
  269 + color: rgba(0, 0, 0, 0.6);
  270 + font-size: 28rpx;
  271 + }
  272 +
  273 + .value {
  274 + flex: 1;
  275 + color: rgba(0, 0, 0, 0.9);
  276 + font-size: 28rpx;
  277 + white-space: pre-wrap;
  278 + word-break: break-all;
  279 + }
  280 + }
  281 +}
  282 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="dev-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入客户名称或订单编号" clearButton="auto"
  6 + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search"
  7 + @input="onSearchInput" />
  8 + <view class="tool-icons">
  9 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  10 + </view>
  11 + </view>
  12 + </view>
  13 +
  14 +
  15 + <!-- 列表卡片组件 -->
  16 + <view class="list-box">
  17 + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id"
  18 + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError">
  19 + <template v-slot="{ item, selected }">
  20 + <view class="card" @click.stop="onCardClick(item)">
  21 + <view class="card-header">
  22 + <text class="title omit2">{{ item.customerName }}</text>
  23 + <text :class="['status', `status_${item.status}`]">{{ filterStatus(item.status) }}</text>
  24 + </view>
  25 + <view class="info-row">
  26 + <text>订单编号</text><text>{{ item.orderCode || '-' }}</text>
  27 + </view>
  28 + <view class="info-row">
  29 + <text>生产厂</text><text>{{ item.workshopName || '-' }}</text>
  30 + </view>
  31 + </view>
  32 + </template>
  33 + </card-list>
  34 + </view>
  35 +
  36 +
  37 +
  38 + <!-- 筛选弹框 -->
  39 + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset"
  40 + @confirm="onFilterConfirm">
  41 + <template v-slot="{ model }">
  42 + <view class="filter-form">
  43 + <view class="form-item">
  44 + <view class="label">生产厂</view>
  45 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  46 + v-model="model.workshopId" @change="onWorkshopChange" :localdata="workshopOptions" />
  47 + </view>
  48 + <view class="form-item">
  49 + <view class="label">审核状态</view>
  50 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  51 + v-model="model.status" @change="onStatusChange" :localdata="statusLocal" />
  52 + </view>
  53 + <!-- <view class="form-item">
  54 + <view class="label">试样种类</view>
  55 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  56 + v-model="model.sampleType" @change="onSampleTypeChange" :localdata="sampleTypeLocal" />
  57 + </view>
  58 + <view class="form-item">
  59 + <view class="label">客户类型</view>
  60 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  61 + v-model="model.customerType" @change="onEnterpriseTypeChange" :localdata="enterpriseTypeLocal" />
  62 + </view> -->
  63 + </view>
  64 + </template>
  65 + </filter-modal>
  66 + </view>
  67 +</template>
  68 +
  69 +<script>
  70 +import CardList from '@/components/card/index.vue'
  71 +import FilterModal from '@/components/filter/index.vue'
  72 +import { workshopQueryApi } from '@/api/devManage.js'
  73 +import SingleSelectSheet from '@/components/single-select/index.vue'
  74 +import {
  75 + queryApi
  76 +} from '@/api/follow_up_form.js'
  77 +import {
  78 + getDicByCodes
  79 +} from '@/utils/dic'
  80 +
  81 +export default {
  82 + components: {
  83 + CardList,
  84 + FilterModal,
  85 + SingleSelectSheet
  86 + },
  87 + data() {
  88 + return {
  89 + searchKeyword: '',
  90 + searchKeywordDebounced: '',
  91 + tabs: [],
  92 + // 给到 card 的筛选值
  93 + query: {
  94 + status: '',
  95 + workshopId: '',
  96 + },
  97 + extraParams: {},
  98 +
  99 + // 批量选择
  100 + rowKey: 'id',
  101 + currentItems: [],
  102 +
  103 + // 筛选弹框
  104 + filterVisible: false,
  105 + filterForm: {
  106 + status: '',
  107 + workshopId: '',
  108 + },
  109 + dicOptions: {
  110 + AUDIT_STATUS: [], // 审核
  111 + },
  112 + statusLocal: [],
  113 + workshopOptions: [],
  114 + }
  115 + },
  116 + computed: {
  117 + extraCombined() {
  118 + return {
  119 + searchKey: this.searchKeywordDebounced || undefined
  120 + }
  121 + }
  122 + },
  123 + watch: {
  124 + extraCombined: {
  125 + deep: true,
  126 + handler(v) {
  127 + this.extraParams = v
  128 + },
  129 + immediate: true
  130 + },
  131 +
  132 + },
  133 + created() {
  134 + this.loadAllDicData()
  135 + this.loadWorkshopOptions()
  136 + },
  137 + onLoad() { },
  138 + // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  139 + onReachBottom() {
  140 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  141 + this.$refs.cardRef.onLoadMore()
  142 + }
  143 + },
  144 + beforeDestroy() {
  145 + if (this.searchDebounceTimer) {
  146 + clearTimeout(this.searchDebounceTimer)
  147 + this.searchDebounceTimer = null
  148 + }
  149 + },
  150 + methods: {
  151 + async loadWorkshopOptions() {
  152 + try {
  153 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  154 + const list = (res && res.data && res.data.datas) || []
  155 + this.workshopOptions = list.map(it => ({ text: it.name || it.workshopName || '', value: it.id || it.workshopId || '' }))
  156 + } catch (e) {
  157 + this.workshopOptions = []
  158 + }
  159 + },
  160 + onCardLoaded({
  161 + items
  162 + }) {
  163 + this.currentItems = items
  164 + },
  165 + onCardError() {
  166 + uni.showToast({
  167 + title: '列表加载失败',
  168 + icon: 'none'
  169 + })
  170 + },
  171 + // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
  172 + onSearchInput(val) {
  173 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  174 + this.searchDebounceTimer = setTimeout(() => {
  175 + this.searchKeywordDebounced = this.searchKeyword
  176 + this.searchDebounceTimer = null
  177 + }, 1200)
  178 + },
  179 + // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
  180 + search(e) {
  181 + const val = e && e.value != null ? e.value : this.searchKeyword
  182 + this.searchKeyword = val
  183 + this.searchKeywordDebounced = val
  184 + },
  185 + onAdd() {
  186 + uni.navigateTo({
  187 + url: '/pages/follow_up_form/add'
  188 + })
  189 + },
  190 + openFilter() {
  191 + this.filterVisible = true
  192 + },
  193 + onFilterReset(payload) {
  194 + this.filterForm = payload
  195 + },
  196 + onFilterConfirm(payload) {
  197 + if ((payload.status === '' || payload.status == null) && this.filterForm.status !== '') {
  198 + payload.status = this.filterForm.status
  199 + }
  200 + this.query = {
  201 + ...payload
  202 + }
  203 + },
  204 + onStatusChange(e) {
  205 + const raw = e && e.detail && e.detail.value !== undefined ?
  206 + e.detail.value :
  207 + (e && e.value !== undefined ? e.value : '')
  208 + this.filterForm.status = raw
  209 + },
  210 +
  211 + onSampleTypeChange(e) {
  212 + const raw = e && e.detail && e.detail.value !== undefined ?
  213 + e.detail.value :
  214 + (e && e.value !== undefined ? e.value : '')
  215 + this.filterForm.sampleType = raw
  216 + },
  217 +
  218 + onEnterpriseTypeChange(e) {
  219 + const raw = e && e.detail && e.detail.value !== undefined ?
  220 + e.detail.value :
  221 + (e && e.value !== undefined ? e.value : '')
  222 + this.filterForm.customerType = raw
  223 + },
  224 +
  225 + // 列表接口(真实请求)
  226 + fetchList({
  227 + pageIndex,
  228 + pageSize,
  229 + query,
  230 + extra
  231 + }) {
  232 + const params = {
  233 + pageIndex,
  234 + pageSize,
  235 + ...extra,
  236 + ...query
  237 + }
  238 + if (this.searchKeywordDebounced) {
  239 + params.searchKey = this.searchKeywordDebounced
  240 + }
  241 + return queryApi(params)
  242 + .then(res => {
  243 + const _data = res.data || {};
  244 + const records = _data.datas || [];
  245 + const totalCount = _data.totalCount || 0;
  246 + const hasNext = _data.hasNext || false
  247 + return {
  248 + records,
  249 + totalCount,
  250 + hasNext
  251 + }
  252 + })
  253 + .catch(err => {
  254 + console.error('fetchList error', err)
  255 + this.onCardError()
  256 + return {
  257 + records: [],
  258 + totalCount: 0,
  259 + hasNext: false
  260 + }
  261 + })
  262 + },
  263 + loadAllDicData() {
  264 + const dicCodes = ['AUDIT_STATUS']
  265 + return getDicByCodes(dicCodes).then(results => {
  266 + this.dicOptions.AUDIT_STATUS = results.AUDIT_STATUS.data || []
  267 + this.statusLocal = (this.dicOptions.AUDIT_STATUS || []).map(it => ({
  268 + value: it.code,
  269 + text: it.name
  270 + }))
  271 + }).catch(() => {
  272 + this.dicOptions = {
  273 + AUDIT_STATUS: [],
  274 + }
  275 + this.statusLocal = []
  276 + })
  277 + },
  278 + onCardClick(item) {
  279 + const id = (item && (item.id || item.code)) || ''
  280 + if (!id) return
  281 + const query = '?id=' + encodeURIComponent(id)
  282 + uni.navigateTo({
  283 + url: '/pages/follow_up_form/detail' + query
  284 + })
  285 + },
  286 + onWorkshopChange(e) {
  287 + const raw = e && e.detail && e.detail.value !== undefined ? e.detail.value : (e && e.value !== undefined ? e.value : '')
  288 + this.filterForm.workshopId = raw
  289 + const match = (this.workshopOptions || []).find(o => String(o.value) === String(raw))
  290 + this.filterForm.workshopIdName = match ? (match.text || '') : ''
  291 + },
  292 + filterStatus(status) {
  293 + const _item = this.statusLocal.filter(item => item.value === status)[0] || {};
  294 + return _item.text || '';
  295 + },
  296 + }
  297 +}
  298 +</script>
  299 +
  300 +<style lang="scss" scoped>
  301 +.page {
  302 + display: flex;
  303 + flex-direction: column;
  304 + height: 100vh;
  305 +}
  306 +
  307 +.dev-list-fixed {
  308 + position: fixed;
  309 + top: 96rpx;
  310 + left: 0;
  311 + right: 0;
  312 + z-index: 2;
  313 + background: #fff;
  314 +
  315 + .search-row {
  316 + display: flex;
  317 + align-items: center;
  318 + padding: 16rpx 32rpx;
  319 +
  320 + .uni-searchbar {
  321 + padding: 0;
  322 + flex: 1;
  323 + }
  324 +
  325 + .tool-icons {
  326 + display: flex;
  327 +
  328 + .tool-icon {
  329 + width: 48rpx;
  330 + height: 48rpx;
  331 + display: block;
  332 + margin-left: 32rpx;
  333 + }
  334 + }
  335 + }
  336 +
  337 +}
  338 +
  339 +/* 仅当前页覆盖 uni-search-bar 盒子高度 */
  340 +::v-deep .uni-searchbar__box {
  341 + height: 80rpx !important;
  342 + justify-content: start;
  343 +
  344 + .uni-searchbar__box-search-input {
  345 + font-size: 32rpx !important;
  346 + }
  347 +}
  348 +
  349 +.list-box {
  350 + flex: 1;
  351 + padding-top: 140rpx;
  352 +
  353 + &.pad-batch {
  354 + padding-bottom: 144rpx;
  355 + }
  356 +
  357 + .card {
  358 + position: relative;
  359 + }
  360 +
  361 + .card-header {
  362 + margin-bottom: 28rpx;
  363 + position: relative;
  364 +
  365 + .title {
  366 + font-size: 36rpx;
  367 + font-weight: 600;
  368 + line-height: 50rpx;
  369 + color: rgba(0, 0, 0, 0.9);
  370 + width: 578rpx;
  371 + }
  372 +
  373 + .status {
  374 + font-weight: 600;
  375 + position: absolute;
  376 + top: -32rpx;
  377 + right: -12rpx;
  378 + height: 48rpx;
  379 + line-height: 48rpx;
  380 + color: #fff;
  381 + font-size: 24rpx;
  382 + padding: 0 14rpx;
  383 + border-radius: 6rpx;
  384 +
  385 + // 审核中
  386 + &.status_AUDIT {
  387 + background: $theme-primary;
  388 + }
  389 +
  390 + // 审核通过
  391 + &.status_PASS {
  392 + background: #2BA471;
  393 + }
  394 +
  395 + // 已驳回
  396 + &.status_REFUSE {
  397 + background: #d54941;
  398 + }
  399 +
  400 + // 已取消
  401 + &.status_CANCEL {
  402 + background: #e7e7e7;
  403 + color: rgba(0, 0, 0, 0.6);
  404 + }
  405 +
  406 + }
  407 +
  408 + }
  409 +
  410 + .info-row {
  411 + display: flex;
  412 + align-items: center;
  413 + color: rgba(0, 0, 0, 0.6);
  414 + font-size: 28rpx;
  415 + margin-bottom: 24rpx;
  416 +
  417 + &:last-child {
  418 + margin-bottom: 0;
  419 + }
  420 +
  421 + text {
  422 + width: 60%;
  423 + line-height: 32rpx;
  424 +
  425 + &:last-child {
  426 + color: rgba(0, 0, 0, 0.9);
  427 + width: 40%;
  428 + }
  429 +
  430 + &.category {
  431 + display: inline-block;
  432 + padding: 4rpx 12rpx;
  433 + border-radius: 6rpx;
  434 + font-size: 24rpx;
  435 + width: auto;
  436 +
  437 + &.category_A {
  438 + background: #FFF0ED;
  439 + color: #D54941;
  440 + }
  441 +
  442 + &.category_B {
  443 + background: #FFF1E9;
  444 + color: #E37318;
  445 + }
  446 +
  447 + &.category_C {
  448 + background: #F2F3FF;
  449 + color: $theme-primary;
  450 + }
  451 +
  452 + &.category_D {
  453 + background: #E3F9E9;
  454 + color: #2BA471;
  455 + }
  456 + }
  457 + }
  458 +
  459 + }
  460 +}
  461 +
  462 +.filter-form {
  463 + .form-item {
  464 + margin-bottom: 24rpx;
  465 + }
  466 +
  467 + .label {
  468 + margin-bottom: 20rpx;
  469 + color: rgba(0, 0, 0, 0.9);
  470 + height: 44rpx;
  471 + line-height: 44rpx;
  472 + font-size: 30rpx;
  473 + }
  474 +
  475 + .uni-easyinput {
  476 + border: 1rpx solid #f3f3f3;
  477 + }
  478 +
  479 +}
  480 +
  481 +/* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
  482 +::v-deep .filter-form .uni-data-checklist .checklist-group {
  483 + .checklist-box {
  484 + &.is--tag {
  485 + width: 212rpx;
  486 + margin-top: 0;
  487 + margin-bottom: 24rpx;
  488 + margin-right: 24rpx;
  489 + height: 80rpx;
  490 + padding: 0 20rpx;
  491 + border-radius: 12rpx;
  492 + background-color: #f3f3f3;
  493 + border-color: #f3f3f3;
  494 +
  495 + &:nth-child(3n) {
  496 + margin-right: 0;
  497 + }
  498 +
  499 + .checklist-content {
  500 + display: flex;
  501 + justify-content: center;
  502 + }
  503 +
  504 + .checklist-text {
  505 + color: rgba(0, 0, 0, 0.9);
  506 + font-size: 28rpx;
  507 + text-align: center;
  508 + }
  509 + }
  510 +
  511 + &.is-checked {
  512 + background-color: $theme-primary-plain-bg !important;
  513 + border-color: $theme-primary-plain-bg !important;
  514 +
  515 + .checklist-text {
  516 + color: $theme-primary !important;
  517 + }
  518 + }
  519 + }
  520 +
  521 +}
  522 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <view class="section">
  6 + <uni-list-item title="订货单位">
  7 + <template v-slot:footer>
  8 + <view class="readonly-text">{{ form.orderingUnitName }}</view>
  9 + </template>
  10 + </uni-list-item>
  11 + <uni-list-item class="select-item" :class="form.sampleTypeName ? 'is-filled' : 'is-empty'" clickable
  12 + @click="openSheet('sampleType')" :rightText="form.sampleTypeName || '请选择'" showArrow>
  13 + <template v-slot:body>
  14 + <view class="item-title"><text>试样种类</text></view>
  15 + </template>
  16 + </uni-list-item>
  17 + <uni-list-item class="select-item" :class="form.customerTypeName ? 'is-filled' : 'is-empty'" clickable
  18 + @click="openSheet('customerType')" :rightText="form.customerTypeName || '请选择'" showArrow>
  19 + <template v-slot:body>
  20 + <view class="item-title"><text>客户类型</text></view>
  21 + </template>
  22 + </uni-list-item>
  23 + <uni-list-item title="生产厂">
  24 + <template v-slot:footer>
  25 + <view class="readonly-text">{{ form.workshopName }}</view>
  26 + </template>
  27 + </uni-list-item>
  28 + <uni-list-item title="所属品种">
  29 + <template v-slot:footer>
  30 + <view class="readonly-text">{{ form.belongingBreed }}</view>
  31 + </template>
  32 + </uni-list-item>
  33 + <uni-list-item title="原供货同行">
  34 + <template v-slot:footer>
  35 + <uni-easyinput v-model="form.originalSupplierPeer" placeholder="请输入原供货同行" :inputBorder="false" />
  36 + </template>
  37 + </uni-list-item>
  38 + </view>
  39 +
  40 + <!-- 产品 -->
  41 + <view class="section2">
  42 + <!-- mode="add" 允许编辑 -->
  43 + <Product mode="add" :list="initPurchaseOrderLineList" @change="purchaseOrderLineListChange"
  44 + :provideSamplesOptions="provideSamplesOptions" :clearParametersOptions="clearParametersOptions" />
  45 + </view>
  46 +
  47 + <view class="section">
  48 +
  49 + <uni-list-item class="select-item" :class="form.sampleQuantityRegulationName ? 'is-filled' : 'is-empty'" clickable
  50 + @click="openSheet('sampleQuantityRegulation')" :rightText="form.sampleQuantityRegulationName || '请选择'" showArrow>
  51 + <template v-slot:body>
  52 + <view class="item-title"><text>本次试样数量是否超规定</text></view>
  53 + </template>
  54 + </uni-list-item>
  55 +
  56 + <uni-list-item class="select-item" :class="form.specificationQuantityRegulationName ? 'is-filled' : 'is-empty'" clickable
  57 + @click="openSheet('specificationQuantityRegulation')" :rightText="form.specificationQuantityRegulationName || '请选择'" showArrow>
  58 + <template v-slot:body>
  59 + <view class="item-title"><text>试样规格个数是否超规定</text></view>
  60 + </template>
  61 + </uni-list-item>
  62 +
  63 + <uni-list-item title="试样次数">
  64 + <template v-slot:footer>
  65 + <uni-easyinput type="textarea" v-model="form.sampleFrequency" placeholder="请输入试样次数" :inputBorder="false" />
  66 + </template>
  67 + </uni-list-item>
  68 +
  69 + <uni-list-item title="前期不合格描述">
  70 + <template v-slot:footer>
  71 + <uni-easyinput type="textarea" v-model="form.earlyNonconformityDescription" placeholder="请输入前期不合格描述" :inputBorder="false" />
  72 + </template>
  73 + </uni-list-item>
  74 +
  75 + </view>
  76 +
  77 + <view class="footer">
  78 + <view class="view-total">
  79 + <view class="head">合计</view>
  80 + <view class="row">
  81 + <view class="row2">
  82 + <text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text>
  83 + </view>
  84 + </view>
  85 + </view>
  86 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
  87 + </view>
  88 + </uni-list>
  89 + </scroll-view>
  90 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value"
  91 + @confirm="onSheetConfirm" />
  92 + </view>
  93 +</template>
  94 +
  95 +<script>
  96 +import { updateApi, getDetailApi } from '@/api/confirmation_form.js'
  97 +import Product from './product.vue'
  98 +import SingleSelectSheet from '@/components/single-select/index.vue'
  99 +import {
  100 + getDicByCodes
  101 +} from '@/utils/dic'
  102 +
  103 +export default {
  104 + name: 'ConfirmationFormModify',
  105 + components: { Product, SingleSelectSheet },
  106 + data() {
  107 + return {
  108 + form: {
  109 + purchaseOrderId: '',
  110 + id: '',
  111 + // 订单基础信息
  112 + purchaseOrderName: '',
  113 + customerName: '',
  114 + customerId: '',
  115 + workshopName: '',
  116 + workshopId: '',
  117 + originPlanShipDate: '',
  118 + deptName: '',
  119 + deptId: '',
  120 + totalQuantity: '',
  121 + totalShippedQuantity: '',
  122 + totalSupplementaryQuantity: '',
  123 + code: '',
  124 + // 默认当前日期 格式为 yyyy-MM-dd
  125 + orderDate: new Date().toISOString().substring(0, 10),
  126 +
  127 + },
  128 + initPurchaseOrderLineList: [],
  129 + maxDeliveryDate: new Date().toISOString().substring(0, 10),
  130 + sheet: { visible: false, title: '请选择', options: [], value: '', field: '' },
  131 + dicOptions: {
  132 + ENTERPRISE_TYPE: [], // 客户类型
  133 + SAMPLE_TYPE: [], // 试样种类
  134 + CLEAR_PARAMETERS: [], // 是否提供明确参数
  135 + PROVIDE_SAMPLES: [], // 是否提供样品
  136 + },
  137 + enterpriseTypeLocal: [], // 客户类型
  138 + sampleTypeLocal: [], // 试样种类
  139 + provideSamplesOptions: [], // 是否提供样品
  140 + clearParametersOptions: [], // 是否提供明确参数
  141 + }
  142 + },
  143 + onLoad(query) {
  144 + const id = (query && (query.id || query.code)) || ''
  145 + if (id) {
  146 + this.loadDetail(id)
  147 + this.loadAllDicData()
  148 + }
  149 + },
  150 + methods: {
  151 + async loadDetail(id) {
  152 + try {
  153 + const res = await getDetailApi(id)
  154 + const m = res.data || {}
  155 + const next = { ...this.form, ...m }
  156 + // 确保ID存在
  157 + next.id = m.id || id
  158 + next.sampleQuantityRegulationName = m.sampleQuantityRegulation === true ? '是' : '否'
  159 + next.specificationQuantityRegulationName = m.specificationQuantityRegulation === true ? '是' : '否'
  160 + // 映射列表
  161 + // 注意:详情返回的是 productSampleConfirmationSlipDetailList,需要赋值给 initPurchaseOrderLineList 以便 Product 组件初始化
  162 + // 且需要处理字段兼容性,确保 Product 组件能正确显示和编辑
  163 + const lines = Array.isArray(m.productSampleConfirmationSlipDetailList) ? m.productSampleConfirmationSlipDetailList.map(x => ({
  164 + ...x,
  165 + // 确保 Product 组件需要的字段存在
  166 + // Product组件使用: quantity(需发), shippedQuantity(实发), supplementaryQuantity(需求补货), salesPrice(单价)
  167 + // 详情接口返回的字段应该已经包含了这些,如果有差异需要在此处转换
  168 + // 注意:add.vue中 onRelateConfirm 做了映射,这里是回显,通常直接使用即可
  169 + })) : []
  170 +
  171 + this.form = next;
  172 + this.initPurchaseOrderLineList = lines;
  173 + } catch (e) {
  174 + uni.showToast({ title: '加载失败', icon: 'none' })
  175 + }
  176 + },
  177 + loadAllDicData() {
  178 + const dicCodes = ['ENTERPRISE_TYPE', 'SAMPLE_TYPE', 'CLEAR_PARAMETERS', 'PROVIDE_SAMPLES']
  179 + return getDicByCodes(dicCodes).then(results => {
  180 + this.dicOptions.ENTERPRISE_TYPE = results.ENTERPRISE_TYPE.data || []
  181 + this.dicOptions.SAMPLE_TYPE = results.SAMPLE_TYPE.data || []
  182 + this.dicOptions.CLEAR_PARAMETERS = results.CLEAR_PARAMETERS.data || []
  183 + this.dicOptions.PROVIDE_SAMPLES = results.PROVIDE_SAMPLES.data || []
  184 +
  185 + this.enterpriseTypeLocal = (this.dicOptions.ENTERPRISE_TYPE || []).map(it => ({
  186 + value: it.code,
  187 + label: it.name
  188 + }))
  189 + this.sampleTypeLocal = (this.dicOptions.SAMPLE_TYPE || []).map(it => ({
  190 + value: it.code,
  191 + label: it.name
  192 + }))
  193 + this.provideSamplesOptions = (this.dicOptions.PROVIDE_SAMPLES || []).map(it => ({
  194 + value: it.code,
  195 + label: it.name
  196 + }))
  197 + this.clearParametersOptions = (this.dicOptions.CLEAR_PARAMETERS || []).map(it => ({
  198 + value: it.code,
  199 + label: it.name
  200 + }))
  201 + }).catch(() => {
  202 + this.dicOptions = {
  203 + ENTERPRISE_TYPE: [], // 客户类型
  204 + SAMPLE_TYPE: [], // 试样种类
  205 + CLEAR_PARAMETERS: [], // 是否提供明确参数
  206 + PROVIDE_SAMPLES: [], // 是否提供样品
  207 + }
  208 + this.enterpriseTypeLocal = []
  209 + this.sampleTypeLocal = []
  210 + this.provideSamplesOptions = []
  211 + this.clearParametersOptions = []
  212 + })
  213 + },
  214 + validateRequired() {
  215 + // const checks = [
  216 + // { key: 'purchaseOrderName', label: '订单编号' }
  217 + // ]
  218 + // for (const it of checks) {
  219 + // const val = this.form[it.key]
  220 + // if (val === undefined || val === null || String(val).trim() === '') {
  221 + // uni.showToast({ title: `请先选择${it.label}`, icon: 'none' })
  222 + // return false
  223 + // }
  224 + // }
  225 + return true
  226 + },
  227 + validateLineListRequired() {
  228 + const list = Array.isArray(this.form.purchaseOrderLineList) ? this.form.purchaseOrderLineList : []
  229 + if (list.length === 0) {
  230 + uni.showToast({ title: '请先添加产品', icon: 'none' })
  231 + return false
  232 + }
  233 + const fields = [
  234 + { key: 'brand', label: '牌号' },
  235 + { key: 'quantity', label: '需发' },
  236 + { key: 'supplementaryQuantity', label: '需求补货' },
  237 + ]
  238 + for (let i = 0; i < list.length; i++) {
  239 + const it = list[i] || {}
  240 + for (const f of fields) {
  241 + const v = it && it[f.key]
  242 + if (v === undefined || v === null || String(v).trim() === '') {
  243 + uni.showToast({ title: `产品第${i + 1}条:${f.label}不能为空!`, icon: 'none' })
  244 + return false
  245 + }
  246 + }
  247 + const has = (v) => v !== undefined && v !== null && String(v).trim() !== ''
  248 + if (has(it.thicknessTolPos) && has(it.thicknessTolNeg)) {
  249 + const pos = Number(it.thicknessTolPos)
  250 + const neg = Number(it.thicknessTolNeg)
  251 + if (!(pos > neg)) {
  252 + uni.showToast({ title: `产品第${i + 1}条:厚度公差上限需大于下限`, icon: 'none' })
  253 + return false
  254 + }
  255 + }
  256 + if (has(it.widthTolPos) && has(it.widthTolNeg)) {
  257 + const pos = Number(it.widthTolPos)
  258 + const neg = Number(it.widthTolNeg)
  259 + if (!(pos > neg)) {
  260 + uni.showToast({ title: `产品第${i + 1}条:宽度公差上限需大于下限`, icon: 'none' })
  261 + return false
  262 + }
  263 + }
  264 + if (has(it.lengthTolPos) && has(it.lengthTolNeg)) {
  265 + const pos = Number(it.lengthTolPos)
  266 + const neg = Number(it.lengthTolNeg)
  267 + if (!(pos > neg)) {
  268 + uni.showToast({ title: `产品第${i + 1}条:长度公差上限需大于下限`, icon: 'none' })
  269 + return false
  270 + }
  271 + }
  272 + }
  273 + return true
  274 + },
  275 + async onSubmit() {
  276 + if (!this.validateRequired()) return
  277 + const payload = { ...this.form }
  278 + delete payload.status
  279 + console.log('onSubmit__payload', payload)
  280 +
  281 + try {
  282 + await updateApi(payload)
  283 + uni.showToast({ title: '保存成功', icon: 'success' })
  284 + setTimeout(() => { uni.redirectTo({ url: '/pages/confirmation_form/index' }) }, 300)
  285 + } catch (e) {
  286 + uni.showToast({ title: (e && e.msg) || '保存失败', icon: 'none' })
  287 + }
  288 + },
  289 + calculateSummary(list) {
  290 + const summary = (list || []).reduce((acc, it) => {
  291 + const qty = Number(it.supplementaryQuantity) || 0
  292 + const shipped = Number(it.shippedQuantity) || 0
  293 + const orderQty = Number(it.quantity) || 0
  294 + acc.totalSupplementaryQuantity += qty
  295 + acc.totalShippedQuantity += shipped
  296 + acc.totalQuantity += orderQty
  297 + return acc
  298 + }, { totalQuantity: 0, totalShippedQuantity: 0, totalSupplementaryQuantity: 0 })
  299 +
  300 + const fixedTotalQuantity = Number(summary.totalQuantity.toFixed(2))
  301 + const fixedTotalShippedQuantity = Number(summary.totalShippedQuantity.toFixed(2))
  302 + const fixedTotalSupplementaryQuantity = Number(summary.totalSupplementaryQuantity.toFixed(2))
  303 + this.form.totalQuantity = fixedTotalQuantity
  304 + this.form.totalShippedQuantity = fixedTotalShippedQuantity
  305 + this.form.totalSupplementaryQuantity = fixedTotalSupplementaryQuantity
  306 + },
  307 + purchaseOrderLineListChange(data) {
  308 + const list = Array.isArray(data) ? data : []
  309 + this.form.productSampleConfirmationSlipDetailList = list
  310 + },
  311 + openSheet(field) {
  312 + let options = []
  313 + let title = ''
  314 + let value = ''
  315 +
  316 + if (field === 'sampleType') {
  317 + title = '试样种类'
  318 + options = this.sampleTypeLocal
  319 + value = this.form.sampleType
  320 + } else if (field === 'customerType') {
  321 + title = '客户类型'
  322 + options = this.enterpriseTypeLocal
  323 + value = this.form.customerType
  324 + } else if (field === 'sampleQuantityRegulation') {
  325 + title = '本次试样数量是否超规定'
  326 + options = [{ value: true, label: '是' }, { value: false, label: '否' }]
  327 + value = this.form.sampleQuantityRegulation
  328 + } else if (field === 'specificationQuantityRegulation') {
  329 + title = '试样规格个数是否超规定'
  330 + options = [{ value: true, label: '是' }, { value: false, label: '否' }]
  331 + value = this.form.specificationQuantityRegulation
  332 + }
  333 +
  334 + const match = options.find(o => String(o.value) === String(value))
  335 + this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
  336 + },
  337 + onSheetConfirm({ value, label }) {
  338 + const field = this.sheet.field
  339 + this.form[field] = value
  340 + this.form[field + 'Name'] = label
  341 + this.sheet.visible = false
  342 + },
  343 + }
  344 +}
  345 +</script>
  346 +
  347 +<style lang="scss" scoped>
  348 +.page {
  349 + display: flex;
  350 + flex-direction: column;
  351 + height: 100%;
  352 +}
  353 +
  354 +.scroll {
  355 + flex: 1;
  356 + padding: 6rpx 0 400rpx;
  357 +}
  358 +
  359 +
  360 +
  361 +.title-header {
  362 + background-color: #fff;
  363 + display: flex;
  364 + align-items: center;
  365 + padding: 32rpx 32rpx 22rpx;
  366 +
  367 + .title-header_icon {
  368 + width: 32rpx;
  369 + height: 28rpx;
  370 + margin-right: 16rpx;
  371 + }
  372 +
  373 + span {
  374 + color: rgba(0, 0, 0, 0.9);
  375 + font-size: 32rpx;
  376 + line-height: 44rpx;
  377 + font-weight: 600;
  378 + }
  379 +}
  380 +
  381 +
  382 +.section {
  383 + background: #fff;
  384 + margin-bottom: 20rpx;
  385 +}
  386 +
  387 +.section2 {
  388 + background: #f1f1f1;
  389 +}
  390 +
  391 +::v-deep .uni-list {
  392 + background: transparent;
  393 +
  394 + &-item {
  395 + &__extra-text {
  396 + font-size: 32rpx;
  397 + }
  398 +
  399 + &__content-title {
  400 + font-size: 32rpx;
  401 + color: rgba(0, 0, 0, 0.9);
  402 + }
  403 +
  404 + &__container {
  405 + padding: 32rpx;
  406 + // align-items: center;
  407 +
  408 + .uni-easyinput {
  409 +
  410 + .is-disabled {
  411 + background-color: transparent !important;
  412 + }
  413 +
  414 + &__placeholder-class {
  415 + font-size: 32rpx;
  416 + color: rgba(0, 0, 0, 0.4);
  417 + }
  418 +
  419 + &__content {
  420 + border: none;
  421 +
  422 + &-input {
  423 + padding-left: 0 !important;
  424 + height: 48rpx;
  425 + line-height: 48rpx;
  426 + font-size: 32rpx;
  427 + }
  428 +
  429 + .content-clear-icon {
  430 + font-size: 44rpx !important;
  431 + }
  432 + }
  433 + }
  434 +
  435 + .amount-row {
  436 + flex: 1;
  437 + display: flex;
  438 + align-items: center;
  439 +
  440 + .uni-easyinput {
  441 + flex: 1;
  442 + }
  443 +
  444 + .unit {
  445 + margin-left: 16rpx;
  446 + color: rgba(0, 0, 0, 0.9);
  447 + }
  448 + }
  449 +
  450 + .item-title,
  451 + .uni-list-item__content {
  452 + flex: none;
  453 + min-height: 48rpx;
  454 + line-height: 48rpx;
  455 + font-size: 32rpx;
  456 + position: relative;
  457 + width: 210rpx;
  458 + margin-right: 32rpx;
  459 + color: rgba(0, 0, 0, 0.9);
  460 + padding-right: 0;
  461 +
  462 +
  463 + .required {
  464 + color: red;
  465 + position: absolute;
  466 + top: 50%;
  467 + transform: translateY(-50%);
  468 + left: -16rpx;
  469 + }
  470 + }
  471 +
  472 + }
  473 +
  474 + &.select-item {
  475 + &.is-empty {
  476 + .uni-list-item__extra-text {
  477 + color: rgba(0, 0, 0, 0.4) !important;
  478 + }
  479 + }
  480 +
  481 + &.is-filled {
  482 + .uni-list-item__extra-text {
  483 + color: rgba(0, 0, 0, 0.9) !important;
  484 + }
  485 + }
  486 +
  487 + .serial-number-row {
  488 + display: flex;
  489 + align-items: center;
  490 + }
  491 +
  492 + }
  493 +
  494 + &.mgb10 {
  495 + margin-bottom: 20rpx;
  496 + }
  497 +
  498 + }
  499 +
  500 + .title-header {
  501 + background-color: #fff;
  502 + display: flex;
  503 + align-items: center;
  504 + padding: 32rpx 32rpx 22rpx;
  505 +
  506 + &_icon {
  507 + width: 32rpx;
  508 + height: 28rpx;
  509 + margin-right: 16rpx;
  510 + }
  511 +
  512 + span {
  513 + color: rgba(0, 0, 0, 0.9);
  514 + font-size: 32rpx;
  515 + line-height: 44rpx;
  516 + font-weight: 600;
  517 + }
  518 + }
  519 +}
  520 +
  521 +/* 只读 easyinput 根据内容自适应高度 */
  522 +::v-deep .uni-list-item__container {
  523 + align-items: flex-start;
  524 +}
  525 +
  526 +/* 只读文本样式 */
  527 +.readonly-text {
  528 + color: rgba(0, 0, 0, 0.9);
  529 + font-size: 32rpx;
  530 + line-height: 48rpx;
  531 + text-align: right;
  532 + white-space: pre-wrap;
  533 + word-break: break-all;
  534 +}
  535 +
  536 +
  537 +.footer {
  538 + position: fixed;
  539 + left: 0;
  540 + right: 0;
  541 + bottom: 0;
  542 + padding: 0 32rpx 32rpx;
  543 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  544 + background: #fff;
  545 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  546 + z-index: 10;
  547 +
  548 + .btn {
  549 + height: 80rpx;
  550 + line-height: 80rpx;
  551 + border-radius: 12rpx;
  552 + font-size: 32rpx;
  553 + }
  554 +
  555 + .submit {
  556 + background: $theme-primary;
  557 + color: #fff;
  558 + }
  559 +
  560 + .view-total {
  561 + padding: 20rpx 0;
  562 +
  563 + .head {
  564 + font-size: 32rpx;
  565 + font-weight: 600;
  566 + line-height: 50rpx;
  567 + color: rgba(0, 0, 0, 0.9);
  568 + padding-bottom: 16rpx;
  569 + margin-bottom: 24rpx;
  570 + border-bottom: 1px dashed #E7E7E7;
  571 + }
  572 +
  573 + .row {
  574 + display: flex;
  575 + margin-bottom: 24rpx;
  576 + line-height: 32rpx;
  577 +
  578 + .row2 {
  579 + width: 50%;
  580 + }
  581 +
  582 + .label {
  583 + width: 180rpx;
  584 + margin-right: 14rpx;
  585 + color: rgba(0, 0, 0, 0.6);
  586 + font-size: 32rpx;
  587 + }
  588 +
  589 + .value {
  590 + flex: 1;
  591 + color: rgba(0, 0, 0, 0.9);
  592 + font-size: 32rpx;
  593 + white-space: pre-wrap;
  594 + word-break: break-all;
  595 + font-weight: 600;
  596 + }
  597 + }
  598 + }
  599 +}
  600 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="product">
  3 +
  4 + <!-- 新增&详情-产品 -->
  5 + <view class="header bp">
  6 + <image class="opCollapse" src="/static/images/title.png" />
  7 + <text class="title">{{ title || '产品' }}</text>
  8 + <view class="ops">
  9 + <!-- <image v-if="mode === 'add'" class="opAdd" @click="onAdd" src="/static/images/plus.png" /> -->
  10 + <view v-if="mode === 'view'" class="op1" @click="toggleViewCollapse">
  11 + <image class="opAdd" :src="collapsedView ? '/static/images/down.png' : '/static/images/up.png'" />
  12 + <text class="op">{{ collapsedView ? '展开' : '收起' }} </text>
  13 + </view>
  14 +
  15 + </view>
  16 + </view>
  17 +
  18 + <view v-if="mode === 'add'" class="section">
  19 + <view v-for="(item, idx) in items" :key="'a-' + idx" class="block">
  20 + <view class="row"><text class="label">牌号</text><text class="value">{{ item.brand }}</text></view>
  21 + <!-- 厚(公差) * 宽(公差) * 长(公差) -->
  22 + <view class="row row-spec"><text class="label">规格(mm)</text>
  23 + <view class="value value-spec">
  24 + <view v-if="item.thickness" class="value-spec_val">{{ item.thickness }}</view>
  25 + <view v-if="item.thickness" class="value-spec_box">
  26 + <view v-if="item.thicknessTolPos" class="value-spec_box_1">{{ item.thicknessTolPos > 0 ? '+' + item.thicknessTolPos : item.thicknessTolPos }}
  27 + </view>
  28 + <view v-if="item.thicknessTolNeg" class="value-spec_box_2">{{ item.thicknessTolNeg > 0 ? '+' + item.thicknessTolNeg : item.thicknessTolNeg }}
  29 + </view>
  30 + </view>
  31 + <view v-if="item.width" class="value-spec_val p12">*</view>
  32 + <view v-if="item.width" class="value-spec_val">{{ item.width }}</view>
  33 + <view v-if="item.width" class="value-spec_box">
  34 + <view v-if="item.widthTolPos" class="value-spec_box_1">{{ item.widthTolPos > 0 ? '+' +
  35 + item.widthTolPos : item.widthTolPos }}</view>
  36 + <view v-if="item.widthTolNeg" class="value-spec_box_2">{{ item.widthTolNeg > 0 ? '+' +
  37 + item.widthTolNeg : item.widthTolNeg }}</view>
  38 + </view>
  39 + <view v-if="item.length" class="value-spec_val p12">*</view>
  40 + <view v-if="item.length" class="value-spec_val">{{ item.length }}</view>
  41 + <view v-if="item.length" class="value-spec_box">
  42 + <view v-if="item.lengthTolPos" class="value-spec_box_1">{{ item.lengthTolPos > 0 ? '+' +
  43 + item.lengthTolPos : item.lengthTolPos }}</view>
  44 + <view v-if="item.lengthTolNeg" class="value-spec_box_2">{{ item.lengthTolNeg > 0 ? '+' +
  45 + item.lengthTolNeg : item.lengthTolNeg }}</view>
  46 + </view>
  47 + </view>
  48 + </view>
  49 + <view class="row"><text class="label">状态</text><text class="value">{{ item.status }}</text></view>
  50 + <view v-show="!item.collapsed">
  51 +
  52 + <view class="row" :class="{ 'noneStyle': !item.showSalesPrice }"><text
  53 + class="label">数量kg</text><text class="value">{{ item.quantity }}</text>
  54 + </view>
  55 +
  56 + <uni-list class="edit-list">
  57 + <uni-list-item class="select-item"
  58 + :class="item.provideSamplesName ? 'is-filled' : 'is-empty'" clickable
  59 + @click="openProductSheet(idx, 'provideSamples')"
  60 + :rightText="item.provideSamplesName || '请选择是否提供样品'" showArrow>
  61 + <template v-slot:body>
  62 + <view class="item-title"><text>是否提供样品</text></view>
  63 + </template>
  64 + </uni-list-item>
  65 + <uni-list-item class="select-item"
  66 + :class="item.clearParametersName ? 'is-filled' : 'is-empty'" clickable
  67 + @click="openProductSheet(idx, 'clearParameters')"
  68 + :rightText="item.clearParametersName || '请选择是否提供明确参数'" showArrow>
  69 + <template v-slot:body>
  70 + <view class="item-title"><text>是否提供明确参数</text></view>
  71 + </template>
  72 + </uni-list-item>
  73 + </uni-list>
  74 + </view>
  75 +
  76 + <view class="block-ops">
  77 + <div class="toggle" @click="toggleItem(idx)">
  78 + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'"
  79 + class="icon" />
  80 + {{ item.collapsed ? '展开' : '收起' }}
  81 + </div>
  82 + </view>
  83 + </view>
  84 + </view>
  85 +
  86 + <view v-else-if="mode === 'view'" class="view-list" v-show="!collapsedView">
  87 + <view v-for="(item, idx) in items" :key="'v-' + idx" class="card">
  88 + <view class="row"><text class="label">牌号</text><text class="value">{{ item.brand }}</text></view>
  89 + <!-- 厚(公差) * 宽(公差) * 长(公差) -->
  90 + <view class="row row-spec"><text class="label">规格(mm)</text>
  91 + <view class="value value-spec">
  92 + <view v-if="item.thickness" class="value-spec_val">{{ item.thickness }}</view>
  93 + <view v-if="item.thickness" class="value-spec_box">
  94 + <view v-if="item.thicknessTolPos" class="value-spec_box_1">{{ item.thicknessTolPos > 0 ? '+'
  95 + +
  96 + item.thicknessTolPos : item.thicknessTolPos }}
  97 + </view>
  98 + <view v-if="item.thicknessTolNeg" class="value-spec_box_2">{{ item.thicknessTolNeg > 0 ? '+'
  99 + +
  100 + item.thicknessTolNeg : item.thicknessTolNeg }}
  101 + </view>
  102 + </view>
  103 + <view v-if="item.width" class="value-spec_val p12">*</view>
  104 + <view v-if="item.width" class="value-spec_val">{{ item.width }}</view>
  105 + <view v-if="item.width" class="value-spec_box">
  106 + <view v-if="item.widthTolPos" class="value-spec_box_1">{{ item.widthTolPos > 0 ? '+' +
  107 + item.widthTolPos : item.widthTolPos }}
  108 + </view>
  109 + <view v-if="item.widthTolNeg" class="value-spec_box_2">{{ item.widthTolNeg > 0 ? '+' +
  110 + item.widthTolNeg : item.widthTolNeg }}
  111 + </view>
  112 + </view>
  113 + <view v-if="item.length" class="value-spec_val p12">*</view>
  114 + <view v-if="item.length" class="value-spec_val">{{ item.length }}</view>
  115 + <view v-if="item.length" class="value-spec_box">
  116 + <view v-if="item.lengthTolPos" class="value-spec_box_1">{{ item.lengthTolPos > 0 ? '+' +
  117 + item.lengthTolPos : item.lengthTolPos }}
  118 + </view>
  119 + <view v-if="item.lengthTolNeg" class="value-spec_box_2">{{ item.lengthTolNeg > 0 ? '+' +
  120 + item.lengthTolNeg : item.lengthTolNeg }}
  121 + </view>
  122 + </view>
  123 + </view>
  124 + </view>
  125 + <view class="row"><text class="label">状态</text><text class="value">{{ item.status }}</text></view>
  126 + <view class="row"><text class="label">数量(kg)</text><text class="value">{{ item.quantity }}</text>
  127 + </view>
  128 + <view class="row"><text class="label">是否提供样品</text><text class="value">{{ item.provideSamplesName
  129 + }}</text>
  130 + </view>
  131 + <view class="row"><text class="label">是否提供明确参数</text><text class="value">{{ item.clearParametersName
  132 + }}</text>
  133 + </view>
  134 + </view>
  135 + </view>
  136 +
  137 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value" @confirm="onProductConfirm" />
  138 + </view>
  139 +</template>
  140 +<script>
  141 +import { uuid } from '@/utils/uuid.js'
  142 +import SingleSelectSheet from '@/components/single-select/index.vue'
  143 +export default {
  144 + name: 'Product',
  145 + props: {
  146 + title: { type: String, default: '' },
  147 + mode: { type: String, default: 'add' },
  148 + list: { type: Array, default: () => [] },
  149 + max: { type: Number, default: 8 },
  150 + provideSamplesOptions: { type: Array, default: () => [] },
  151 + clearParametersOptions: { type: Array, default: () => [] },
  152 + },
  153 + components: { SingleSelectSheet },
  154 + data() {
  155 + return {
  156 + items: [],
  157 + collapsedView: false,
  158 + roleCodes: [],
  159 + sheet: { visible: false, title: '请选择产品', options: [], value: '', idx: -1, mode: '' }
  160 + }
  161 + },
  162 + computed: {
  163 + minDeliveryDate() {
  164 + const s = this.orderDate
  165 + if (!s) return ''
  166 + const parts = String(s).split('-')
  167 + const y = Number(parts[0])
  168 + const m = Number(parts[1])
  169 + const d = Number(parts[2])
  170 + if (!y || !m || !d) return ''
  171 + const dt = new Date(y, m - 1, d)
  172 + dt.setDate(dt.getDate() + 1)
  173 + const yy = dt.getFullYear()
  174 + const mm = String(dt.getMonth() + 1).padStart(2, '0')
  175 + const dd = String(dt.getDate()).padStart(2, '0')
  176 + return `${yy}/${mm}/${dd}`
  177 + },
  178 + // roleCodes() {
  179 + // const g = this.$store && this.$store.getters
  180 + // return (g && g.roleCodes) || [];
  181 + // },
  182 + },
  183 + watch: {
  184 + items: {
  185 + handler() { this.emitChange() },
  186 + deep: true
  187 + },
  188 + list: {
  189 + handler(v) {
  190 + const arr = Array.isArray(v) ? v : []
  191 + this.items = arr.map(x => {
  192 + const it = { ...this.defaultItem(), ...x, collapsed: true }
  193 + return it
  194 + })
  195 + },
  196 + deep: true
  197 + },
  198 + },
  199 + created() {
  200 + const init = Array.isArray(this.list) && this.list.length > 0 ? this.list.map(v => ({ ...this.defaultItem(), ...v, collapsed: false })) : [{ ...this.defaultItem(), collapsed: false }]
  201 + this.items = init;
  202 + },
  203 + methods: {
  204 + defaultItem() {
  205 + return {
  206 + purchaseOrderId: uuid(),
  207 + collapsed: false,
  208 + id: '',
  209 + // 厚度公差
  210 + thicknessTolPos: '',
  211 + thicknessTolNeg: '',
  212 + // 宽度公差
  213 + widthTolPos: '',
  214 + widthTolNeg: '',
  215 + // 长度公差
  216 + lengthTolPos: '',
  217 + lengthTolNeg: '',
  218 + // 其他字段
  219 + industry: '',
  220 + quality: '',
  221 + brand: '',
  222 + thickness: '',
  223 + width: '',
  224 + length: '',
  225 + status: '',
  226 + quantity: '',
  227 + shippedQuantity: '',
  228 + supplementaryQuantity: '',
  229 + confirmedDeliveryDate: '',
  230 + remarks: '',
  231 + }
  232 + },
  233 +
  234 + onAdd() {
  235 + const obj = this.defaultItem()
  236 + obj.collapsed = true
  237 + this.items.push(obj)
  238 + this.emitChange()
  239 + },
  240 + onRemove(id) {
  241 + if (!id) return
  242 + uni.showModal({
  243 + title: '系统提示',
  244 + content: '是否确定删除选中的产品?',
  245 + confirmText: '确定',
  246 + cancelText: '取消',
  247 + success: (res) => {
  248 + if (res && res.confirm) {
  249 + const i = this.items.findIndex(it => String(it.purchaseOrderId) === String(id))
  250 + if (i >= 0) {
  251 + this.items.splice(i, 1)
  252 + this.emitChange()
  253 + }
  254 + }
  255 + }
  256 + })
  257 + },
  258 + toggleItem(idx) {
  259 + const it = this.items[idx]
  260 + if (!it) return
  261 + it.collapsed = !it.collapsed
  262 + this.$set(this.items, idx, it)
  263 + },
  264 + emitChange() {
  265 + const out = this.items.map(it => ({ ...it }))
  266 + this.$emit('input', out)
  267 + this.$emit('update:value', out)
  268 + this.$emit('change', out)
  269 + },
  270 + onNonNegativeNumberInput(val, item, idx, field) {
  271 + let v = String(val != null ? val : (item && item[field]) || '')
  272 + v = v.replace(/[^0-9.]/g, '')
  273 + v = v.replace(/(\..*)\./g, '$1')
  274 + if (v.startsWith('.')) v = '0' + v
  275 + if (v === '') { item[field] = ''; if (typeof idx === 'number') this.$set(this.items, idx, { ...item }); return }
  276 + const num = Number(v)
  277 + if (isNaN(num) || num < 0) {
  278 + item[field] = '0'
  279 + } else {
  280 + item[field] = v
  281 + }
  282 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  283 + },
  284 + onNonNegativeNumberBlur(item, idx, field) {
  285 + const v = String((item && item[field]) || '')
  286 + const num = Number(v)
  287 + if (isNaN(num) || num < 0) item[field] = '0'
  288 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  289 + },
  290 + onRealNumberInput(val, item, idx, field) {
  291 + let s = String(val != null ? val : (item && item[field]) || '')
  292 + const neg = s.trim().startsWith('-')
  293 + s = s.replace(/[^0-9.\-]/g, '')
  294 + s = s.replace(/(?!^)-/g, '')
  295 + s = s.replace(/(\..*)\./g, '$1')
  296 + if (s.startsWith('.')) s = '0' + s
  297 + if (s.startsWith('-.')) s = '-0.' + s.slice(2)
  298 + if (neg && !s.startsWith('-')) s = '-' + s.replace(/-/g, '')
  299 + item[field] = s
  300 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  301 + },
  302 + onRealNumberBlur(item, idx, field) {
  303 + const s = String((item && item[field]) || '')
  304 + if (s === '') { if (typeof idx === 'number') this.$set(this.items, idx, { ...item }); return }
  305 + const n = Number(s)
  306 + if (isNaN(n)) item[field] = ''
  307 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  308 + },
  309 + // 限制输入为2位小数
  310 + onTwoDecimalInput(val, item, idx, field) {
  311 + let v = String(val != null ? val : (item && item[field]) || '')
  312 + v = v.replace(/[^0-9.]/g, '')
  313 + v = v.replace(/(\..*)\./g, '$1')
  314 +
  315 + // Restrict to 2 decimal places
  316 + const decimalIndex = v.indexOf('.')
  317 + if (decimalIndex !== -1 && v.length > decimalIndex + 3) {
  318 + v = v.substring(0, decimalIndex + 3)
  319 + }
  320 +
  321 + if (v.startsWith('.')) v = '0' + v
  322 +
  323 + // If the value was modified (truncated or cleaned)
  324 + if (String(val) !== v) {
  325 + // Hack: Temporarily set the dirty value to trigger Vue update mechanism
  326 + // This ensures that when we set the clean value back, Vue detects a change
  327 + item[field] = val
  328 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  329 +
  330 + // Then revert to the clean value asynchronously
  331 + setTimeout(() => {
  332 + item[field] = v
  333 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  334 + }, 0)
  335 + } else {
  336 + item[field] = v
  337 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  338 + }
  339 + },
  340 + onTwoDecimalBlur(item, idx, field) {
  341 + let v = String((item && item[field]) || '')
  342 + const num = Number(v)
  343 + if (isNaN(num) || num < 0) {
  344 + item[field] = '0'
  345 + } else {
  346 + if (v.endsWith('.')) {
  347 + item[field] = v.slice(0, -1)
  348 + }
  349 + }
  350 + if (typeof idx === 'number') this.$set(this.items, idx, { ...item })
  351 + },
  352 + toggleViewCollapse() {
  353 + this.collapsedView = !this.collapsedView
  354 + },
  355 + onDeliveryChange(e, item, idx) {
  356 + const getStr = (x) => {
  357 + if (x && x.detail && x.detail.value !== undefined) return x.detail.value
  358 + if (typeof x === 'string') return x
  359 + return item && item.deliveryDate ? item.deliveryDate : ''
  360 + }
  361 + const val = getStr(e)
  362 + if (!val || !this.orderDate) return
  363 + const parse = (s) => {
  364 + const p = String(s).replace(/\//g, '-').split('-')
  365 + const y = Number(p[0])
  366 + const m = Number(p[1])
  367 + const d = Number(p[2])
  368 + return new Date(y, m - 1, d)
  369 + }
  370 + const sel = parse(val)
  371 + const ord = parse(this.orderDate)
  372 + if (!(sel > ord)) {
  373 + item.deliveryDate = ''
  374 + this.$set(this.items, idx, { ...item })
  375 + uni.showToast({ title: '发货日期必须大于订货日期', icon: 'none' })
  376 + }
  377 + },
  378 + openProductSheet(idx, mode = 'provideSamples') {
  379 + let opts = []
  380 + let title = ''
  381 + let value = ''
  382 + const item = this.items[idx]
  383 +
  384 + if (mode === 'provideSamples') {
  385 + opts = this.provideSamplesOptions
  386 + value = item.provideSamples
  387 + title = '选择是否提供样品'
  388 + } else if (mode === 'clearParameters') {
  389 + opts = this.clearParametersOptions;
  390 + value = item.clearParameters
  391 + title = '是否提供明确参数'
  392 + }
  393 + console.log('____', { ...this.sheet, visible: true, title, options: opts, idx, value, mode })
  394 + this.sheet = { ...this.sheet, visible: true, title, options: opts, idx, value, mode }
  395 + },
  396 + onProductConfirm({ value, label }) {
  397 + const idx = this.sheet.idx
  398 + const _mode = this.sheet.mode;
  399 + const it = this.items[idx]
  400 + if (!it) { this.sheet.visible = false; return }
  401 + it[_mode] = value
  402 + it[_mode + 'Name'] = label || ''
  403 + this.$set(this.items, idx, it)
  404 + this.sheet.visible = false
  405 + this.emitChange()
  406 + },
  407 + }
  408 +}
  409 +</script>
  410 +<style lang="scss" scoped>
  411 +.header {
  412 + background-color: #fff;
  413 + display: flex;
  414 + align-items: center;
  415 + padding: 24rpx 32rpx;
  416 +
  417 + &.bp {
  418 + border-bottom: 1px solid #f0f0f0;
  419 + }
  420 +}
  421 +
  422 +.dot {
  423 + width: 16rpx;
  424 + height: 16rpx;
  425 + background: #3D48A3;
  426 + border-radius: 50%;
  427 + margin-right: 12rpx;
  428 +}
  429 +
  430 +.title {
  431 + font-size: 32rpx;
  432 + color: rgba(0, 0, 0, 0.9);
  433 + font-weight: 600;
  434 +}
  435 +
  436 +.ops {
  437 + margin-left: auto;
  438 +}
  439 +
  440 +.op {
  441 + color: $theme-primary;
  442 + font-size: 28rpx;
  443 + margin-left: 8rpx;
  444 +}
  445 +
  446 +.op1 {
  447 + display: flex;
  448 + align-items: center;
  449 +}
  450 +
  451 +.opAdd {
  452 + color: rgba(0, 0, 0, 0.6);
  453 + width: 40rpx;
  454 + height: 40rpx;
  455 +}
  456 +
  457 +.opCollapse {
  458 + color: rgba(0, 0, 0, 0.6);
  459 + width: 32rpx;
  460 + height: 28rpx;
  461 + margin-right: 16rpx;
  462 +}
  463 +
  464 +::v-deep .uni-list {
  465 + background: transparent;
  466 +
  467 + .uni-list--border-top {
  468 + background-color: transparent !important;
  469 + }
  470 +
  471 + &-item {
  472 + &__extra-text {
  473 + font-size: 32rpx;
  474 + }
  475 +
  476 + &__content-title {
  477 + font-size: 32rpx;
  478 + color: rgba(0, 0, 0, 0.9);
  479 + }
  480 +
  481 + &__container {
  482 + padding: 32rpx;
  483 +
  484 + .uni-easyinput {
  485 +
  486 + .is-disabled {
  487 + background-color: transparent !important;
  488 + }
  489 +
  490 + &__placeholder-class {
  491 + font-size: 32rpx;
  492 + color: rgba(0, 0, 0, 0.4);
  493 + }
  494 +
  495 + &__content {
  496 + border: none;
  497 +
  498 + &-input {
  499 + padding-left: 0 !important;
  500 + height: 48rpx;
  501 + line-height: 48rpx;
  502 + font-size: 32rpx;
  503 + }
  504 +
  505 + .content-clear-icon {
  506 + font-size: 44rpx !important;
  507 + }
  508 + }
  509 + }
  510 +
  511 + .amount-row {
  512 + flex: 1;
  513 + display: flex;
  514 + align-items: center;
  515 +
  516 + .uni-easyinput {
  517 + flex: 1;
  518 + }
  519 +
  520 + .unit {
  521 + margin-left: 16rpx;
  522 + color: rgba(0, 0, 0, 0.9);
  523 + }
  524 + }
  525 +
  526 + .item-title,
  527 + .uni-list-item__content {
  528 + flex: none;
  529 + min-height: 48rpx;
  530 + line-height: 48rpx;
  531 + font-size: 32rpx;
  532 + position: relative;
  533 + width: 210rpx;
  534 + margin-right: 32rpx;
  535 + color: rgba(0, 0, 0, 0.9);
  536 + padding-right: 0;
  537 +
  538 +
  539 + .required {
  540 + color: red;
  541 + position: absolute;
  542 + top: 50%;
  543 + transform: translateY(-50%);
  544 + left: -16rpx;
  545 + }
  546 + }
  547 +
  548 + }
  549 +
  550 + &.select-item {
  551 + &.is-empty {
  552 + .uni-list-item__extra-text {
  553 + color: rgba(0, 0, 0, 0.4) !important;
  554 + }
  555 + }
  556 +
  557 + &.is-filled {
  558 + .uni-list-item__extra-text {
  559 + color: rgba(0, 0, 0, 0.9) !important;
  560 + }
  561 + }
  562 +
  563 + .serial-number-row {
  564 + display: flex;
  565 + align-items: center;
  566 + }
  567 +
  568 + }
  569 +
  570 + &.mgb10 {
  571 + margin-bottom: 20rpx;
  572 + }
  573 +
  574 + }
  575 +
  576 + .title-header {
  577 + background-color: #fff;
  578 + display: flex;
  579 + align-items: center;
  580 + padding: 32rpx 32rpx 22rpx;
  581 +
  582 + &_icon {
  583 + width: 32rpx;
  584 + height: 28rpx;
  585 + margin-right: 16rpx;
  586 + }
  587 +
  588 + span {
  589 + color: rgba(0, 0, 0, 0.9);
  590 + font-size: 32rpx;
  591 + line-height: 44rpx;
  592 + font-weight: 600;
  593 + }
  594 + }
  595 +}
  596 +
  597 +/* 只读 easyinput 根据内容自适应高度 */
  598 +::v-deep .uni-list-item__container {
  599 + align-items: flex-start;
  600 +}
  601 +
  602 +.block-ops {
  603 + display: flex;
  604 + padding: 20rpx 32rpx 20rpx;
  605 + justify-content: space-around;
  606 +}
  607 +
  608 +.del {
  609 + color: #D54941;
  610 + font-size: 28rpx;
  611 + display: flex;
  612 + align-items: center;
  613 +
  614 + image {
  615 + width: 40rpx;
  616 + height: 40rpx;
  617 + }
  618 +}
  619 +
  620 +.toggle {
  621 + color: $theme-primary;
  622 + font-size: 28rpx;
  623 + display: flex;
  624 + align-items: center;
  625 +
  626 + image {
  627 + width: 40rpx;
  628 + height: 40rpx;
  629 + }
  630 +}
  631 +
  632 +.section {
  633 + background: #f1f1f1;
  634 + margin-bottom: 20rpx;
  635 +
  636 + .block {
  637 + background: #ffffff;
  638 + // padding: 32rpx 0;
  639 + margin-bottom: 20rpx;
  640 +
  641 + &:last-child {
  642 + margin-bottom: 0;
  643 + }
  644 + }
  645 +
  646 + .row {
  647 + display: flex;
  648 + // margin-bottom: 24rpx;
  649 + line-height: 32rpx;
  650 + padding: 32rpx;
  651 + border-bottom: 1rpx solid #f2f2f2 !important;
  652 +
  653 +
  654 + &.noneStyle {
  655 + border-bottom: 0;
  656 + border-bottom: none;
  657 + }
  658 +
  659 + &.row-spec {
  660 + align-items: center;
  661 + }
  662 + }
  663 +
  664 + .row:last-child {
  665 + margin-bottom: 0;
  666 + }
  667 +
  668 + .label {
  669 + width: 210rpx;
  670 + margin-right: 32rpx;
  671 + color: rgba(0, 0, 0, 0.9);
  672 + font-size: 32rpx;
  673 + line-height: 48rpx;
  674 + }
  675 +
  676 + .value {
  677 + flex: 1;
  678 + color: rgba(0, 0, 0, 0.9);
  679 + font-size: 32rpx;
  680 + white-space: pre-wrap;
  681 + word-break: break-all;
  682 + line-height: 48rpx;
  683 + }
  684 +
  685 + .value-spec {
  686 + height: 48rpx;
  687 + display: flex;
  688 + align-items: center;
  689 + color: #000000;
  690 +
  691 + &_box {
  692 + position: relative;
  693 + width: 60rpx;
  694 + height: 48rpx;
  695 +
  696 + &_1 {
  697 + font-size: 16rpx;
  698 + position: absolute;
  699 + top: -10rpx;
  700 + left: 0;
  701 + }
  702 +
  703 + &_2 {
  704 + font-size: 16rpx;
  705 + position: absolute;
  706 + bottom: -10rpx;
  707 + left: 0;
  708 + }
  709 + }
  710 +
  711 + &_val {
  712 + font-size: 28rpx;
  713 +
  714 + &.p12 {
  715 + padding-right: 12rpx;
  716 + }
  717 + }
  718 + }
  719 +
  720 + .view-total {
  721 + padding-top: 20rpx;
  722 +
  723 + .head {
  724 + font-size: 32rpx;
  725 + font-weight: 600;
  726 + line-height: 50rpx;
  727 + color: rgba(0, 0, 0, 0.9);
  728 + padding-bottom: 16rpx;
  729 + margin-bottom: 24rpx;
  730 + border-bottom: 1px dashed #E7E7E7;
  731 + }
  732 +
  733 + .row {
  734 + display: flex;
  735 + margin-bottom: 24rpx;
  736 + line-height: 32rpx;
  737 +
  738 + .label {
  739 + width: 180rpx;
  740 + margin-right: 14rpx;
  741 + color: rgba(0, 0, 0, 0.6);
  742 + font-size: 28rpx;
  743 + }
  744 +
  745 + .value {
  746 + flex: 1;
  747 + color: rgba(0, 0, 0, 0.9);
  748 + font-size: 28rpx;
  749 + white-space: pre-wrap;
  750 + word-break: break-all;
  751 + }
  752 + }
  753 + }
  754 +}
  755 +
  756 +
  757 +.view-list {
  758 + padding: 26rpx 32rpx;
  759 + background: #ffffff;
  760 +
  761 + .card {
  762 + background: #f3f3f3;
  763 + border-radius: 16rpx;
  764 + padding: 32rpx 44rpx;
  765 + margin-bottom: 20rpx;
  766 +
  767 + &:last-child {
  768 + margin-bottom: 0;
  769 + }
  770 + }
  771 +
  772 + .row {
  773 + display: flex;
  774 + margin-bottom: 24rpx;
  775 + line-height: 32rpx;
  776 +
  777 + &.row-spec {
  778 + height: 60rpx;
  779 + align-items: center;
  780 + }
  781 + }
  782 +
  783 + .row:last-child {
  784 + margin-bottom: 0;
  785 + }
  786 +
  787 + .label {
  788 + width: 200rpx;
  789 + margin-right: 14rpx;
  790 + color: rgba(0, 0, 0, 0.6);
  791 + font-size: 28rpx;
  792 + }
  793 +
  794 + .value {
  795 + flex: 1;
  796 + color: rgba(0, 0, 0, 0.9);
  797 + font-size: 28rpx;
  798 + white-space: pre-wrap;
  799 + word-break: break-all;
  800 + }
  801 +
  802 + .value-spec {
  803 + height: 60rpx;
  804 + display: flex;
  805 + align-items: center;
  806 + color: #000000;
  807 +
  808 + &_box {
  809 + position: relative;
  810 + width: 60rpx;
  811 + height: 60rpx;
  812 +
  813 + &_1 {
  814 + font-size: 16rpx;
  815 + position: absolute;
  816 + top: 0;
  817 + left: 0;
  818 + }
  819 +
  820 + &_2 {
  821 + font-size: 16rpx;
  822 + position: absolute;
  823 + bottom: 0;
  824 + left: 0;
  825 + }
  826 + }
  827 +
  828 + &_val {
  829 + font-size: 28rpx;
  830 +
  831 + &.p12 {
  832 + padding-right: 12rpx;
  833 + }
  834 + }
  835 + }
  836 +
  837 + .view-total {
  838 + padding-top: 20rpx;
  839 +
  840 + .head {
  841 + font-size: 32rpx;
  842 + font-weight: 600;
  843 + line-height: 50rpx;
  844 + color: rgba(0, 0, 0, 0.9);
  845 + padding-bottom: 16rpx;
  846 + margin-bottom: 24rpx;
  847 + border-bottom: 1px dashed #E7E7E7;
  848 + }
  849 +
  850 + .row {
  851 + display: flex;
  852 + margin-bottom: 24rpx;
  853 + line-height: 32rpx;
  854 +
  855 + .label {
  856 + width: 180rpx;
  857 + margin-right: 14rpx;
  858 + color: rgba(0, 0, 0, 0.6);
  859 + font-size: 28rpx;
  860 + }
  861 +
  862 + .value {
  863 + flex: 1;
  864 + color: rgba(0, 0, 0, 0.9);
  865 + font-size: 28rpx;
  866 + white-space: pre-wrap;
  867 + word-break: break-all;
  868 + }
  869 + }
  870 + }
  871 +}
  872 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <view class="section">
  6 + <text class="row company">{{ form.orderingUnitName }}</text>
  7 + <view :class="['status', `status_${form.status}`]" />
  8 + <view class="row"><text class="label">试样种类</text><text class="value">{{ form.sampleTypeName }}</text></view>
  9 + <view class="row"><text class="label">客户类型</text><text class="value">{{ form.customerTypeName }}</text></view>
  10 + <view class="row"><text class="label">分厂</text><text class="value">{{ form.workshopName }}</text></view>
  11 + <view class="row"><text class="label">所属品种</text><text class="value">{{ form.belongingBreed }}</text></view>
  12 + <view class="row"><text class="label">原供货同行</text><text class="value">{{ form.originalSupplierPeer }}</text>
  13 + </view>
  14 + </view>
  15 +
  16 + <!-- 产品 -->
  17 + <view class="section2">
  18 + <Product mode="view" :list="form.productSampleConfirmationSlipDetailList" />
  19 + </view>
  20 +
  21 + <view class="section">
  22 + <view class="row"><text class="label">数量</text><text class="value">{{ form.totalQuantity }}</text></view>
  23 + <view class="row"><text class="label">本次试样数量是否超规定</text><text class="value">{{ form.sampleQuantityRegulation ?
  24 + '是' : '否' }}</text></view>
  25 + <view class="row"><text class="label">试样规格个数是否超规定</text><text class="value">{{
  26 + form.specificationQuantityRegulation ? '是' : '否' }}</text></view>
  27 + <view class="row"><text class="label">试样次数</text><text class="value">{{ form.sampleFrequency }}</text></view>
  28 + <view class="row"><text class="label">前期不合格描述</text><text class="value">{{ form.earlyNonconformityDescription
  29 + }}</text></view>
  30 + </view>
  31 + </view>
  32 + </scroll-view>
  33 + </view>
  34 +</template>
  35 +
  36 +<script>
  37 +import { getDetailApi } from '@/api/confirmation_form.js'
  38 +import Product from './product.vue'
  39 +
  40 +export default {
  41 + name: 'ConfirmationFormViewer',
  42 + components: { Product },
  43 + props: { id: { type: [String, Number], default: '' } },
  44 + data() {
  45 + return {
  46 + form: {},
  47 + }
  48 + },
  49 + computed: {
  50 + },
  51 + watch: {
  52 + id: {
  53 + immediate: true,
  54 + handler(val) {
  55 + const v = (val !== undefined && val !== null) ? String(val) : ''
  56 + if (v) this.loadDetail(v)
  57 + }
  58 + }
  59 + },
  60 + onLoad(query) {
  61 + },
  62 + methods: {
  63 + async loadDetail(id) {
  64 + try {
  65 + const res = await getDetailApi(id)
  66 + this.form = res.data || {}
  67 + } catch (e) {
  68 + this.form = {}
  69 + }
  70 + },
  71 + }
  72 +}
  73 +</script>
  74 +
  75 +<style lang="scss" scoped>
  76 +.page {
  77 + display: flex;
  78 + flex-direction: column;
  79 + height: 100vh;
  80 +}
  81 +
  82 +.scroll {
  83 + flex: 1;
  84 + background: #f3f3f3;
  85 +}
  86 +
  87 +.detail-page {
  88 + padding-bottom: 20rpx;
  89 +}
  90 +
  91 +.section {
  92 + padding: 32rpx;
  93 + background: #fff;
  94 + margin-bottom: 20rpx;
  95 + position: relative;
  96 +
  97 +}
  98 +
  99 +.row {
  100 + display: flex;
  101 + margin-bottom: 28rpx;
  102 +
  103 + &:last-child {
  104 + margin-bottom: 0;
  105 + }
  106 +
  107 + &.company {
  108 + font-size: 36rpx;
  109 + font-weight: 600;
  110 + color: rgba(0, 0, 0, 0.9);
  111 + padding-top: 10rpx;
  112 + margin-bottom: 32rpx;
  113 + line-height: 50rpx;
  114 + }
  115 +
  116 + .label {
  117 + max-width: 400rpx;
  118 + margin-right: 20rpx;
  119 + line-height: 32rpx;
  120 + font-size: 28rpx;
  121 + color: rgba(0, 0, 0, 0.6);
  122 + }
  123 +
  124 + .value {
  125 + flex: 1;
  126 + line-height: 32rpx;
  127 + font-size: 28rpx;
  128 + color: rgba(0, 0, 0, 0.9);
  129 + text-align: right;
  130 + word-break: break-all;
  131 + }
  132 +}
  133 +
  134 +.title-header {
  135 + background-color: #fff;
  136 + display: flex;
  137 + align-items: center;
  138 + padding: 32rpx 32rpx 22rpx;
  139 + border-bottom: 1rpx dashed #f0f0f0;
  140 +
  141 + &_icon {
  142 + width: 32rpx;
  143 + height: 28rpx;
  144 + margin-right: 16rpx;
  145 + }
  146 +
  147 + span {
  148 + color: rgba(0, 0, 0, 0.9);
  149 + font-size: 32rpx;
  150 + line-height: 44rpx;
  151 + font-weight: 600;
  152 + }
  153 +}
  154 +</style>
... ...
... ... @@ -205,7 +205,7 @@ export default {
205 205 },{
206 206 text: '产品试样结果跟踪单',
207 207 icon: '/static/images/index/draft_order.png',
208   - link: '/pages/draft_order/index',
  208 + link: '/pages/follow_up_form/index',
209 209 name: 'FollowupForm'
210 210 }]
211 211 },
... ...