Commit 1e1cd31de0307500e98281b2d6c37befcef9aa10

Authored by gesilong
1 parent 7c82fe02

commit:联调过磅

1 1 import request from '@/utils/request'
2 2 import { ContentTypeEnum } from '@/utils/httpEnum'
3 3
4   -// 过磅码单分页查询
  4 +// 过磅码单(对齐 PC 端 api/warehouse-manage/weight-ticket)
  5 +const baseUrl = '/procurement/weighing/ticket'
  6 +
  7 +/**
  8 + * 分页查询过磅单列表
  9 + * params: pageIndex, pageSize, code, planCode, supplierName, companyShortId,
  10 + * approvalStatus, createBy, startTime, endTime, deleted(boolean)
  11 + */
5 12 export function weighTicketQueryApi(params) {
6 13 return request({
7   - url: '/procurement/weighTicket/query',
  14 + url: baseUrl + '/query',
8 15 method: 'get',
9 16 params
10 17 })
11 18 }
12 19
13   -// 过磅码单详情
  20 +/**
  21 + * 生成过磅单编号(直接返回编号字符串)
  22 + */
  23 +export function weighTicketGenerateCodeApi() {
  24 + return request({
  25 + url: baseUrl + '/generateCode',
  26 + method: 'get'
  27 + })
  28 +}
  29 +
  30 +/**
  31 + * 查询过磅单可选计划列表
  32 + * params: pageIndex, pageSize, code(计划编号), companyName(收料单位)
  33 + */
  34 +export function weighingTicketPlansApi(params) {
  35 + return request({
  36 + url: baseUrl + '/weighingTicketPlans',
  37 + method: 'get',
  38 + params
  39 + })
  40 +}
  41 +
  42 +/**
  43 + * 根据计划id集合查询计划明细行
  44 + * ids: 计划id数组
  45 + */
  46 +export function weighTicketPlanLinesApi(ids) {
  47 + return request({
  48 + url: baseUrl + '/planLines',
  49 + method: 'get',
  50 + params: { ids: (ids || []).join(',') }
  51 + })
  52 +}
  53 +
  54 +/**
  55 + * 查询过磅单详情
  56 + */
14 57 export function weighTicketDetailApi(params) {
15 58 return request({
16   - url: '/procurement/weighTicket',
  59 + url: baseUrl,
17 60 method: 'get',
18 61 params
19 62 })
20 63 }
21 64
22   -// 新增
23   -export function weighTicketSaveApi(data) {
  65 +/**
  66 + * 提交审核
  67 + */
  68 +export function weighTicketSubmitAuditApi(id) {
  69 + return request({
  70 + url: baseUrl + '/submit?id=' + id,
  71 + method: 'put'
  72 + })
  73 +}
  74 +
  75 +/**
  76 + * 作废过磅单
  77 + */
  78 +export function weighTicketCancelApi(id) {
  79 + return request({
  80 + url: baseUrl + '/delete?id=' + id,
  81 + method: 'put'
  82 + })
  83 +}
  84 +
  85 +/**
  86 + * 新增过磅单
  87 + */
  88 +export function weighTicketCreateApi(data) {
24 89 return request({
25   - url: '/procurement/weighTicket',
  90 + url: baseUrl + '/create',
26 91 method: 'post',
27 92 data,
28 93 contentType: ContentTypeEnum.JSON
29 94 })
30 95 }
31 96
32   -// 编辑
  97 +/**
  98 + * 修改过磅单
  99 + */
33 100 export function weighTicketUpdateApi(data) {
34 101 return request({
35   - url: '/procurement/weighTicket',
  102 + url: baseUrl,
36 103 method: 'put',
37 104 data,
38 105 contentType: ContentTypeEnum.JSON
39 106 })
40 107 }
41 108
42   -// 作废 / 恢复
43   -export function weighTicketDeleteApi(params) {
  109 +/**
  110 + * 上传签字凭证
  111 + * data: { id, fileIds(逗号分隔), fileNames(逗号分隔) }
  112 + */
  113 +export function weighTicketUploadSignApi(data) {
44 114 return request({
45   - url: '/procurement/weighTicket/delete',
  115 + url: baseUrl + '/attachment/sign',
46 116 method: 'put',
  117 + data,
  118 + contentType: ContentTypeEnum.JSON
  119 + })
  120 +}
  121 +
  122 +/**
  123 + * 上传延期说明附件
  124 + * data: { id, fileIds(逗号分隔), fileNames(逗号分隔) }
  125 + */
  126 +export function weighTicketUploadDelayApi(data) {
  127 + return request({
  128 + url: baseUrl + '/attachment/delay',
  129 + method: 'put',
  130 + data,
  131 + contentType: ContentTypeEnum.JSON
  132 + })
  133 +}
  134 +
  135 +/**
  136 + * 公司简称下拉数据(PC: base-data/short-name)
  137 + */
  138 +export function companyShortQueryApi(params) {
  139 + return request({
  140 + url: '/basedata/company/short/query',
  141 + method: 'get',
47 142 params
48 143 })
49 144 }
... ...
... ... @@ -573,6 +573,22 @@
573 573 }
574 574 },
575 575 {
  576 + "path": "pages/weight-ticket/upload",
  577 + "style": {
  578 + "navigationBarTitleText": "上传附件",
  579 + "navigationBarBackgroundColor": "#ffffff",
  580 + "navigationBarTextStyle": "black"
  581 + }
  582 + },
  583 + {
  584 + "path": "pages/weight-ticket/reweigh",
  585 + "style": {
  586 + "navigationBarTitleText": "复磅",
  587 + "navigationBarBackgroundColor": "#ffffff",
  588 + "navigationBarTextStyle": "black"
  589 + }
  590 + },
  591 + {
576 592 "path": "pages/order_list/index",
577 593 "style": {
578 594 "navigationBarTitleText": "订货单列表",
... ...
... ... @@ -5,208 +5,132 @@
5 5 <!-- 编号 -->
6 6 <uni-list-item class="select-item" :class="form.code ? 'is-filled' : 'is-empty'">
7 7 <template v-slot:body>
8   - <view class="item-title"><text>编号</text></view>
  8 + <view class="item-title"><text class="required">*</text><text>编号</text></view>
9 9 </template>
10 10 <template v-slot:footer>
11 11 <view class="serial-number-row">
12 12 <text class="readonly-text value-code">{{ form.code || '-' }}</text>
13   - <button class="generate-btn" @click="initCode">点此生成</button>
  13 + <button class="generate-btn" @click="onGenerateCode">点此生成</button>
14 14 </view>
15 15 </template>
16 16 </uni-list-item>
17 17
18 18 <!-- 类型 -->
19   - <uni-list-item>
  19 + <uni-list-item class="select-item" :class="form.type ? 'is-filled' : 'is-empty'">
20 20 <template v-slot:body>
21 21 <view class="item-title"><text class="required">*</text><text>类型</text></view>
22 22 </template>
23 23 <template v-slot:footer>
24   - <uni-easyinput v-model="form.type" :inputBorder="false" placeholder="请输入类型" />
  24 + <picker mode="selector" :range="typeLabels" @change="onTypeChange">
  25 + <view class="picker-value" :class="{ placeholder: !form.type }">
  26 + {{ getTypeLabel() || '请选择类型' }}
  27 + </view>
  28 + </picker>
25 29 </template>
26 30 </uni-list-item>
27 31
28   - <!-- 计划编号 -->
29   - <uni-list-item>
  32 + <!-- 计划编号(弹窗多选计划) -->
  33 + <uni-list-item class="select-item" :class="form.planCode ? 'is-filled' : 'is-empty'" clickable
  34 + @click="openPlanSheet" :rightText="form.planCode || '请选择计划'" showArrow>
30 35 <template v-slot:body>
31 36 <view class="item-title"><text class="required">*</text><text>计划编号</text></view>
32 37 </template>
33   - <template v-slot:footer>
34   - <uni-easyinput v-model="form.unit" :inputBorder="false" placeholder="请输入计划编号" />
35   - </template>
36   - </uni-list-item>
37   -
38   - <!-- 时间 -->
39   - <uni-list-item class="select-item" :class="form.firstGrossWeightTime ? 'is-filled' : 'is-empty'">
40   - <template v-slot:body>
41   - <view class="item-title"><text class="required">*</text><text>时间</text></view>
42   - </template>
43   - <template v-slot:footer>
44   - <uni-datetime-picker type="date" v-model="form.firstGrossWeightTime" />
45   - </template>
46   - </uni-list-item>
47   -
48   - <!-- 供料单位 -->
49   - <uni-list-item>
50   - <template v-slot:body>
51   - <view class="item-title"><text class="required">*</text><text>供料单位</text></view>
52   - </template>
53   - <template v-slot:footer>
54   - <uni-easyinput v-model="form.supplierName" :inputBorder="false" placeholder="请输入供料单位" />
55   - </template>
56 38 </uni-list-item>
57 39
58   - <!-- 收料单位 -->
59   - <uni-list-item>
60   - <template v-slot:body>
61   - <view class="item-title"><text class="required">*</text><text>收料单位</text></view>
62   - </template>
  40 + <!-- 单位(禁用) -->
  41 + <uni-list-item title="单位">
63 42 <template v-slot:footer>
64   - <uni-easyinput v-model="form.companyShortName" :inputBorder="false" placeholder="请输入收料单位" />
  43 + <uni-easyinput v-model="form.unit" :inputBorder="false" disabled />
65 44 </template>
66 45 </uni-list-item>
67 46
68   - <!-- 物料名称 -->
69   - <uni-list-item>
70   - <template v-slot:body>
71   - <view class="item-title"><text class="required">*</text><text>物料名称</text></view>
72   - </template>
  47 + <!-- 供料单位(带出,禁用) -->
  48 + <uni-list-item title="供料单位">
73 49 <template v-slot:footer>
74   - <uni-easyinput v-model="form.materialName" :inputBorder="false" placeholder="请输入物料名称" />
  50 + <uni-easyinput v-model="form.supplierName" :inputBorder="false" disabled placeholder="选择计划后自动带出" />
75 51 </template>
76 52 </uni-list-item>
77 53
78   - <!-- 过磅总重 -->
79   - <uni-list-item>
80   - <template v-slot:body>
81   - <view class="item-title"><text class="required">*</text><text>过磅总重</text></view>
82   - </template>
  54 + <!-- 收料单位(带出,禁用) -->
  55 + <uni-list-item title="收料单位">
83 56 <template v-slot:footer>
84   - <uni-easyinput v-model="form.totalGrossWeight" type="digit" :inputBorder="false" placeholder="请输入过磅总重" @input="calcNetWeight" />
85   - </template>
86   - </uni-list-item>
87   -
88   - <!-- 实际伙重 -->
89   - <uni-list-item title="实际伙重">
90   - <template v-slot:footer>
91   - <uni-easyinput v-model="form.actualImpurityWeight" type="digit" :inputBorder="false" placeholder="请输入实际伙重" @input="calcNetWeight" />
92   - </template>
93   - </uni-list-item>
94   -
95   - <!-- 货物净重 -->
96   - <uni-list-item title="货物净重">
97   - <template v-slot:footer>
98   - <uni-easyinput v-model="form.netWeight" type="digit" :inputBorder="false" placeholder="请输入货物净重" />
99   - </template>
100   - </uni-list-item>
101   -
102   - <!-- 净重大写 -->
103   - <uni-list-item title="净重大写">
104   - <template v-slot:footer>
105   - <uni-easyinput v-model="form.netWeightUpper" :inputBorder="false" placeholder="请输入净重大写" />
106   - </template>
107   - </uni-list-item>
108   -
109   - <!-- 来料扣杂类型 -->
110   - <uni-list-item title="来料扣杂类型">
111   - <template v-slot:footer>
112   - <uni-easyinput v-model="form.deductionTypes" :inputBorder="false" placeholder="请输入来料扣杂类型" />
113   - </template>
114   - </uni-list-item>
115   -
116   - <!-- 供应存欠料 -->
117   - <uni-list-item title="供应存欠料">
118   - <template v-slot:footer>
119   - <uni-easyinput v-model="form.supplierStockMaterial" type="digit" :inputBorder="false" placeholder="请输入供应存欠料" />
120   - </template>
121   - </uni-list-item>
122   -
123   - <!-- 物流存欠料 -->
124   - <uni-list-item title="物流存欠料">
125   - <template v-slot:footer>
126   - <uni-easyinput v-model="form.logisticsStockMaterial" type="digit" :inputBorder="false" placeholder="请输入物流存欠料" />
127   - </template>
128   - </uni-list-item>
129   -
130   - <!-- 车牌号 -->
131   - <uni-list-item>
132   - <template v-slot:body>
133   - <view class="item-title"><text class="required">*</text><text>车牌号</text></view>
134   - </template>
135   - <template v-slot:footer>
136   - <uni-easyinput v-model="form.plateNumber" :inputBorder="false" placeholder="请输入车牌号" />
  57 + <uni-easyinput v-model="form.companyShortName" :inputBorder="false" disabled placeholder="选择计划后自动带出" />
137 58 </template>
138 59 </uni-list-item>
139 60 </uni-list>
140 61
141   - <!-- 计划单明细 -->
  62 + <!-- 计划单明细(只读) -->
142 63 <view class="sub-table">
143 64 <view class="header">
144 65 <image class="opCollapse" src="/static/images/title.png" />
145 66 <text class="title">计划单明细</text>
146   - <view class="ops">
147   - <image class="opAdd" @click="addPlan" src="/static/images/plus.png" />
148   - </view>
149 67 </view>
150 68 <view class="add-list">
151   - <view v-if="!planList.length" class="empty-tip">暂无明细,请添加</view>
152   - <view v-for="(item, idx) in planList" :key="'p-' + idx" class="block">
  69 + <view v-if="!planList.length" class="empty-tip">选择计划后自动带出</view>
  70 + <view v-for="(item, idx) in planList" :key="'p-' + (item.id || idx)" class="block">
153 71 <uni-list>
154 72 <uni-list-item title="计划品种">
155 73 <template v-slot:footer>
156   - <uni-easyinput v-model="item.productName" :inputBorder="false" placeholder="请输入计划品种" />
  74 + <uni-easyinput :value="item.productName" :inputBorder="false" disabled />
157 75 </template>
158 76 </uni-list-item>
159 77 <uni-list-item title="计划数量">
160 78 <template v-slot:footer>
161   - <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入计划数量" />
  79 + <uni-easyinput :value="item.quantity" :inputBorder="false" disabled />
162 80 </template>
163 81 </uni-list-item>
164 82 <uni-list-item title="计划编号">
165 83 <template v-slot:footer>
166   - <uni-easyinput v-model="item.planCode" :inputBorder="false" placeholder="请输入计划编号" />
  84 + <uni-easyinput :value="item.planCode" :inputBorder="false" disabled />
167 85 </template>
168 86 </uni-list-item>
169 87 </uni-list>
170   - <view class="block-ops">
171   - <div class="del" @click="removeRow('plan', idx)">
172   - <image src="/static/images/delete.png" class="icon" />删除
173   - </div>
174   - </view>
175 88 </view>
176 89 </view>
177 90 </view>
178 91
179   - <!-- 过磅明细 -->
  92 + <!-- 过磅明细(可编辑) -->
180 93 <view class="sub-table">
181 94 <view class="header">
182 95 <image class="opCollapse" src="/static/images/title.png" />
183 96 <text class="title">过磅明细</text>
184 97 <view class="ops">
185   - <image class="opAdd" @click="addWeigh" src="/static/images/plus.png" />
  98 + <image class="opAdd" @click="addWeightRow" src="/static/images/plus.png" />
186 99 </view>
187 100 </view>
188 101 <view class="add-list">
189 102 <view v-if="!weighList.length" class="empty-tip">暂无明细,请添加</view>
190   - <view v-for="(item, idx) in weighList" :key="'w-' + idx" class="block">
  103 + <view v-for="(item, idx) in weighList" :key="'w-' + (item.id || idx)" class="block">
191 104 <uni-list>
192   - <uni-list-item title="过磅品种">
  105 + <uni-list-item class="select-item" :class="item.productId ? 'is-filled' : 'is-empty'">
  106 + <template v-slot:body>
  107 + <view class="item-title"><text class="required">*</text><text>过磅品种</text></view>
  108 + </template>
193 109 <template v-slot:footer>
194   - <uni-easyinput v-model="item.productName" :inputBorder="false" placeholder="请输入过磅品种" />
  110 + <picker mode="selector" :range="weightProductLabels"
  111 + @change="(e) => handleWeightProductChange(idx, Number(e.detail.value))">
  112 + <view class="picker-value" :class="{ placeholder: !item.productId }">
  113 + {{ getWeightProductLabel(item) || '请选择过磅品种' }}
  114 + </view>
  115 + </picker>
195 116 </template>
196 117 </uni-list-item>
197 118 <uni-list-item title="开票品名">
198 119 <template v-slot:footer>
199   - <uni-easyinput v-model="item.invoiceProductName" :inputBorder="false" placeholder="请输入开票品名" />
  120 + <uni-easyinput v-model="item.invoiceProductName" :inputBorder="false" disabled />
200 121 </template>
201 122 </uni-list-item>
202 123 <uni-list-item title="过磅数量">
  124 + <template v-slot:body>
  125 + <view class="item-title"><text class="required">*</text><text>过磅数量</text></view>
  126 + </template>
203 127 <template v-slot:footer>
204 128 <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入过磅数量" />
205 129 </template>
206 130 </uni-list-item>
207 131 <uni-list-item title="计划编号">
208 132 <template v-slot:footer>
209   - <uni-easyinput v-model="item.planCode" :inputBorder="false" placeholder="请输入计划编号" />
  133 + <uni-easyinput v-model="item.planCode" :inputBorder="false" disabled />
210 134 </template>
211 135 </uni-list-item>
212 136 </uni-list>
... ... @@ -219,25 +143,36 @@
219 143 </view>
220 144 </view>
221 145
222   - <!-- 申请数量明细 -->
223   - <view class="sub-table">
  146 + <!-- 申请数量明细(仅买断展示) -->
  147 + <view v-if="isBuyout" class="sub-table">
224 148 <view class="header">
225 149 <image class="opCollapse" src="/static/images/title.png" />
226 150 <text class="title">申请数量明细</text>
227 151 <view class="ops">
228   - <image class="opAdd" @click="addApply" src="/static/images/plus.png" />
  152 + <image class="opAdd" @click="addApplyRow" src="/static/images/plus.png" />
229 153 </view>
230 154 </view>
231 155 <view class="add-list">
232 156 <view v-if="!applyList.length" class="empty-tip">暂无明细,请添加</view>
233   - <view v-for="(item, idx) in applyList" :key="'a-' + idx" class="block">
  157 + <view v-for="(item, idx) in applyList" :key="'a-' + (item.id || idx)" class="block">
234 158 <uni-list>
235   - <uni-list-item title="类别">
  159 + <uni-list-item class="select-item" :class="item.breedRelationshipId ? 'is-filled' : 'is-empty'">
  160 + <template v-slot:body>
  161 + <view class="item-title"><text class="required">*</text><text>类别</text></view>
  162 + </template>
236 163 <template v-slot:footer>
237   - <uni-easyinput v-model="item.category" :inputBorder="false" placeholder="请输入类别" />
  164 + <picker mode="selector" :range="varietyLabels"
  165 + @change="(e) => handleVarietyChange(idx, Number(e.detail.value))">
  166 + <view class="picker-value" :class="{ placeholder: !item.breedRelationshipId }">
  167 + {{ getVarietyLabel(item) || '请选择类别' }}
  168 + </view>
  169 + </picker>
238 170 </template>
239 171 </uni-list-item>
240 172 <uni-list-item title="数量">
  173 + <template v-slot:body>
  174 + <view class="item-title"><text class="required">*</text><text>数量</text></view>
  175 + </template>
241 176 <template v-slot:footer>
242 177 <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" />
243 178 </template>
... ... @@ -252,6 +187,88 @@
252 187 </view>
253 188 </view>
254 189
  190 + <!-- 其他信息 -->
  191 + <view class="remark-section">
  192 + <view class="header">
  193 + <image class="opCollapse" src="/static/images/title.png" />
  194 + <text class="title">其他信息</text>
  195 + </view>
  196 + <view class="section-content">
  197 + <uni-list>
  198 + <uni-list-item title="物料名称">
  199 + <template v-slot:footer>
  200 + <uni-easyinput :value="materialName" :inputBorder="false" disabled />
  201 + </template>
  202 + </uni-list-item>
  203 + <uni-list-item>
  204 + <template v-slot:body>
  205 + <view class="item-title"><text class="required">*</text><text>过磅总重</text></view>
  206 + </template>
  207 + <template v-slot:footer>
  208 + <uni-easyinput v-model="form.totalGrossWeight" type="digit" :inputBorder="false" placeholder="请输入过磅总重" />
  209 + </template>
  210 + </uni-list-item>
  211 + <uni-list-item>
  212 + <template v-slot:body>
  213 + <view class="item-title"><text class="required">*</text><text>实际伙重</text></view>
  214 + </template>
  215 + <template v-slot:footer>
  216 + <uni-easyinput v-model="form.actualImpurityWeight" type="digit" :inputBorder="false" placeholder="请输入实际伙重" />
  217 + </template>
  218 + </uni-list-item>
  219 + <uni-list-item title="货物净重">
  220 + <template v-slot:footer>
  221 + <uni-easyinput :value="computedNetWeight" :inputBorder="false" disabled />
  222 + </template>
  223 + </uni-list-item>
  224 + <uni-list-item title="净重大写">
  225 + <template v-slot:footer>
  226 + <uni-easyinput :value="computedNetWeightUpper" :inputBorder="false" disabled />
  227 + </template>
  228 + </uni-list-item>
  229 + </uni-list>
  230 +
  231 + <view class="form-group">
  232 + <view class="item-title"><text class="required">*</text><text>来料扣杂类型</text></view>
  233 + <uni-data-checkbox
  234 + mode="tag"
  235 + :multiple="true"
  236 + :value-field="'value'"
  237 + :text-field="'text'"
  238 + v-model="form.deductionTypes"
  239 + :localdata="deductionTypeOptions"
  240 + />
  241 + </view>
  242 +
  243 + <uni-list>
  244 + <uni-list-item>
  245 + <template v-slot:body>
  246 + <view class="item-title"><text class="required">*</text><text>供应存欠料</text></view>
  247 + </template>
  248 + <template v-slot:footer>
  249 + <uni-easyinput v-model="form.supplierStockMaterial" :inputBorder="false" placeholder="请输入供应存欠料" />
  250 + </template>
  251 + </uni-list-item>
  252 + <uni-list-item>
  253 + <template v-slot:body>
  254 + <view class="item-title"><text class="required">*</text><text>物流存欠料</text></view>
  255 + </template>
  256 + <template v-slot:footer>
  257 + <uni-easyinput v-model="form.logisticsStockMaterial" :inputBorder="false" placeholder="请输入物流存欠料" />
  258 + </template>
  259 + </uni-list-item>
  260 + <uni-list-item>
  261 + <template v-slot:body>
  262 + <view class="item-title"><text class="required">*</text><text>车牌号</text></view>
  263 + </template>
  264 + <template v-slot:footer>
  265 + <uni-easyinput v-model="form.plateNumber" :inputBorder="false" placeholder="请输入车牌号" />
  266 + </template>
  267 + </uni-list-item>
  268 + </uni-list>
  269 + </view>
  270 + </view>
  271 +
255 272 <!-- 备注模块 -->
256 273 <view class="remark-section">
257 274 <view class="header">
... ... @@ -260,8 +277,8 @@
260 277 </view>
261 278 <view class="section-content">
262 279 <view class="remark-group">
263   - <view class="remark-label"><text class="required">*</text><text>业务摘要</text></view>
264   - <uni-easyinput v-model="form.businessSummary" type="textarea" :inputBorder="false" placeholder="请输入业务摘要" maxlength="2000" />
  280 + <view class="remark-label">业务摘要</view>
  281 + <uni-easyinput :value="computedBusinessSummary" type="textarea" :inputBorder="false" disabled autoHeight />
265 282 </view>
266 283 <view class="remark-group">
267 284 <view class="remark-label">特别说明</view>
... ... @@ -269,134 +286,450 @@
269 286 </view>
270 287 <view class="remark-group">
271 288 <view class="remark-label">单据备注</view>
272   - <uni-easyinput v-model="form.remark" type="textarea" :inputBorder="false" placeholder="请输入单据备注" maxlength="2000" />
  289 + <uni-easyinput :value="computedDocumentRemark" type="textarea" :inputBorder="false" disabled autoHeight />
273 290 </view>
274 291 </view>
275 292 </view>
276 293
277 294 <view class="footer">
278   - <button class="btn submit" type="primary" @click="onSubmit">提交</button>
  295 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
279 296 </view>
280 297 </scroll-view>
  298 +
  299 + <RelateSelectSheet :visible.sync="planSheet.visible" title="选择计划" :multiple="true"
  300 + :fetchFn="planSheet.fetchFn" :display-fields="planSheet.displayFields"
  301 + row-key="id" :selectedKeys.sync="planSheet.selectedKeys" @confirm="onPlanConfirm" />
281 302 </view>
282 303 </template>
283 304
284 305 <script>
285   -import { weighTicketSaveApi } from '@/api/procure-manage/weighTicket.js'
  306 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  307 +import {
  308 + weighTicketGenerateCodeApi,
  309 + weighingTicketPlansApi,
  310 + weighTicketPlanLinesApi,
  311 + weighTicketCreateApi
  312 +} from '@/api/procure-manage/weighTicket.js'
  313 +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js'
  314 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
286 315
287 316 export default {
288 317 name: 'AddWeightTicket',
  318 + components: { RelateSelectSheet },
289 319 data() {
290 320 return {
291 321 form: {
292 322 code: '',
293 323 type: '',
294   - unit: '',
295   - firstGrossWeightTime: '',
  324 + planCode: '',
  325 + unit: '公斤',
296 326 supplierName: '',
297 327 companyShortName: '',
298   - materialName: '',
299 328 totalGrossWeight: '',
300 329 actualImpurityWeight: '',
301   - netWeight: '',
302   - netWeightUpper: '',
303   - deductionTypes: '',
  330 + deductionTypes: [],
304 331 supplierStockMaterial: '',
305 332 logisticsStockMaterial: '',
306 333 plateNumber: '',
307   - businessSummary: '',
308   - specialNote: '',
309   - remark: ''
  334 + specialNote: ''
310 335 },
  336 + dicOptions: { TYPE: [], DEDUCTION_TYPE: [] },
  337 + // 选中计划行(多选)
  338 + selectedPlanRows: [],
  339 + // 计划单明细(只读)
311 340 planList: [],
  341 + // 过磅明细(可编辑)
312 342 weighList: [],
313   - applyList: []
  343 + // 申请数量明细(买断时)
  344 + applyList: [],
  345 + varietyOptions: [],
  346 + planSheet: {
  347 + visible: false,
  348 + fetchFn: null,
  349 + displayFields: [
  350 + { label: '计划编号', field: 'code' },
  351 + { label: '是否买断', field: 'buyout', format: v => (v ? '是' : '否') },
  352 + { label: '收料单位', field: 'companyName' },
  353 + { label: '供料单位', field: 'supplierName' },
  354 + { label: '总数量', field: 'totalQuantity' },
  355 + { label: '创建人', field: 'createBy' }
  356 + ],
  357 + selectedKeys: []
  358 + }
  359 + }
  360 + },
  361 + computed: {
  362 + typeLabels() {
  363 + return (this.dicOptions.TYPE || []).map(it => it.name)
  364 + },
  365 + deductionTypeOptions() {
  366 + return (this.dicOptions.DEDUCTION_TYPE || []).map(it => ({
  367 + value: it.code != null ? String(it.code) : '',
  368 + text: it.name || ''
  369 + })).filter(it => it.value)
  370 + },
  371 + isBuyout() {
  372 + const first = (this.selectedPlanRows || [])[0]
  373 + return !!(first && first.buyout)
  374 + },
  375 + // 过磅品种下拉:数据来自计划单明细
  376 + weightProductLabels() {
  377 + return (this.planList || []).map(item =>
  378 + [item.productName, item.planCode].filter(Boolean).join(' ')
  379 + )
  380 + },
  381 + varietyLabels() {
  382 + return (this.varietyOptions || []).map(item =>
  383 + [item.productName, item.code].filter(Boolean).join(' ')
  384 + )
  385 + },
  386 + // 物料名称:过磅明细开票品名去重顿号拼接
  387 + materialName() {
  388 + const names = (this.weighList || [])
  389 + .map(item => item.invoiceProductName)
  390 + .filter(name => !!name)
  391 + return Array.from(new Set(names)).join('、')
  392 + },
  393 + // 货物净重 = 过磅总重 - 实际伙重(保留两位)
  394 + computedNetWeight() {
  395 + const gross = this.form.totalGrossWeight
  396 + if (gross === undefined || gross === null || gross === '') return ''
  397 + const num = Number(gross) - Number(this.form.actualImpurityWeight || 0)
  398 + return String(Math.round(num * 100) / 100)
  399 + },
  400 + // 净重大写:中文大写
  401 + computedNetWeightUpper() {
  402 + const net = this.computedNetWeight
  403 + if (net === '' || net === null || net === undefined) return ''
  404 + return numberToChineseUpper(net)
  405 + },
  406 + // 业务摘要
  407 + computedBusinessSummary() {
  408 + const parts = []
  409 + const weightItems = (this.weighList || [])
  410 + .map(item => {
  411 + const name = item.productName || ''
  412 + const qty = item.quantity === null || item.quantity === undefined ? '' : item.quantity
  413 + if (!name && qty === '') return ''
  414 + return `${name}${qty}公斤`
  415 + })
  416 + .filter(text => !!text)
  417 + if (weightItems.length) parts.push(weightItems.join('、'))
  418 + const deductionNames = (this.form.deductionTypes || [])
  419 + .map(code => getDicName('DEDUCTION_TYPE', code, this.dicOptions.DEDUCTION_TYPE))
  420 + .filter(name => !!name)
  421 + if (deductionNames.length) parts.push(deductionNames.join('、'))
  422 + if (this.form.supplierStockMaterial) parts.push(`供应存欠料:${this.form.supplierStockMaterial}`)
  423 + if (this.form.logisticsStockMaterial) parts.push(`物流存欠料:${this.form.logisticsStockMaterial}`)
  424 + if (this.form.plateNumber) parts.push(`车牌号:${this.form.plateNumber}`)
  425 + return parts.join(',')
  426 + },
  427 + // 单据备注 = 业务摘要 + 特别说明
  428 + computedDocumentRemark() {
  429 + const summary = this.computedBusinessSummary || ''
  430 + const note = this.form.specialNote || ''
  431 + return [summary, note].filter(text => !!text).join('\n')
314 432 }
315 433 },
316   - onLoad() {
317   - this.initCode()
  434 + created() {
  435 + this.planSheet.fetchFn = this.fetchPlans.bind(this)
  436 + this.loadDicData()
  437 + this.loadVarietyOptions()
  438 + this.onGenerateCode()
318 439 },
319 440 methods: {
320   - initCode() {
321   - // 编号生成接口待后端提供,先占位
322   - this.form.code = this.form.code || ''
  441 + async loadDicData() {
  442 + try {
  443 + const results = await getDicByCodes(['TYPE', 'DEDUCTION_TYPE'])
  444 + this.dicOptions.TYPE = (results.TYPE && results.TYPE.data) || []
  445 + this.dicOptions.DEDUCTION_TYPE = (results.DEDUCTION_TYPE && results.DEDUCTION_TYPE.data) || []
  446 + // 类型默认选择「常规」
  447 + const defaultType = (this.dicOptions.TYPE || []).find(item => item.name === '常规')
  448 + if (defaultType) {
  449 + this.form.type = defaultType.code != null ? String(defaultType.code) : ''
  450 + }
  451 + } catch (e) {
  452 + this.dicOptions = { TYPE: [], DEDUCTION_TYPE: [] }
  453 + }
323 454 },
324   - calcNetWeight() {
325   - const g = Number(this.form.totalGrossWeight)
326   - const i = Number(this.form.actualImpurityWeight || 0)
327   - if (!isNaN(g) && g > 0) {
328   - const net = g - (isNaN(i) ? 0 : i)
329   - this.form.netWeight = String(Math.round(net * 10000) / 10000)
  455 + async loadVarietyOptions() {
  456 + try {
  457 + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 })
  458 + const _data = (res && res.data) || {}
  459 + this.varietyOptions = _data.datas || _data.list || _data.records || []
  460 + } catch (e) {
  461 + this.varietyOptions = []
330 462 }
331 463 },
332   - addPlan() {
333   - this.planList.push({ productName: '', quantity: '', planCode: '' })
  464 + getTypeLabel() {
  465 + const found = (this.dicOptions.TYPE || []).find(it => String(it.code) === String(this.form.type))
  466 + return found ? found.name : ''
  467 + },
  468 + onTypeChange(e) {
  469 + const idx = Number(e.detail.value)
  470 + const opt = (this.dicOptions.TYPE || [])[idx]
  471 + this.form.type = opt && opt.code != null ? String(opt.code) : ''
  472 + },
  473 + onGenerateCode() {
  474 + weighTicketGenerateCodeApi()
  475 + .then(res => {
  476 + this.form.code = (res && res.data) ? res.data : ''
  477 + })
  478 + .catch(() => {})
  479 + },
  480 + // 计划选择弹窗数据源
  481 + fetchPlans({ pageIndex, pageSize, extra }) {
  482 + const keyword = (extra && extra.keyword) || ''
  483 + const params = {
  484 + pageIndex,
  485 + pageSize,
  486 + code: keyword || undefined
  487 + }
  488 + return weighingTicketPlansApi(params).then(res => {
  489 + const _data = (res && res.data) || {}
  490 + const records = _data.datas || _data.list || _data.records || []
  491 + const totalCount = _data.totalCount || _data.count || 0
  492 + const hasNext = _data.hasNext || false
  493 + return { records, totalCount, hasNext }
  494 + }).catch(() => ({ records: [], totalCount: 0, hasNext: false }))
  495 + },
  496 + openPlanSheet() {
  497 + this.planSheet.selectedKeys = this.selectedPlanRows.map(it => it.id)
  498 + this.planSheet.visible = true
  499 + },
  500 + onPlanConfirm({ items }) {
  501 + const list = items || []
  502 + if (!list.length) {
  503 + this.selectedPlanRows = []
  504 + this.form.planCode = ''
  505 + this.form.supplierName = ''
  506 + this.form.companyShortName = ''
  507 + this.planList = []
  508 + this.weighList = []
  509 + return
  510 + }
  511 + // 校验:所选计划的「是否买断、供料单位、收料单位、车间」必须一致
  512 + const first = list[0]
  513 + const isSame = list.every(item =>
  514 + item.buyout === first.buyout &&
  515 + item.supplierName === first.supplierName &&
  516 + item.companyName === first.companyName &&
  517 + item.workshopName === first.workshopName
  518 + )
  519 + if (!isSame) {
  520 + uni.showToast({ title: '是否买断,供料单位,收料单位应保持一致', icon: 'none' })
  521 + this.planSheet.selectedKeys = this.selectedPlanRows.map(it => it.id)
  522 + return
  523 + }
  524 + this.selectedPlanRows = list.map(it => ({ ...it }))
  525 + this.form.planCode = list.map(it => it.code).filter(Boolean).join(',')
  526 + this.form.supplierName = first.supplierName || ''
  527 + this.form.companyShortName = (first.companyName || '') + (first.workshopName || '')
  528 + // 查询计划明细行,填充计划单明细;过磅明细初始化为计划明细拷贝
  529 + weighTicketPlanLinesApi(list.map(it => it.id)).then(res => {
  530 + const lines = (res && res.data) || []
  531 + this.planList = lines.map(it => ({ ...it }))
  532 + this.weighList = lines.map(it => ({ ...it, id: it.id || genRowId() }))
  533 + }).catch(() => {
  534 + this.planList = []
  535 + this.weighList = []
  536 + })
334 537 },
335   - addWeigh() {
336   - this.weighList.push({ productName: '', invoiceProductName: '', quantity: '', planCode: '' })
  538 + getWeightProductLabel(item) {
  539 + if (!item.productId) return ''
  540 + const found = (this.planList || []).find(it => it.productId === item.productId)
  541 + return found ? [found.productName, found.planCode].filter(Boolean).join(' ') : ''
337 542 },
338   - addApply() {
339   - this.applyList.push({ category: '', quantity: '' })
  543 + // 过磅品种改选:整行替换为选中计划明细数据
  544 + handleWeightProductChange(idx, optIdx) {
  545 + const raw = (this.planList || [])[optIdx]
  546 + if (!raw) return
  547 + const rowId = this.weighList[idx].id
  548 + this.weighList.splice(idx, 1, { ...raw, id: rowId })
  549 + },
  550 + getVarietyLabel(item) {
  551 + if (!item.breedRelationshipId) return ''
  552 + const found = (this.varietyOptions || []).find(it => it.id === item.breedRelationshipId)
  553 + return found ? [found.productName, found.code].filter(Boolean).join(' ') : ''
  554 + },
  555 + handleVarietyChange(idx, optIdx) {
  556 + const raw = (this.varietyOptions || [])[optIdx]
  557 + if (!raw) return
  558 + const row = this.applyList[idx]
  559 + row.category = raw.code || ''
  560 + row.breedRelationshipId = raw.id || ''
  561 + },
  562 + addWeightRow() {
  563 + this.weighList.push({
  564 + id: genRowId(),
  565 + procurementPlanId: '',
  566 + planCode: '',
  567 + productId: '',
  568 + invoiceProductName: '',
  569 + quantity: ''
  570 + })
  571 + },
  572 + addApplyRow() {
  573 + this.applyList.push({
  574 + id: genRowId(),
  575 + category: '',
  576 + breedRelationshipId: '',
  577 + quantity: ''
  578 + })
340 579 },
341 580 removeRow(type, idx) {
342   - if (type === 'plan') this.planList.splice(idx, 1)
343   - else if (type === 'weigh') this.weighList.splice(idx, 1)
  581 + if (type === 'weigh') this.weighList.splice(idx, 1)
344 582 else if (type === 'apply') this.applyList.splice(idx, 1)
345 583 },
346 584 validate() {
347 585 const f = this.form
348   - if (!f.type) return this.tip('请输入类型')
349   - if (!f.unit) return this.tip('请输入计划编号')
350   - if (!f.firstGrossWeightTime) return this.tip('请选择时间')
351   - if (!f.supplierName) return this.tip('请输入供料单位')
352   - if (!f.companyShortName) return this.tip('请输入收料单位')
353   - if (!f.materialName) return this.tip('请输入物料名称')
354   - if (!f.totalGrossWeight) return this.tip('请输入过磅总重')
  586 + if (!f.code) return this.tip('请生成编号')
  587 + if (!f.type) return this.tip('请选择类型')
  588 + if (!this.selectedPlanRows.length) return this.tip('请选择计划编号')
  589 + if (f.totalGrossWeight === '' || f.totalGrossWeight == null) return this.tip('请输入过磅总重')
  590 + if (f.actualImpurityWeight === '' || f.actualImpurityWeight == null) return this.tip('请输入实际伙重')
  591 + if (!f.deductionTypes || !f.deductionTypes.length) return this.tip('请选择来料扣杂类型')
  592 + if (!f.supplierStockMaterial) return this.tip('请输入供应存欠料')
  593 + if (!f.logisticsStockMaterial) return this.tip('请输入物流存欠料')
355 594 if (!f.plateNumber) return this.tip('请输入车牌号')
356   - if (!f.businessSummary) return this.tip('请输入业务摘要')
  595 + // 过磅明细:品种、数量必填
  596 + const weightList = this.weighList || []
  597 + for (let i = 0; i < weightList.length; i++) {
  598 + const row = weightList[i]
  599 + if (!row.productId) return this.tip(`过磅明细第${i + 1}行:请选择过磅品种`)
  600 + if (row.quantity === '' || row.quantity == null) return this.tip(`过磅明细第${i + 1}行:请输入过磅数量`)
  601 + }
  602 + // 申请数量明细:类别、数量必填(买断时)
  603 + if (this.isBuyout) {
  604 + const applyList = this.applyList || []
  605 + for (let i = 0; i < applyList.length; i++) {
  606 + const row = applyList[i]
  607 + if (!row.breedRelationshipId) return this.tip(`申请数量明细第${i + 1}行:请选择类别`)
  608 + if (row.quantity === '' || row.quantity == null) return this.tip(`申请数量明细第${i + 1}行:请输入数量`)
  609 + }
  610 + }
357 611 return true
358 612 },
359 613 tip(msg) {
360 614 uni.showToast({ title: msg, icon: 'none' })
361 615 return false
362 616 },
  617 + buildSubmitData() {
  618 + return {
  619 + code: this.form.code,
  620 + type: this.form.type,
  621 + planIds: this.selectedPlanRows.map(it => it.id),
  622 + weighLines: (this.weighList || []).map(row => ({
  623 + procurementPlanId: row.procurementPlanId || '',
  624 + planCode: row.planCode || '',
  625 + productId: row.productId || '',
  626 + invoiceProductName: row.invoiceProductName || '',
  627 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  628 + itemId: row.id
  629 + })),
  630 + applyLines: (this.applyList || []).map(row => ({
  631 + category: row.category || '',
  632 + breedRelationshipId: row.breedRelationshipId || '',
  633 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  634 + itemId: row.breedRelationshipId
  635 + })),
  636 + unit: this.form.unit,
  637 + materialName: this.materialName || null,
  638 + totalGrossWeight: this.form.totalGrossWeight === '' ? null : Number(this.form.totalGrossWeight),
  639 + actualImpurityWeight: this.form.actualImpurityWeight === '' ? null : Number(this.form.actualImpurityWeight),
  640 + netWeight: this.computedNetWeight === '' ? null : Number(this.computedNetWeight),
  641 + netWeightUpper: this.computedNetWeightUpper || null,
  642 + deductionTypes: (this.form.deductionTypes || []).join(',') || null,
  643 + supplierStockMaterial: this.form.supplierStockMaterial || null,
  644 + logisticsStockMaterial: this.form.logisticsStockMaterial || null,
  645 + plateNumber: this.form.plateNumber || null,
  646 + businessSummary: this.computedBusinessSummary || null,
  647 + specialNote: this.form.specialNote || null,
  648 + documentRemark: this.computedDocumentRemark || null
  649 + }
  650 + },
363 651 async onSubmit() {
364 652 if (!this.validate()) return
365 653 const confirmRes = await new Promise(resolve => {
366   - uni.showModal({ title: '提示', content: '确定新增过磅码单吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  654 + uni.showModal({ title: '提示', content: '确定保存过磅单吗?', confirmText: '确定', cancelText: '取消', success: resolve })
367 655 })
368 656 if (!(confirmRes && confirmRes.confirm)) return
369   - const clean = (obj) => {
370   - const out = {}
371   - Object.keys(obj || {}).forEach(k => {
372   - const v = obj[k]
373   - const isEmptyString = typeof v === 'string' && v.trim() === ''
374   - const isUndef = v === undefined || v === null
375   - if (!(isEmptyString || isUndef)) out[k] = v
376   - })
377   - return out
378   - }
379   - const payload = clean({
380   - ...this.form,
381   - planDetailList: this.planList.map(it => clean(it)),
382   - weighDetailList: this.weighList.map(it => clean(it)),
383   - applyQuantityList: this.applyList.map(it => clean(it))
384   - })
385 657 uni.showLoading({ title: '提交中...', mask: true })
386   - weighTicketSaveApi(payload)
  658 + weighTicketCreateApi(this.buildSubmitData())
387 659 .then(() => {
388 660 uni.hideLoading()
389   - uni.showToast({ title: '新增成功', icon: 'none' })
  661 + uni.showToast({ title: '保存成功', icon: 'none' })
390 662 try { uni.setStorageSync('WEIGHT_TICKET_LIST_NEED_REFRESH', '1') } catch (e) {}
391 663 setTimeout(() => { uni.navigateBack() }, 400)
392 664 })
393 665 .catch(e => {
394 666 uni.hideLoading()
395   - uni.showToast({ title: (e && (e.msg || e.message)) || '新增失败', icon: 'none' })
  667 + uni.showToast({ title: (e && (e.msg || e.message)) || '保存失败', icon: 'none' })
396 668 })
397 669 }
398 670 }
399 671 }
  672 +
  673 +// 生成前端临时行 id
  674 +function genRowId() {
  675 + return 'row_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8)
  676 +}
  677 +
  678 +// 数字转中文大写(对齐 PC 端 useWeightTicketForm.ts)
  679 +function numberToChineseUpper(num) {
  680 + if (num === null || num === undefined || num === '' || isNaN(Number(num))) return '零'
  681 + const n = Number(num)
  682 + if (!isFinite(n)) return '零'
  683 +
  684 + const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
  685 + const smallUnits = ['', '拾', '佰', '仟']
  686 + const bigUnits = ['', '万', '亿', '兆']
  687 +
  688 + const negative = n < 0
  689 + const abs = Math.abs(n)
  690 + const parts = abs.toString().split('.')
  691 + const intStrRaw = parts[0]
  692 + const decStrRaw = parts[1] || ''
  693 + const s = intStrRaw
  694 + const len = s.length
  695 +
  696 + let intPart = ''
  697 + let zeroFlag = false
  698 + for (let i = 0; i < len; i++) {
  699 + const d = parseInt(s[i], 10)
  700 + const pos = len - 1 - i
  701 + const unitIdx = pos % 4
  702 + const groupIdx = Math.floor(pos / 4)
  703 + if (d !== 0) {
  704 + if (zeroFlag) {
  705 + intPart += digit[0]
  706 + zeroFlag = false
  707 + }
  708 + intPart += digit[d] + smallUnits[unitIdx]
  709 + } else {
  710 + zeroFlag = true
  711 + }
  712 + if (unitIdx === 0) {
  713 + const start = Math.max(0, len - (groupIdx + 1) * 4)
  714 + const end = len - groupIdx * 4
  715 + const groupDigits = s.slice(start, end)
  716 + if (!/^0+$/.test(groupDigits)) {
  717 + intPart += bigUnits[groupIdx] || ''
  718 + }
  719 + }
  720 + }
  721 + if (intPart === '') intPart = digit[0]
  722 +
  723 + let result = intPart
  724 + if (decStrRaw) {
  725 + const decChars = decStrRaw
  726 + .split('')
  727 + .map(c => digit[parseInt(c, 10)])
  728 + .join('')
  729 + result += '点' + decChars
  730 + }
  731 + return (negative ? '负' : '') + result
  732 +}
400 733 </script>
401 734
402 735 <style lang="scss" scoped>
... ... @@ -557,6 +890,18 @@ export default {
557 890 text-align: left;
558 891 }
559 892
  893 +.picker-value {
  894 + font-size: 32rpx;
  895 + color: rgba(0, 0, 0, 0.9);
  896 + line-height: 48rpx;
  897 + text-align: right;
  898 + min-height: 48rpx;
  899 +
  900 + &.placeholder {
  901 + color: rgba(0, 0, 0, 0.4);
  902 + }
  903 +}
  904 +
560 905 .sub-table,
561 906 .remark-section {
562 907 margin-top: 20rpx;
... ... @@ -644,19 +989,15 @@ export default {
644 989 padding: 32rpx;
645 990 }
646 991
647   -.remark-group {
648   - margin-bottom: 32rpx;
649   -
650   - &:last-child {
651   - margin-bottom: 0;
652   - }
  992 +.form-group {
  993 + margin: 24rpx 0 32rpx;
653 994
654   - .remark-label {
  995 + .item-title {
655 996 display: flex;
656 997 align-items: center;
657 998 font-size: 32rpx;
658 999 color: rgba(0, 0, 0, 0.9);
659   - margin-bottom: 16rpx;
  1000 + margin-bottom: 20rpx;
660 1001 position: relative;
661 1002 padding-left: 16rpx;
662 1003
... ... @@ -667,4 +1008,20 @@ export default {
667 1008 }
668 1009 }
669 1010 }
  1011 +
  1012 +.remark-group {
  1013 + margin-bottom: 32rpx;
  1014 +
  1015 + &:last-child {
  1016 + margin-bottom: 0;
  1017 + }
  1018 +
  1019 + .remark-label {
  1020 + display: flex;
  1021 + align-items: center;
  1022 + font-size: 32rpx;
  1023 + color: rgba(0, 0, 0, 0.9);
  1024 + margin-bottom: 16rpx;
  1025 + }
  1026 +}
670 1027 </style>
... ...
... ... @@ -5,47 +5,12 @@
5 5 <!-- 基础信息 -->
6 6 <view class="section">
7 7 <text class="row company">{{ safeText(form.code) }}</text>
8   - <view class="row"><text class="label">类型</text><text class="value">{{ safeText(form.type) }}</text></view>
9   - <view class="row"><text class="label">计划编号</text><text class="value">{{ safeText(form.unit) }}</text></view>
  8 + <view class="row"><text class="label">类型</text><text class="value">{{ typeName }}</text></view>
  9 + <view class="row"><text class="label">计划编号</text><text class="value">{{ safeText(planCodeText) }}</text></view>
  10 + <view class="row"><text class="label">单位</text><text class="value">{{ safeText(form.unit) }}</text></view>
10 11 <view class="row"><text class="label">时间</text><text class="value">{{ formatDate(form.firstGrossWeightTime) }}</text></view>
11 12 <view class="row"><text class="label">供料单位</text><text class="value">{{ safeText(form.supplierName) }}</text></view>
12 13 <view class="row"><text class="label">收料单位</text><text class="value">{{ safeText(form.companyShortName) }}</text></view>
13   - <view class="row"><text class="label">物料名称</text><text class="value">{{ safeText(form.materialName) }}</text></view>
14   - <view class="row"><text class="label">过磅总重</text><text class="value">{{ formatNumber(form.totalGrossWeight) }}</text></view>
15   - <view class="row"><text class="label">实际伙重</text><text class="value">{{ formatNumber(form.actualImpurityWeight) }}</text></view>
16   - <view class="row"><text class="label">货物净重</text><text class="value">{{ formatNumber(form.netWeight) }}</text></view>
17   - <view class="row"><text class="label">净重大写</text><text class="value">{{ safeText(form.netWeightUpper) }}</text></view>
18   - <view class="row"><text class="label">来料扣杂类型</text><text class="value">{{ safeText(form.deductionTypes) }}</text></view>
19   - <view class="row"><text class="label">供应存欠料</text><text class="value">{{ safeText(form.supplierStockMaterial) }}</text></view>
20   - <view class="row"><text class="label">物流存欠料</text><text class="value">{{ safeText(form.logisticsStockMaterial) }}</text></view>
21   - <view class="row"><text class="label">车牌号</text><text class="value">{{ safeText(form.plateNumber) }}</text></view>
22   - </view>
23   -
24   - <!-- 业务摘要 -->
25   - <view class="title-header">
26   - <image class="title-header_icon" src="/static/images/title.png" />
27   - <span>业务摘要</span>
28   - </view>
29   - <view class="section">
30   - <text class="remark-text">{{ safeText(form.businessSummary) }}</text>
31   - </view>
32   -
33   - <!-- 特别说明 -->
34   - <view class="title-header">
35   - <image class="title-header_icon" src="/static/images/title.png" />
36   - <span>特别说明</span>
37   - </view>
38   - <view class="section">
39   - <text class="remark-text">{{ safeText(form.specialNote) }}</text>
40   - </view>
41   -
42   - <!-- 单据备注 -->
43   - <view class="title-header">
44   - <image class="title-header_icon" src="/static/images/title.png" />
45   - <span>单据备注</span>
46   - </view>
47   - <view class="section">
48   - <text class="remark-text">{{ safeText(form.specialNote) }}</text>
49 14 </view>
50 15
51 16 <!-- 计划单明细 -->
... ... @@ -91,8 +56,8 @@
91 56 </view>
92 57 </view>
93 58
94   - <!-- 申请数量明细 -->
95   - <view class="sub-table">
  59 + <!-- 申请数量明细(仅买断或有数据时展示) -->
  60 + <view v-if="isBuyout" class="sub-table">
96 61 <view class="sub-header">
97 62 <image class="sub-bar" src="/static/images/title.png" />
98 63 <text class="sub-title">申请数量明细</text>
... ... @@ -110,16 +75,65 @@
110 75 </view>
111 76 </view>
112 77 </view>
  78 +
  79 + <!-- 其他信息 -->
  80 + <view class="title-header">
  81 + <image class="title-header_icon" src="/static/images/title.png" />
  82 + <span>其他信息</span>
  83 + </view>
  84 + <view class="section">
  85 + <view class="row"><text class="label">物料名称</text><text class="value">{{ safeText(form.materialName) }}</text></view>
  86 + <view class="row"><text class="label">过磅总重</text><text class="value">{{ formatNumber(form.totalGrossWeight) }}</text></view>
  87 + <view class="row"><text class="label">实际伙重</text><text class="value">{{ formatNumber(form.actualImpurityWeight) }}</text></view>
  88 + <view class="row"><text class="label">货物净重</text><text class="value">{{ formatNumber(form.netWeight) }}</text></view>
  89 + <view class="row"><text class="label">净重大写</text><text class="value">{{ safeText(form.netWeightUpper) }}</text></view>
  90 + <view class="row"><text class="label">来料扣杂类型</text><text class="value">{{ deductionNames }}</text></view>
  91 + <view class="row"><text class="label">供应存欠料</text><text class="value">{{ safeText(form.supplierStockMaterial) }}</text></view>
  92 + <view class="row"><text class="label">物流存欠料</text><text class="value">{{ safeText(form.logisticsStockMaterial) }}</text></view>
  93 + <view class="row"><text class="label">车牌号</text><text class="value">{{ safeText(form.plateNumber) }}</text></view>
  94 + </view>
  95 +
  96 + <!-- 业务摘要 -->
  97 + <view class="title-header">
  98 + <image class="title-header_icon" src="/static/images/title.png" />
  99 + <span>业务摘要</span>
  100 + </view>
  101 + <view class="section">
  102 + <text class="remark-text">{{ safeText(form.businessSummary) }}</text>
  103 + </view>
  104 +
  105 + <!-- 特别说明 -->
  106 + <view class="title-header">
  107 + <image class="title-header_icon" src="/static/images/title.png" />
  108 + <span>特别说明</span>
  109 + </view>
  110 + <view class="section">
  111 + <text class="remark-text">{{ safeText(form.specialNote) }}</text>
  112 + </view>
  113 +
  114 + <!-- 单据备注 -->
  115 + <view class="title-header">
  116 + <image class="title-header_icon" src="/static/images/title.png" />
  117 + <span>单据备注</span>
  118 + </view>
  119 + <view class="section">
  120 + <text class="remark-text">{{ safeText(form.documentRemark) }}</text>
  121 + </view>
113 122 </view>
114 123 </scroll-view>
  124 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
115 125 </view>
116 126 </template>
117 127
118 128 <script>
119   -import { weighTicketDetailApi } from '@/api/procure-manage/weighTicket.js'
  129 +import DetailButtons from '@/components/detail-buttons/index.vue'
  130 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
  131 +import { weighTicketDetailApi, weighTicketSubmitAuditApi, weighTicketCancelApi } from '@/api/procure-manage/weighTicket.js'
  132 +import { getInfo } from '@/api/login.js'
120 133
121 134 export default {
122 135 name: 'WeightTicketDetail',
  136 + components: { DetailButtons },
123 137 data() {
124 138 return {
125 139 id: '',
... ... @@ -127,12 +141,79 @@ export default {
127 141 planList: [],
128 142 weighList: [],
129 143 applyList: [],
130   - collapse: { plan: false, weigh: false, apply: false }
  144 + buyout: false,
  145 + currentUserId: '',
  146 + collapse: { plan: false, weigh: false, apply: false },
  147 + dicOptions: {
  148 + TYPE: [],
  149 + DEDUCTION_TYPE: [],
  150 + AUDIT_STATUS: []
  151 + },
  152 + buttons: [
  153 + { text: '修改', visible: true, variant: 'outline', event: 'edit' },
  154 + { text: '提交审核', visible: true, variant: 'outline', event: 'submitAudit' },
  155 + { text: '作废', visible: true, variant: 'outline', event: 'cancel' },
  156 + { text: '上传签字凭证', visible: true, variant: 'outline', event: 'uploadSign' },
  157 + { text: '上传延期说明', visible: true, variant: 'outline', event: 'uploadDelay' },
  158 + { text: '复磅', visible: true, variant: 'primary', event: 'reweigh' }
  159 + ]
  160 + }
  161 + },
  162 + computed: {
  163 + typeName() {
  164 + const v = this.form.type == null ? '' : String(this.form.type)
  165 + if (!v) return '-'
  166 + return getDicName('TYPE', v, this.dicOptions.TYPE) || v
  167 + },
  168 + planCodeText() {
  169 + const codes = Array.from(new Set((this.planList || []).map(it => it.planCode).filter(Boolean)))
  170 + return codes.join(',')
  171 + },
  172 + deductionNames() {
  173 + const codes = (this.form.deductionTypes == null ? '' : String(this.form.deductionTypes)).split(',').filter(Boolean)
  174 + const names = codes.map(code => getDicName('DEDUCTION_TYPE', code, this.dicOptions.DEDUCTION_TYPE)).filter(Boolean)
  175 + return names.length ? names.join('、') : '-'
  176 + },
  177 + isBuyout() {
  178 + return this.buyout || (this.applyList || []).length > 0
  179 + },
  180 + displayButtons() {
  181 + const status = this.form.approvalStatus || ''
  182 + const isMine = this.form.createById === this.currentUserId
  183 + const isPass = status === 'PASS'
  184 + return [
  185 + {
  186 + ...this.buttons[0],
  187 + visible: (status === 'DRAFT' || status === 'REFUSE' || !status) && isMine && this.$auth.hasPermi('warehouse-manage:weight-ticket:modify')
  188 + },
  189 + {
  190 + ...this.buttons[1],
  191 + visible: (status === 'DRAFT' || status === 'REFUSE' || !status) && isMine && this.$auth.hasPermi('warehouse-manage:weight-ticket:submit')
  192 + },
  193 + {
  194 + ...this.buttons[2],
  195 + visible: isPass && this.$auth.hasPermi('warehouse-manage:weight-ticket:cancel')
  196 + },
  197 + {
  198 + ...this.buttons[3],
  199 + visible: isPass && this.$auth.hasPermi('warehouse-manage:weight-ticket:uploadSigned')
  200 + },
  201 + {
  202 + ...this.buttons[4],
  203 + visible: isPass && this.$auth.hasPermi('warehouse-manage:weight-ticket:uploadExtension')
  204 + },
  205 + {
  206 + ...this.buttons[5],
  207 + visible: isPass && this.$auth.hasPermi('warehouse-manage:weight-ticket:reweigh')
  208 + }
  209 + ]
131 210 }
132 211 },
133 212 onLoad(query) {
134 213 this.id = (query && query.id) || ''
  214 + this.loadDicData()
135 215 this.loadDetail()
  216 + this.getUserInfo()
136 217 },
137 218 onShow() {
138 219 let needRefresh = ''
... ... @@ -145,6 +226,24 @@ export default {
145 226 toggle(key) {
146 227 this.collapse = { ...this.collapse, [key]: !this.collapse[key] }
147 228 },
  229 + async loadDicData() {
  230 + try {
  231 + const results = await getDicByCodes(['TYPE', 'DEDUCTION_TYPE', 'AUDIT_STATUS'])
  232 + this.dicOptions.TYPE = (results.TYPE && results.TYPE.data) || []
  233 + this.dicOptions.DEDUCTION_TYPE = (results.DEDUCTION_TYPE && results.DEDUCTION_TYPE.data) || []
  234 + this.dicOptions.AUDIT_STATUS = (results.AUDIT_STATUS && results.AUDIT_STATUS.data) || []
  235 + } catch (e) {
  236 + this.dicOptions = { TYPE: [], DEDUCTION_TYPE: [], AUDIT_STATUS: [] }
  237 + }
  238 + },
  239 + async getUserInfo() {
  240 + try {
  241 + const res = await getInfo()
  242 + this.currentUserId = (res && (res.id || (res.data && res.data.id))) || ''
  243 + } catch (e) {
  244 + this.currentUserId = ''
  245 + }
  246 + },
148 247 loadDetail() {
149 248 if (!this.id) {
150 249 uni.showToast({ title: '缺少单据ID', icon: 'none' })
... ... @@ -155,9 +254,10 @@ export default {
155 254 .then(res => {
156 255 const data = res && res.data ? (res.data.data || res.data) : {}
157 256 this.form = Object.assign({}, data)
158   - this.planList = this.pickList(data, ['planDetailList', 'planLineList', 'procurementPlanLineList'])
159   - this.weighList = this.pickList(data, ['weighDetailList', 'weighLineList', 'grossWeightList'])
160   - this.applyList = this.pickList(data, ['applyQuantityList', 'applyDetailList', 'applyLineList'])
  257 + this.buyout = !!data.buyout
  258 + this.planList = Array.isArray(data.planLines) ? data.planLines.map(x => ({ ...x })) : []
  259 + this.weighList = Array.isArray(data.weighLines) ? data.weighLines.map(x => ({ ...x })) : []
  260 + this.applyList = Array.isArray(data.applyLines) ? data.applyLines.map(x => ({ ...x })) : []
161 261 })
162 262 .catch(() => {
163 263 uni.showToast({ title: '详情加载失败', icon: 'none' })
... ... @@ -166,12 +266,83 @@ export default {
166 266 uni.hideLoading && uni.hideLoading()
167 267 })
168 268 },
169   - pickList(data, keys) {
170   - if (!data) return []
171   - for (const k of keys) {
172   - if (Array.isArray(data[k])) return data[k].map(x => Object.assign({}, x))
  269 + handleButtonClick(btn) {
  270 + if (!btn || btn.disabled || !btn.visible) return
  271 + const e = btn.event || ''
  272 + if (e === 'edit') return this.onEdit()
  273 + if (e === 'submitAudit') return this.onSubmitAudit()
  274 + if (e === 'cancel') return this.onCancel()
  275 + if (e === 'uploadSign') return this.onUpload('sign')
  276 + if (e === 'uploadDelay') return this.onUpload('delay')
  277 + if (e === 'reweigh') return this.onReweigh()
  278 + },
  279 + onEdit() {
  280 + uni.navigateTo({ url: '/pages/weight-ticket/modify?id=' + this.id })
  281 + },
  282 + onUpload(mode) {
  283 + uni.navigateTo({ url: '/pages/weight-ticket/upload?id=' + this.id + '&mode=' + mode })
  284 + },
  285 + onReweigh() {
  286 + uni.navigateTo({ url: '/pages/weight-ticket/reweigh?id=' + this.id })
  287 + },
  288 + refreshListFlags() {
  289 + try {
  290 + uni.setStorageSync('WEIGHT_TICKET_LIST_NEED_REFRESH', '1')
  291 + uni.setStorageSync('WEIGHT_TICKET_DETAIL_NEED_REFRESH', '1')
  292 + } catch (e) {}
  293 + },
  294 + runWithConfirm(opts) {
  295 + const title = opts.title || '提示'
  296 + const content = opts.content || ''
  297 + const run = opts.run
  298 + const loading = opts.loading || '处理中'
  299 + const successText = opts.successText || '操作成功'
  300 + const failText = opts.failText || '操作失败'
  301 + if (!this.id) {
  302 + uni.showToast({ title: '缺少单据ID', icon: 'none' })
  303 + return
173 304 }
174   - return []
  305 + uni.showModal({
  306 + title,
  307 + content,
  308 + confirmText: '确定',
  309 + cancelText: '取消',
  310 + success: res => {
  311 + if (!res || !res.confirm) return
  312 + uni.showLoading({ title: loading, mask: true })
  313 + Promise.resolve(run(this.id))
  314 + .then(() => {
  315 + uni.hideLoading()
  316 + uni.showToast({ title: successText, icon: 'none' })
  317 + this.refreshListFlags()
  318 + this.loadDetail()
  319 + })
  320 + .catch(e => {
  321 + uni.hideLoading()
  322 + uni.showToast({ title: (e && (e.msg || e.message)) || failText, icon: 'none' })
  323 + })
  324 + }
  325 + })
  326 + },
  327 + onSubmitAudit() {
  328 + this.runWithConfirm({
  329 + title: '提交审核',
  330 + content: '确认提交审核吗?',
  331 + loading: '提交中',
  332 + successText: '提交审核成功',
  333 + failText: '提交审核失败',
  334 + run: id => weighTicketSubmitAuditApi(id)
  335 + })
  336 + },
  337 + onCancel() {
  338 + this.runWithConfirm({
  339 + title: '作废',
  340 + content: '确认作废该过磅单吗?',
  341 + loading: '处理中',
  342 + successText: '作废成功',
  343 + failText: '作废失败',
  344 + run: id => weighTicketCancelApi(id)
  345 + })
175 346 },
176 347 safeText(v) {
177 348 const s = v == null ? '' : String(v)
... ... @@ -200,7 +371,7 @@ export default {
200 371 }
201 372 .scroll {
202 373 flex: 1;
203   - padding: 8rpx 0 32rpx;
  374 + padding: 8rpx 0 144rpx;
204 375 }
205 376 .detail-page {
206 377 background: #f3f3f3;
... ...
... ... @@ -5,7 +5,7 @@
5 5 <uni-search-bar
6 6 v-model="searchKeyword"
7 7 radius="6"
8   - placeholder="请输入编号/计划编号/供料单位"
  8 + placeholder="请输入编号"
9 9 clearButton="auto"
10 10 cancelButton="none"
11 11 bgColor="#F3F3F3"
... ... @@ -15,7 +15,7 @@
15 15 />
16 16 <view class="tool-icons">
17 17 <image
18   - v-if="$auth.hasPermi('procure-manage:weigh-ticket:add')"
  18 + v-if="$auth.hasPermi('warehouse-manage:weight-ticket:add')"
19 19 class="tool-icon"
20 20 src="/static/images/dev_manage/add_icon.png"
21 21 @click="onAdd"
... ... @@ -60,13 +60,17 @@
60 60 </view>
61 61 <view class="info-row">
62 62 <text>计划编号</text>
63   - <text>{{ item.planCode || '-' }}</text>
  63 + <text>{{ item.planCodes || '-' }}</text>
64 64 </view>
65 65 <view class="info-row">
66 66 <text>公司简称</text>
67 67 <text>{{ item.companyShortName || '-' }}</text>
68 68 </view>
69 69 <view class="info-row">
  70 + <text>创建人</text>
  71 + <text>{{ item.createBy || '-' }}</text>
  72 + </view>
  73 + <view class="info-row">
70 74 <text>时间</text>
71 75 <text>{{ formatDateOnly(item.firstGrossWeightTime) }}</text>
72 76 </view>
... ... @@ -98,7 +102,12 @@
98 102 </view>
99 103 <view class="form-item">
100 104 <view class="label">公司简称</view>
101   - <input class="input" v-model="model.companyShortName" placeholder="请输入公司简称" />
  105 + <picker mode="selector" :range="companyShortLabels" @change="onCompanyShortChange">
  106 + <view class="fake-select">
  107 + <text v-if="companyShortLabel" class="value">{{ companyShortLabel }}</text>
  108 + <text v-else class="placeholder">请选择公司简称</text>
  109 + </view>
  110 + </picker>
102 111 </view>
103 112 <view class="form-item">
104 113 <view class="label">审核状态</view>
... ... @@ -108,11 +117,14 @@
108 117 :value-field="'value'"
109 118 :text-field="'text'"
110 119 v-model="model.approvalStatus"
111   - @change="onApprovalStatusChange"
112 120 :localdata="approvalStatusOptions"
113 121 />
114 122 </view>
115 123 <view class="form-item">
  124 + <view class="label">创建人</view>
  125 + <input class="input" v-model="model.createBy" placeholder="请输入创建人" />
  126 + </view>
  127 + <view class="form-item">
116 128 <view class="label">时间</view>
117 129 <uni-datetime-picker type="daterange" v-model="model.timeRange" start="2023-01-01" />
118 130 </view>
... ... @@ -126,7 +138,7 @@
126 138 import CardList from '@/components/card/index.vue'
127 139 import FilterModal from '@/components/filter/index.vue'
128 140 import { getDicByCodes, getDicName } from '@/utils/dic.js'
129   -import { weighTicketQueryApi } from '@/api/procure-manage/weighTicket.js'
  141 +import { weighTicketQueryApi, companyShortQueryApi } from '@/api/procure-manage/weighTicket.js'
130 142
131 143 export default {
132 144 name: 'WeightTicketList',
... ... @@ -136,17 +148,18 @@ export default {
136 148 searchKeyword: '',
137 149 searchKeywordDebounced: '',
138 150 searchDebounceTimer: null,
139   - deleted: 0,
  151 + deleted: false,
140 152 tabs: [
141   - { label: '有效', value: 0 },
142   - { label: '作废', value: 1 }
  153 + { label: '有效', value: false },
  154 + { label: '作废', value: true }
143 155 ],
144 156 query: {
145 157 code: '',
146 158 planCode: '',
147 159 supplierName: '',
148   - companyShortName: '',
  160 + companyShortId: '',
149 161 approvalStatus: '',
  162 + createBy: '',
150 163 timeRange: []
151 164 },
152 165 extraParams: {},
... ... @@ -156,21 +169,33 @@ export default {
156 169 code: '',
157 170 planCode: '',
158 171 supplierName: '',
159   - companyShortName: '',
  172 + companyShortId: '',
160 173 approvalStatus: '',
  174 + createBy: '',
161 175 timeRange: []
162 176 },
163   - approvalStatusOptions: [{ value: '', text: '全部' }],
  177 + approvalStatusOptions: [
  178 + { value: '', text: '全部' },
  179 + { value: 'DRAFT', text: '草稿' },
  180 + { value: 'AUDIT', text: '审核中' },
  181 + { value: 'PASS', text: '审核通过' },
  182 + { value: 'REFUSE', text: '已驳回' },
  183 + { value: 'CANCEL', text: '已取消' }
  184 + ],
  185 + companyShortOptions: [],
  186 + companyShortLabel: '',
164 187 dicOptions: {
165 188 AUDIT_STATUS: []
166 189 }
167 190 }
168 191 },
169 192 computed: {
  193 + companyShortLabels() {
  194 + return ['全部'].concat(this.companyShortOptions.map(it => it.label))
  195 + },
170 196 extraCombined() {
171   - const keyword = this.searchKeywordDebounced || ''
172 197 return {
173   - keyword: keyword || undefined,
  198 + keyword: this.searchKeywordDebounced || undefined,
174 199 deleted: this.deleted
175 200 }
176 201 }
... ... @@ -186,6 +211,7 @@ export default {
186 211 },
187 212 created() {
188 213 this.loadDicData()
  214 + this.loadCompanyShortOptions()
189 215 },
190 216 onShow() {
191 217 let needRefresh = ''
... ... @@ -212,18 +238,20 @@ export default {
212 238 try {
213 239 const results = await getDicByCodes(['AUDIT_STATUS'])
214 240 this.dicOptions.AUDIT_STATUS = (results.AUDIT_STATUS && results.AUDIT_STATUS.data) || []
215   - const list = Array.isArray(this.dicOptions.AUDIT_STATUS) ? this.dicOptions.AUDIT_STATUS : []
216   - this.approvalStatusOptions = [{ value: '', text: '全部' }].concat(
217   - list
218   - .map(it => ({
219   - value: it && it.code != null ? String(it.code) : '',
220   - text: it && it.name != null ? String(it.name) : ''
221   - }))
222   - .filter(it => it.value)
223   - )
224 241 } catch (e) {
225 242 this.dicOptions.AUDIT_STATUS = []
226   - this.approvalStatusOptions = [{ value: '', text: '全部' }]
  243 + }
  244 + },
  245 + async loadCompanyShortOptions() {
  246 + try {
  247 + const res = await companyShortQueryApi({ pageIndex: 1, pageSize: 9999 })
  248 + const datas = (res && res.data && res.data.datas) || []
  249 + this.companyShortOptions = datas.map(item => ({
  250 + label: item.shortName || '',
  251 + value: item.id || ''
  252 + })).filter(it => it.value)
  253 + } catch (e) {
  254 + this.companyShortOptions = []
227 255 }
228 256 },
229 257 getAuditStatusText(status) {
... ... @@ -261,39 +289,46 @@ export default {
261 289 switchTab(t) {
262 290 this.deleted = t.value
263 291 },
  292 + onCompanyShortChange(e) {
  293 + const idx = Number(e.detail.value)
  294 + if (idx <= 0) {
  295 + this.filterForm.companyShortId = ''
  296 + this.companyShortLabel = ''
  297 + } else {
  298 + const opt = this.companyShortOptions[idx - 1]
  299 + this.filterForm.companyShortId = opt ? opt.value : ''
  300 + this.companyShortLabel = opt ? opt.label : ''
  301 + }
  302 + },
264 303 openFilter() {
265 304 this.filterForm = {
266 305 code: this.query.code || '',
267 306 planCode: this.query.planCode || '',
268 307 supplierName: this.query.supplierName || '',
269   - companyShortName: this.query.companyShortName || '',
  308 + companyShortId: this.query.companyShortId || '',
270 309 approvalStatus: this.query.approvalStatus || '',
  310 + createBy: this.query.createBy || '',
271 311 timeRange: Array.isArray(this.query.timeRange) ? this.query.timeRange.slice(0) : []
272 312 }
  313 + const found = this.companyShortOptions.find(it => it.value === this.filterForm.companyShortId)
  314 + this.companyShortLabel = found ? found.label : ''
273 315 this.filterVisible = true
274 316 },
275 317 onFilterReset(payload) {
276 318 this.filterForm = payload
  319 + this.companyShortLabel = ''
277 320 },
278 321 onFilterConfirm(payload) {
279 322 this.query = {
280 323 code: payload.code || '',
281 324 planCode: payload.planCode || '',
282 325 supplierName: payload.supplierName || '',
283   - companyShortName: payload.companyShortName || '',
  326 + companyShortId: payload.companyShortId || '',
284 327 approvalStatus: payload.approvalStatus || '',
  328 + createBy: payload.createBy || '',
285 329 timeRange: Array.isArray(payload.timeRange) ? payload.timeRange : []
286 330 }
287 331 },
288   - onApprovalStatusChange(e) {
289   - const raw =
290   - e && e.detail && e.detail.value !== undefined
291   - ? e.detail.value
292   - : e && e.value !== undefined
293   - ? e.value
294   - : ''
295   - this.filterForm.approvalStatus = raw
296   - },
297 332 fetchList({ pageIndex, pageSize, query, extra }) {
298 333 const q = query || {}
299 334 const e = extra || {}
... ... @@ -302,19 +337,15 @@ export default {
302 337 const params = {
303 338 pageIndex,
304 339 pageSize,
305   - deleted: e.deleted != null ? e.deleted : 0,
306   - code: q.code || '',
  340 + deleted: e.deleted === true,
  341 + code: q.code || keyword || '',
307 342 planCode: q.planCode || '',
308 343 supplierName: q.supplierName || '',
309   - companyShortName: q.companyShortName || '',
  344 + companyShortId: q.companyShortId || '',
310 345 approvalStatus: q.approvalStatus || '',
311   - startFirstGrossWeightTime: range && range.length === 2 ? range[0] : '',
312   - endFirstGrossWeightTime: range && range.length === 2 ? range[1] : ''
313   - }
314   - if (keyword) {
315   - if (!params.code) params.code = keyword
316   - if (!params.planCode) params.planCode = keyword
317   - if (!params.supplierName) params.supplierName = keyword
  346 + createBy: q.createBy || '',
  347 + startTime: range && range.length === 2 ? range[0] : '',
  348 + endTime: range && range.length === 2 ? range[1] : ''
318 349 }
319 350 return weighTicketQueryApi(params)
320 351 .then(res => {
... ... @@ -331,7 +362,7 @@ export default {
331 362 })
332 363 },
333 364 onCardClick(item) {
334   - const id = item && (item.id || item.weighTicketId)
  365 + const id = item && item.id
335 366 if (!id) {
336 367 uni.showToast({ title: '缺少ID', icon: 'none' })
337 368 return
... ... @@ -415,6 +446,7 @@ export default {
415 446 &.status_AUDIT { background: $theme-primary; }
416 447 &.status_PASS { background: #2ba471; }
417 448 &.status_REFUSE { background: #d54941; }
  449 + &.status_DRAFT { background: #e7e7e7; color: rgba(0, 0, 0, 0.9); }
418 450 &.status_CANCEL { background: #e7e7e7; color: rgba(0, 0, 0, 0.9); }
419 451 }
420 452 }
... ... @@ -453,5 +485,15 @@ export default {
453 485 background: #fff;
454 486 box-sizing: border-box;
455 487 }
  488 + .fake-select {
  489 + height: 72rpx;
  490 + line-height: 72rpx;
  491 + padding: 0 24rpx;
  492 + border: 1rpx solid #e7e7e7;
  493 + border-radius: 8rpx;
  494 + font-size: 28rpx;
  495 + .placeholder { color: #999; }
  496 + .value { color: rgba(0, 0, 0, 0.9); }
  497 + }
456 498 }
457 499 </style>
... ...
... ... @@ -2,10 +2,10 @@
2 2 <view class="page">
3 3 <scroll-view class="scroll" scroll-y>
4 4 <uni-list>
5   - <!-- 编号 -->
  5 + <!-- 编号(只读) -->
6 6 <uni-list-item class="select-item" :class="form.code ? 'is-filled' : 'is-empty'">
7 7 <template v-slot:body>
8   - <view class="item-title"><text>编号</text></view>
  8 + <view class="item-title"><text class="required">*</text><text>编号</text></view>
9 9 </template>
10 10 <template v-slot:footer>
11 11 <text class="readonly-text value-code">{{ form.code || '-' }}</text>
... ... @@ -13,197 +13,121 @@
13 13 </uni-list-item>
14 14
15 15 <!-- 类型 -->
16   - <uni-list-item>
  16 + <uni-list-item class="select-item" :class="form.type ? 'is-filled' : 'is-empty'">
17 17 <template v-slot:body>
18 18 <view class="item-title"><text class="required">*</text><text>类型</text></view>
19 19 </template>
20 20 <template v-slot:footer>
21   - <uni-easyinput v-model="form.type" :inputBorder="false" placeholder="请输入类型" />
  21 + <picker mode="selector" :range="typeLabels" @change="onTypeChange">
  22 + <view class="picker-value" :class="{ placeholder: !form.type }">
  23 + {{ getTypeLabel() || '请选择类型' }}
  24 + </view>
  25 + </picker>
22 26 </template>
23 27 </uni-list-item>
24 28
25   - <!-- 计划编号 -->
26   - <uni-list-item>
  29 + <!-- 计划编号(弹窗多选计划) -->
  30 + <uni-list-item class="select-item" :class="form.planCode ? 'is-filled' : 'is-empty'" clickable
  31 + @click="openPlanSheet" :rightText="form.planCode || '请选择计划'" showArrow>
27 32 <template v-slot:body>
28 33 <view class="item-title"><text class="required">*</text><text>计划编号</text></view>
29 34 </template>
30   - <template v-slot:footer>
31   - <uni-easyinput v-model="form.unit" :inputBorder="false" placeholder="请输入计划编号" />
32   - </template>
33   - </uni-list-item>
34   -
35   - <!-- 时间 -->
36   - <uni-list-item class="select-item" :class="form.firstGrossWeightTime ? 'is-filled' : 'is-empty'">
37   - <template v-slot:body>
38   - <view class="item-title"><text class="required">*</text><text>时间</text></view>
39   - </template>
40   - <template v-slot:footer>
41   - <uni-datetime-picker type="date" v-model="form.firstGrossWeightTime" />
42   - </template>
43   - </uni-list-item>
44   -
45   - <!-- 供料单位 -->
46   - <uni-list-item>
47   - <template v-slot:body>
48   - <view class="item-title"><text class="required">*</text><text>供料单位</text></view>
49   - </template>
50   - <template v-slot:footer>
51   - <uni-easyinput v-model="form.supplierName" :inputBorder="false" placeholder="请输入供料单位" />
52   - </template>
53   - </uni-list-item>
54   -
55   - <!-- 收料单位 -->
56   - <uni-list-item>
57   - <template v-slot:body>
58   - <view class="item-title"><text class="required">*</text><text>收料单位</text></view>
59   - </template>
60   - <template v-slot:footer>
61   - <uni-easyinput v-model="form.companyShortName" :inputBorder="false" placeholder="请输入收料单位" />
62   - </template>
63   - </uni-list-item>
64   -
65   - <!-- 物料名称 -->
66   - <uni-list-item>
67   - <template v-slot:body>
68   - <view class="item-title"><text class="required">*</text><text>物料名称</text></view>
69   - </template>
70   - <template v-slot:footer>
71   - <uni-easyinput v-model="form.materialName" :inputBorder="false" placeholder="请输入物料名称" />
72   - </template>
73   - </uni-list-item>
74   -
75   - <!-- 过磅总重 -->
76   - <uni-list-item>
77   - <template v-slot:body>
78   - <view class="item-title"><text class="required">*</text><text>过磅总重</text></view>
79   - </template>
80   - <template v-slot:footer>
81   - <uni-easyinput v-model="form.totalGrossWeight" type="digit" :inputBorder="false" placeholder="请输入过磅总重" @input="calcNetWeight" />
82   - </template>
83   - </uni-list-item>
84   -
85   - <!-- 实际伙重 -->
86   - <uni-list-item title="实际伙重">
87   - <template v-slot:footer>
88   - <uni-easyinput v-model="form.actualImpurityWeight" type="digit" :inputBorder="false" placeholder="请输入实际伙重" @input="calcNetWeight" />
89   - </template>
90   - </uni-list-item>
91   -
92   - <!-- 货物净重 -->
93   - <uni-list-item title="货物净重">
94   - <template v-slot:footer>
95   - <uni-easyinput v-model="form.netWeight" type="digit" :inputBorder="false" placeholder="请输入货物净重" />
96   - </template>
97   - </uni-list-item>
98   -
99   - <!-- 净重大写 -->
100   - <uni-list-item title="净重大写">
101   - <template v-slot:footer>
102   - <uni-easyinput v-model="form.netWeightUpper" :inputBorder="false" placeholder="请输入净重大写" />
103   - </template>
104 35 </uni-list-item>
105 36
106   - <!-- 来料扣杂类型 -->
107   - <uni-list-item title="来料扣杂类型">
  37 + <!-- 单位(禁用) -->
  38 + <uni-list-item title="单位">
108 39 <template v-slot:footer>
109   - <uni-easyinput v-model="form.deductionTypes" :inputBorder="false" placeholder="请输入来料扣杂类型" />
  40 + <uni-easyinput v-model="form.unit" :inputBorder="false" disabled />
110 41 </template>
111 42 </uni-list-item>
112 43
113   - <!-- 供应存欠料 -->
114   - <uni-list-item title="供应存欠料">
  44 + <!-- 供料单位(带出,禁用) -->
  45 + <uni-list-item title="供料单位">
115 46 <template v-slot:footer>
116   - <uni-easyinput v-model="form.supplierStockMaterial" type="digit" :inputBorder="false" placeholder="请输入供应存欠料" />
  47 + <uni-easyinput v-model="form.supplierName" :inputBorder="false" disabled placeholder="选择计划后自动带出" />
117 48 </template>
118 49 </uni-list-item>
119 50
120   - <!-- 物流存欠料 -->
121   - <uni-list-item title="物流存欠料">
122   - <template v-slot:footer>
123   - <uni-easyinput v-model="form.logisticsStockMaterial" type="digit" :inputBorder="false" placeholder="请输入物流存欠料" />
124   - </template>
125   - </uni-list-item>
126   -
127   - <!-- 车牌号 -->
128   - <uni-list-item>
129   - <template v-slot:body>
130   - <view class="item-title"><text class="required">*</text><text>车牌号</text></view>
131   - </template>
  51 + <!-- 收料单位(带出,禁用) -->
  52 + <uni-list-item title="收料单位">
132 53 <template v-slot:footer>
133   - <uni-easyinput v-model="form.plateNumber" :inputBorder="false" placeholder="请输入车牌号" />
  54 + <uni-easyinput v-model="form.companyShortName" :inputBorder="false" disabled placeholder="选择计划后自动带出" />
134 55 </template>
135 56 </uni-list-item>
136 57 </uni-list>
137 58
138   - <!-- 计划单明细 -->
  59 + <!-- 计划单明细(只读) -->
139 60 <view class="sub-table">
140 61 <view class="header">
141 62 <image class="opCollapse" src="/static/images/title.png" />
142 63 <text class="title">计划单明细</text>
143   - <view class="ops">
144   - <image class="opAdd" @click="addPlan" src="/static/images/plus.png" />
145   - </view>
146 64 </view>
147 65 <view class="add-list">
148   - <view v-if="!planList.length" class="empty-tip">暂无明细,请添加</view>
149   - <view v-for="(item, idx) in planList" :key="'p-' + idx" class="block">
  66 + <view v-if="!planList.length" class="empty-tip">选择计划后自动带出</view>
  67 + <view v-for="(item, idx) in planList" :key="'p-' + (item.id || idx)" class="block">
150 68 <uni-list>
151 69 <uni-list-item title="计划品种">
152 70 <template v-slot:footer>
153   - <uni-easyinput v-model="item.productName" :inputBorder="false" placeholder="请输入计划品种" />
  71 + <uni-easyinput :value="item.productName" :inputBorder="false" disabled />
154 72 </template>
155 73 </uni-list-item>
156 74 <uni-list-item title="计划数量">
157 75 <template v-slot:footer>
158   - <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入计划数量" />
  76 + <uni-easyinput :value="item.quantity" :inputBorder="false" disabled />
159 77 </template>
160 78 </uni-list-item>
161 79 <uni-list-item title="计划编号">
162 80 <template v-slot:footer>
163   - <uni-easyinput v-model="item.planCode" :inputBorder="false" placeholder="请输入计划编号" />
  81 + <uni-easyinput :value="item.planCode" :inputBorder="false" disabled />
164 82 </template>
165 83 </uni-list-item>
166 84 </uni-list>
167   - <view class="block-ops">
168   - <div class="del" @click="removeRow('plan', idx)">
169   - <image src="/static/images/delete.png" class="icon" />删除
170   - </div>
171   - </view>
172 85 </view>
173 86 </view>
174 87 </view>
175 88
176   - <!-- 过磅明细 -->
  89 + <!-- 过磅明细(可编辑) -->
177 90 <view class="sub-table">
178 91 <view class="header">
179 92 <image class="opCollapse" src="/static/images/title.png" />
180 93 <text class="title">过磅明细</text>
181 94 <view class="ops">
182   - <image class="opAdd" @click="addWeigh" src="/static/images/plus.png" />
  95 + <image class="opAdd" @click="addWeightRow" src="/static/images/plus.png" />
183 96 </view>
184 97 </view>
185 98 <view class="add-list">
186 99 <view v-if="!weighList.length" class="empty-tip">暂无明细,请添加</view>
187   - <view v-for="(item, idx) in weighList" :key="'w-' + idx" class="block">
  100 + <view v-for="(item, idx) in weighList" :key="'w-' + (item.id || idx)" class="block">
188 101 <uni-list>
189   - <uni-list-item title="过磅品种">
  102 + <uni-list-item class="select-item" :class="item.productId ? 'is-filled' : 'is-empty'">
  103 + <template v-slot:body>
  104 + <view class="item-title"><text class="required">*</text><text>过磅品种</text></view>
  105 + </template>
190 106 <template v-slot:footer>
191   - <uni-easyinput v-model="item.productName" :inputBorder="false" placeholder="请输入过磅品种" />
  107 + <picker mode="selector" :range="weightProductLabels"
  108 + @change="(e) => handleWeightProductChange(idx, Number(e.detail.value))">
  109 + <view class="picker-value" :class="{ placeholder: !item.productId }">
  110 + {{ getWeightProductLabel(item) || '请选择过磅品种' }}
  111 + </view>
  112 + </picker>
192 113 </template>
193 114 </uni-list-item>
194 115 <uni-list-item title="开票品名">
195 116 <template v-slot:footer>
196   - <uni-easyinput v-model="item.invoiceProductName" :inputBorder="false" placeholder="请输入开票品名" />
  117 + <uni-easyinput v-model="item.invoiceProductName" :inputBorder="false" disabled />
197 118 </template>
198 119 </uni-list-item>
199 120 <uni-list-item title="过磅数量">
  121 + <template v-slot:body>
  122 + <view class="item-title"><text class="required">*</text><text>过磅数量</text></view>
  123 + </template>
200 124 <template v-slot:footer>
201 125 <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入过磅数量" />
202 126 </template>
203 127 </uni-list-item>
204 128 <uni-list-item title="计划编号">
205 129 <template v-slot:footer>
206   - <uni-easyinput v-model="item.planCode" :inputBorder="false" placeholder="请输入计划编号" />
  130 + <uni-easyinput v-model="item.planCode" :inputBorder="false" disabled />
207 131 </template>
208 132 </uni-list-item>
209 133 </uni-list>
... ... @@ -216,25 +140,36 @@
216 140 </view>
217 141 </view>
218 142
219   - <!-- 申请数量明细 -->
220   - <view class="sub-table">
  143 + <!-- 申请数量明细(仅买断展示) -->
  144 + <view v-if="isBuyout" class="sub-table">
221 145 <view class="header">
222 146 <image class="opCollapse" src="/static/images/title.png" />
223 147 <text class="title">申请数量明细</text>
224 148 <view class="ops">
225   - <image class="opAdd" @click="addApply" src="/static/images/plus.png" />
  149 + <image class="opAdd" @click="addApplyRow" src="/static/images/plus.png" />
226 150 </view>
227 151 </view>
228 152 <view class="add-list">
229 153 <view v-if="!applyList.length" class="empty-tip">暂无明细,请添加</view>
230   - <view v-for="(item, idx) in applyList" :key="'a-' + idx" class="block">
  154 + <view v-for="(item, idx) in applyList" :key="'a-' + (item.id || idx)" class="block">
231 155 <uni-list>
232   - <uni-list-item title="类别">
  156 + <uni-list-item class="select-item" :class="item.breedRelationshipId ? 'is-filled' : 'is-empty'">
  157 + <template v-slot:body>
  158 + <view class="item-title"><text class="required">*</text><text>类别</text></view>
  159 + </template>
233 160 <template v-slot:footer>
234   - <uni-easyinput v-model="item.category" :inputBorder="false" placeholder="请输入类别" />
  161 + <picker mode="selector" :range="varietyLabels"
  162 + @change="(e) => handleVarietyChange(idx, Number(e.detail.value))">
  163 + <view class="picker-value" :class="{ placeholder: !item.breedRelationshipId }">
  164 + {{ getVarietyLabel(item) || '请选择类别' }}
  165 + </view>
  166 + </picker>
235 167 </template>
236 168 </uni-list-item>
237 169 <uni-list-item title="数量">
  170 + <template v-slot:body>
  171 + <view class="item-title"><text class="required">*</text><text>数量</text></view>
  172 + </template>
238 173 <template v-slot:footer>
239 174 <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" />
240 175 </template>
... ... @@ -249,6 +184,88 @@
249 184 </view>
250 185 </view>
251 186
  187 + <!-- 其他信息 -->
  188 + <view class="remark-section">
  189 + <view class="header">
  190 + <image class="opCollapse" src="/static/images/title.png" />
  191 + <text class="title">其他信息</text>
  192 + </view>
  193 + <view class="section-content">
  194 + <uni-list>
  195 + <uni-list-item title="物料名称">
  196 + <template v-slot:footer>
  197 + <uni-easyinput :value="materialName" :inputBorder="false" disabled />
  198 + </template>
  199 + </uni-list-item>
  200 + <uni-list-item>
  201 + <template v-slot:body>
  202 + <view class="item-title"><text class="required">*</text><text>过磅总重</text></view>
  203 + </template>
  204 + <template v-slot:footer>
  205 + <uni-easyinput v-model="form.totalGrossWeight" type="digit" :inputBorder="false" placeholder="请输入过磅总重" />
  206 + </template>
  207 + </uni-list-item>
  208 + <uni-list-item>
  209 + <template v-slot:body>
  210 + <view class="item-title"><text class="required">*</text><text>实际伙重</text></view>
  211 + </template>
  212 + <template v-slot:footer>
  213 + <uni-easyinput v-model="form.actualImpurityWeight" type="digit" :inputBorder="false" placeholder="请输入实际伙重" />
  214 + </template>
  215 + </uni-list-item>
  216 + <uni-list-item title="货物净重">
  217 + <template v-slot:footer>
  218 + <uni-easyinput :value="computedNetWeight" :inputBorder="false" disabled />
  219 + </template>
  220 + </uni-list-item>
  221 + <uni-list-item title="净重大写">
  222 + <template v-slot:footer>
  223 + <uni-easyinput :value="computedNetWeightUpper" :inputBorder="false" disabled />
  224 + </template>
  225 + </uni-list-item>
  226 + </uni-list>
  227 +
  228 + <view class="form-group">
  229 + <view class="item-title"><text class="required">*</text><text>来料扣杂类型</text></view>
  230 + <uni-data-checkbox
  231 + mode="tag"
  232 + :multiple="true"
  233 + :value-field="'value'"
  234 + :text-field="'text'"
  235 + v-model="form.deductionTypes"
  236 + :localdata="deductionTypeOptions"
  237 + />
  238 + </view>
  239 +
  240 + <uni-list>
  241 + <uni-list-item>
  242 + <template v-slot:body>
  243 + <view class="item-title"><text class="required">*</text><text>供应存欠料</text></view>
  244 + </template>
  245 + <template v-slot:footer>
  246 + <uni-easyinput v-model="form.supplierStockMaterial" :inputBorder="false" placeholder="请输入供应存欠料" />
  247 + </template>
  248 + </uni-list-item>
  249 + <uni-list-item>
  250 + <template v-slot:body>
  251 + <view class="item-title"><text class="required">*</text><text>物流存欠料</text></view>
  252 + </template>
  253 + <template v-slot:footer>
  254 + <uni-easyinput v-model="form.logisticsStockMaterial" :inputBorder="false" placeholder="请输入物流存欠料" />
  255 + </template>
  256 + </uni-list-item>
  257 + <uni-list-item>
  258 + <template v-slot:body>
  259 + <view class="item-title"><text class="required">*</text><text>车牌号</text></view>
  260 + </template>
  261 + <template v-slot:footer>
  262 + <uni-easyinput v-model="form.plateNumber" :inputBorder="false" placeholder="请输入车牌号" />
  263 + </template>
  264 + </uni-list-item>
  265 + </uni-list>
  266 + </view>
  267 + </view>
  268 +
252 269 <!-- 备注模块 -->
253 270 <view class="remark-section">
254 271 <view class="header">
... ... @@ -257,8 +274,8 @@
257 274 </view>
258 275 <view class="section-content">
259 276 <view class="remark-group">
260   - <view class="remark-label"><text class="required">*</text><text>业务摘要</text></view>
261   - <uni-easyinput v-model="form.businessSummary" type="textarea" :inputBorder="false" placeholder="请输入业务摘要" maxlength="2000" />
  277 + <view class="remark-label">业务摘要</view>
  278 + <uni-easyinput :value="computedBusinessSummary" type="textarea" :inputBorder="false" disabled autoHeight />
262 279 </view>
263 280 <view class="remark-group">
264 281 <view class="remark-label">特别说明</view>
... ... @@ -266,7 +283,7 @@
266 283 </view>
267 284 <view class="remark-group">
268 285 <view class="remark-label">单据备注</view>
269   - <uni-easyinput v-model="form.remark" type="textarea" :inputBorder="false" placeholder="请输入单据备注" maxlength="2000" />
  286 + <uni-easyinput :value="computedDocumentRemark" type="textarea" :inputBorder="false" disabled autoHeight />
270 287 </view>
271 288 </view>
272 289 </view>
... ... @@ -275,47 +292,171 @@
275 292 <button class="btn submit" type="primary" @click="onSubmit">保存</button>
276 293 </view>
277 294 </scroll-view>
  295 +
  296 + <RelateSelectSheet :visible.sync="planSheet.visible" title="选择计划" :multiple="true"
  297 + :fetchFn="planSheet.fetchFn" :display-fields="planSheet.displayFields"
  298 + row-key="id" :selectedKeys.sync="planSheet.selectedKeys" @confirm="onPlanConfirm" />
278 299 </view>
279 300 </template>
280 301
281 302 <script>
282   -import { weighTicketDetailApi, weighTicketUpdateApi } from '@/api/procure-manage/weighTicket.js'
  303 +import RelateSelectSheet from '@/components/relate-select/index.vue'
  304 +import {
  305 + weighingTicketPlansApi,
  306 + weighTicketPlanLinesApi,
  307 + weighTicketDetailApi,
  308 + weighTicketUpdateApi
  309 +} from '@/api/procure-manage/weighTicket.js'
  310 +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js'
  311 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
283 312
284 313 export default {
285 314 name: 'ModifyWeightTicket',
  315 + components: { RelateSelectSheet },
286 316 data() {
287 317 return {
288 318 id: '',
289 319 form: {
290 320 code: '',
291 321 type: '',
292   - unit: '',
293   - firstGrossWeightTime: '',
  322 + planCode: '',
  323 + unit: '公斤',
294 324 supplierName: '',
295 325 companyShortName: '',
296   - materialName: '',
297 326 totalGrossWeight: '',
298 327 actualImpurityWeight: '',
299   - netWeight: '',
300   - netWeightUpper: '',
301   - deductionTypes: '',
  328 + deductionTypes: [],
302 329 supplierStockMaterial: '',
303 330 logisticsStockMaterial: '',
304 331 plateNumber: '',
305   - businessSummary: '',
306   - specialNote: '',
307   - remark: ''
  332 + specialNote: ''
308 333 },
  334 + dicOptions: { TYPE: [], DEDUCTION_TYPE: [] },
  335 + // 选中计划行(多选)
  336 + selectedPlanRows: [],
  337 + // 计划单明细(只读)
309 338 planList: [],
  339 + // 过磅明细(可编辑)
310 340 weighList: [],
311   - applyList: []
  341 + // 申请数量明细(买断时)
  342 + applyList: [],
  343 + varietyOptions: [],
  344 + planSheet: {
  345 + visible: false,
  346 + fetchFn: null,
  347 + displayFields: [
  348 + { label: '计划编号', field: 'code' },
  349 + { label: '是否买断', field: 'buyout', format: v => (v ? '是' : '否') },
  350 + { label: '收料单位', field: 'companyName' },
  351 + { label: '供料单位', field: 'supplierName' },
  352 + { label: '总数量', field: 'totalQuantity' },
  353 + { label: '创建人', field: 'createBy' }
  354 + ],
  355 + selectedKeys: []
  356 + }
  357 + }
  358 + },
  359 + computed: {
  360 + typeLabels() {
  361 + return (this.dicOptions.TYPE || []).map(it => it.name)
  362 + },
  363 + deductionTypeOptions() {
  364 + return (this.dicOptions.DEDUCTION_TYPE || []).map(it => ({
  365 + value: it.code != null ? String(it.code) : '',
  366 + text: it.name || ''
  367 + })).filter(it => it.value)
  368 + },
  369 + isBuyout() {
  370 + const first = (this.selectedPlanRows || [])[0]
  371 + return !!(first && first.buyout)
  372 + },
  373 + // 过磅品种下拉:数据来自计划单明细
  374 + weightProductLabels() {
  375 + return (this.planList || []).map(item =>
  376 + [item.productName, item.planCode].filter(Boolean).join(' ')
  377 + )
  378 + },
  379 + varietyLabels() {
  380 + return (this.varietyOptions || []).map(item =>
  381 + [item.productName, item.code].filter(Boolean).join(' ')
  382 + )
  383 + },
  384 + // 物料名称:过磅明细开票品名去重顿号拼接
  385 + materialName() {
  386 + const names = (this.weighList || [])
  387 + .map(item => item.invoiceProductName)
  388 + .filter(name => !!name)
  389 + return Array.from(new Set(names)).join('、')
  390 + },
  391 + // 货物净重 = 过磅总重 - 实际伙重(保留两位)
  392 + computedNetWeight() {
  393 + const gross = this.form.totalGrossWeight
  394 + if (gross === undefined || gross === null || gross === '') return ''
  395 + const num = Number(gross) - Number(this.form.actualImpurityWeight || 0)
  396 + return String(Math.round(num * 100) / 100)
  397 + },
  398 + // 净重大写:中文大写
  399 + computedNetWeightUpper() {
  400 + const net = this.computedNetWeight
  401 + if (net === '' || net === null || net === undefined) return ''
  402 + return numberToChineseUpper(net)
  403 + },
  404 + // 业务摘要
  405 + computedBusinessSummary() {
  406 + const parts = []
  407 + const weightItems = (this.weighList || [])
  408 + .map(item => {
  409 + const name = item.productName || ''
  410 + const qty = item.quantity === null || item.quantity === undefined ? '' : item.quantity
  411 + if (!name && qty === '') return ''
  412 + return `${name}${qty}公斤`
  413 + })
  414 + .filter(text => !!text)
  415 + if (weightItems.length) parts.push(weightItems.join('、'))
  416 + const deductionNames = (this.form.deductionTypes || [])
  417 + .map(code => getDicName('DEDUCTION_TYPE', code, this.dicOptions.DEDUCTION_TYPE))
  418 + .filter(name => !!name)
  419 + if (deductionNames.length) parts.push(deductionNames.join('、'))
  420 + if (this.form.supplierStockMaterial) parts.push(`供应存欠料:${this.form.supplierStockMaterial}`)
  421 + if (this.form.logisticsStockMaterial) parts.push(`物流存欠料:${this.form.logisticsStockMaterial}`)
  422 + if (this.form.plateNumber) parts.push(`车牌号:${this.form.plateNumber}`)
  423 + return parts.join(',')
  424 + },
  425 + // 单据备注 = 业务摘要 + 特别说明
  426 + computedDocumentRemark() {
  427 + const summary = this.computedBusinessSummary || ''
  428 + const note = this.form.specialNote || ''
  429 + return [summary, note].filter(text => !!text).join('\n')
312 430 }
313 431 },
314 432 onLoad(query) {
315 433 this.id = (query && query.id) || ''
  434 + },
  435 + created() {
  436 + this.planSheet.fetchFn = this.fetchPlans.bind(this)
  437 + this.loadDicData()
  438 + this.loadVarietyOptions()
316 439 this.loadDetail()
317 440 },
318 441 methods: {
  442 + async loadDicData() {
  443 + try {
  444 + const results = await getDicByCodes(['TYPE', 'DEDUCTION_TYPE'])
  445 + this.dicOptions.TYPE = (results.TYPE && results.TYPE.data) || []
  446 + this.dicOptions.DEDUCTION_TYPE = (results.DEDUCTION_TYPE && results.DEDUCTION_TYPE.data) || []
  447 + } catch (e) {
  448 + this.dicOptions = { TYPE: [], DEDUCTION_TYPE: [] }
  449 + }
  450 + },
  451 + async loadVarietyOptions() {
  452 + try {
  453 + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 })
  454 + const _data = (res && res.data) || {}
  455 + this.varietyOptions = _data.datas || _data.list || _data.records || []
  456 + } catch (e) {
  457 + this.varietyOptions = []
  458 + }
  459 + },
319 460 loadDetail() {
320 461 if (!this.id) {
321 462 uni.showToast({ title: '缺少单据ID', icon: 'none' })
... ... @@ -325,10 +466,34 @@ export default {
325 466 weighTicketDetailApi({ id: this.id })
326 467 .then(res => {
327 468 const data = res && res.data ? (res.data.data || res.data) : {}
328   - this.form = Object.assign({}, this.form, data)
329   - this.planList = this.pickList(data, ['planDetailList', 'planLineList', 'procurementPlanLineList'])
330   - this.weighList = this.pickList(data, ['weighDetailList', 'weighLineList', 'grossWeightList'])
331   - this.applyList = this.pickList(data, ['applyQuantityList', 'applyDetailList', 'applyLineList'])
  469 + const buyout = !!data.buyout
  470 + this.form = {
  471 + code: data.code || '',
  472 + type: data.type != null ? String(data.type) : '',
  473 + planCode: data.planCode || data.planCodes || '',
  474 + unit: data.unit || '',
  475 + supplierName: data.supplierName || '',
  476 + companyShortName: data.companyShortName || '',
  477 + totalGrossWeight: data.totalGrossWeight == null ? '' : String(data.totalGrossWeight),
  478 + actualImpurityWeight: data.actualImpurityWeight == null ? '' : String(data.actualImpurityWeight),
  479 + deductionTypes: data.deductionTypes ? String(data.deductionTypes).split(',').filter(Boolean) : [],
  480 + supplierStockMaterial: data.supplierStockMaterial || '',
  481 + logisticsStockMaterial: data.logisticsStockMaterial || '',
  482 + plateNumber: data.plateNumber || '',
  483 + specialNote: data.specialNote || ''
  484 + }
  485 + // 计划选择回显:行对象缓存供提交 planIds 使用(id 取计划明细行上的计划 id)
  486 + this.planList = (Array.isArray(data.planLines) ? data.planLines : []).map(it => ({ ...it }))
  487 + this.selectedPlanRows = this.planList.map(item => ({
  488 + id: item.procurementPlanId || item.id,
  489 + code: item.planCode || '',
  490 + buyout: buyout,
  491 + supplierName: data.supplierName || '',
  492 + companyName: data.companyShortName || '',
  493 + workshopName: ''
  494 + }))
  495 + this.weighList = (Array.isArray(data.weighLines) ? data.weighLines : []).map(it => ({ ...it, id: it.id || genRowId() }))
  496 + this.applyList = (Array.isArray(data.applyLines) ? data.applyLines : []).map(it => ({ ...it, id: it.id || genRowId() }))
332 497 })
333 498 .catch(() => {
334 499 uni.showToast({ title: '加载详情失败', icon: 'none' })
... ... @@ -337,77 +502,195 @@ export default {
337 502 uni.hideLoading()
338 503 })
339 504 },
340   - pickList(data, keys) {
341   - if (!data) return []
342   - for (const k of keys) {
343   - if (Array.isArray(data[k])) return data[k].map(x => Object.assign({}, x))
  505 + getTypeLabel() {
  506 + const found = (this.dicOptions.TYPE || []).find(it => String(it.code) === String(this.form.type))
  507 + return found ? found.name : ''
  508 + },
  509 + onTypeChange(e) {
  510 + const idx = Number(e.detail.value)
  511 + const opt = (this.dicOptions.TYPE || [])[idx]
  512 + this.form.type = opt && opt.code != null ? String(opt.code) : ''
  513 + },
  514 + // 计划选择弹窗数据源
  515 + fetchPlans({ pageIndex, pageSize, extra }) {
  516 + const keyword = (extra && extra.keyword) || ''
  517 + const params = {
  518 + pageIndex,
  519 + pageSize,
  520 + code: keyword || undefined
344 521 }
345   - return []
  522 + return weighingTicketPlansApi(params).then(res => {
  523 + const _data = (res && res.data) || {}
  524 + const records = _data.datas || _data.list || _data.records || []
  525 + const totalCount = _data.totalCount || _data.count || 0
  526 + const hasNext = _data.hasNext || false
  527 + return { records, totalCount, hasNext }
  528 + }).catch(() => ({ records: [], totalCount: 0, hasNext: false }))
346 529 },
347   - calcNetWeight() {
348   - const g = Number(this.form.totalGrossWeight)
349   - const i = Number(this.form.actualImpurityWeight || 0)
350   - if (!isNaN(g) && g > 0) {
351   - const net = g - (isNaN(i) ? 0 : i)
352   - this.form.netWeight = String(Math.round(net * 10000) / 10000)
  530 + openPlanSheet() {
  531 + this.planSheet.selectedKeys = this.selectedPlanRows.map(it => it.id)
  532 + this.planSheet.visible = true
  533 + },
  534 + onPlanConfirm({ items }) {
  535 + const list = items || []
  536 + if (!list.length) {
  537 + this.selectedPlanRows = []
  538 + this.form.planCode = ''
  539 + this.form.supplierName = ''
  540 + this.form.companyShortName = ''
  541 + this.planList = []
  542 + this.weighList = []
  543 + return
353 544 }
  545 + // 校验:所选计划的「是否买断、供料单位、收料单位、车间」必须一致
  546 + const first = list[0]
  547 + const isSame = list.every(item =>
  548 + item.buyout === first.buyout &&
  549 + item.supplierName === first.supplierName &&
  550 + item.companyName === first.companyName &&
  551 + item.workshopName === first.workshopName
  552 + )
  553 + if (!isSame) {
  554 + uni.showToast({ title: '是否买断,供料单位,收料单位应保持一致', icon: 'none' })
  555 + this.planSheet.selectedKeys = this.selectedPlanRows.map(it => it.id)
  556 + return
  557 + }
  558 + this.selectedPlanRows = list.map(it => ({ ...it }))
  559 + this.form.planCode = list.map(it => it.code).filter(Boolean).join(',')
  560 + this.form.supplierName = first.supplierName || ''
  561 + this.form.companyShortName = (first.companyName || '') + (first.workshopName || '')
  562 + // 查询计划明细行,填充计划单明细;过磅明细初始化为计划明细拷贝
  563 + weighTicketPlanLinesApi(list.map(it => it.id)).then(res => {
  564 + const lines = (res && res.data) || []
  565 + this.planList = lines.map(it => ({ ...it }))
  566 + this.weighList = lines.map(it => ({ ...it, id: it.id || genRowId() }))
  567 + }).catch(() => {
  568 + this.planList = []
  569 + this.weighList = []
  570 + })
354 571 },
355   - addPlan() {
356   - this.planList.push({ productName: '', quantity: '', planCode: '' })
  572 + getWeightProductLabel(item) {
  573 + if (!item.productId) return ''
  574 + const found = (this.planList || []).find(it => it.productId === item.productId)
  575 + return found ? [found.productName, found.planCode].filter(Boolean).join(' ') : ''
357 576 },
358   - addWeigh() {
359   - this.weighList.push({ productName: '', invoiceProductName: '', quantity: '', planCode: '' })
  577 + // 过磅品种改选:整行替换为选中计划明细数据
  578 + handleWeightProductChange(idx, optIdx) {
  579 + const raw = (this.planList || [])[optIdx]
  580 + if (!raw) return
  581 + const rowId = this.weighList[idx].id
  582 + this.weighList.splice(idx, 1, { ...raw, id: rowId })
360 583 },
361   - addApply() {
362   - this.applyList.push({ category: '', quantity: '' })
  584 + getVarietyLabel(item) {
  585 + if (!item.breedRelationshipId) return ''
  586 + const found = (this.varietyOptions || []).find(it => it.id === item.breedRelationshipId)
  587 + return found ? [found.productName, found.code].filter(Boolean).join(' ') : ''
  588 + },
  589 + handleVarietyChange(idx, optIdx) {
  590 + const raw = (this.varietyOptions || [])[optIdx]
  591 + if (!raw) return
  592 + const row = this.applyList[idx]
  593 + row.category = raw.code || ''
  594 + row.breedRelationshipId = raw.id || ''
  595 + },
  596 + addWeightRow() {
  597 + this.weighList.push({
  598 + id: genRowId(),
  599 + procurementPlanId: '',
  600 + planCode: '',
  601 + productId: '',
  602 + invoiceProductName: '',
  603 + quantity: ''
  604 + })
  605 + },
  606 + addApplyRow() {
  607 + this.applyList.push({
  608 + id: genRowId(),
  609 + category: '',
  610 + breedRelationshipId: '',
  611 + quantity: ''
  612 + })
363 613 },
364 614 removeRow(type, idx) {
365   - if (type === 'plan') this.planList.splice(idx, 1)
366   - else if (type === 'weigh') this.weighList.splice(idx, 1)
  615 + if (type === 'weigh') this.weighList.splice(idx, 1)
367 616 else if (type === 'apply') this.applyList.splice(idx, 1)
368 617 },
369 618 validate() {
370 619 const f = this.form
371   - if (!f.type) return this.tip('请输入类型')
372   - if (!f.unit) return this.tip('请输入计划编号')
373   - if (!f.firstGrossWeightTime) return this.tip('请选择时间')
374   - if (!f.supplierName) return this.tip('请输入供料单位')
375   - if (!f.companyShortName) return this.tip('请输入收料单位')
376   - if (!f.materialName) return this.tip('请输入物料名称')
377   - if (!f.totalGrossWeight) return this.tip('请输入过磅总重')
  620 + if (!f.code) return this.tip('缺少编号')
  621 + if (!f.type) return this.tip('请选择类型')
  622 + if (!this.selectedPlanRows.length) return this.tip('请选择计划编号')
  623 + if (f.totalGrossWeight === '' || f.totalGrossWeight == null) return this.tip('请输入过磅总重')
  624 + if (f.actualImpurityWeight === '' || f.actualImpurityWeight == null) return this.tip('请输入实际伙重')
  625 + if (!f.deductionTypes || !f.deductionTypes.length) return this.tip('请选择来料扣杂类型')
  626 + if (!f.supplierStockMaterial) return this.tip('请输入供应存欠料')
  627 + if (!f.logisticsStockMaterial) return this.tip('请输入物流存欠料')
378 628 if (!f.plateNumber) return this.tip('请输入车牌号')
379   - if (!f.businessSummary) return this.tip('请输入业务摘要')
  629 + // 过磅明细:品种、数量必填
  630 + const weightList = this.weighList || []
  631 + for (let i = 0; i < weightList.length; i++) {
  632 + const row = weightList[i]
  633 + if (!row.productId) return this.tip(`过磅明细第${i + 1}行:请选择过磅品种`)
  634 + if (row.quantity === '' || row.quantity == null) return this.tip(`过磅明细第${i + 1}行:请输入过磅数量`)
  635 + }
  636 + // 申请数量明细:类别、数量必填(买断时)
  637 + if (this.isBuyout) {
  638 + const applyList = this.applyList || []
  639 + for (let i = 0; i < applyList.length; i++) {
  640 + const row = applyList[i]
  641 + if (!row.breedRelationshipId) return this.tip(`申请数量明细第${i + 1}行:请选择类别`)
  642 + if (row.quantity === '' || row.quantity == null) return this.tip(`申请数量明细第${i + 1}行:请输入数量`)
  643 + }
  644 + }
380 645 return true
381 646 },
382 647 tip(msg) {
383 648 uni.showToast({ title: msg, icon: 'none' })
384 649 return false
385 650 },
  651 + buildSubmitData() {
  652 + return {
  653 + id: this.id,
  654 + code: this.form.code,
  655 + type: this.form.type,
  656 + planIds: this.selectedPlanRows.map(it => it.id),
  657 + weighLines: (this.weighList || []).map(row => ({
  658 + procurementPlanId: row.procurementPlanId || '',
  659 + planCode: row.planCode || '',
  660 + productId: row.productId || '',
  661 + invoiceProductName: row.invoiceProductName || '',
  662 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  663 + itemId: row.id
  664 + })),
  665 + applyLines: (this.applyList || []).map(row => ({
  666 + category: row.category || '',
  667 + breedRelationshipId: row.breedRelationshipId || '',
  668 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  669 + itemId: row.breedRelationshipId
  670 + })),
  671 + unit: this.form.unit,
  672 + materialName: this.materialName || null,
  673 + totalGrossWeight: this.form.totalGrossWeight === '' ? null : Number(this.form.totalGrossWeight),
  674 + actualImpurityWeight: this.form.actualImpurityWeight === '' ? null : Number(this.form.actualImpurityWeight),
  675 + netWeight: this.computedNetWeight === '' ? null : Number(this.computedNetWeight),
  676 + netWeightUpper: this.computedNetWeightUpper || null,
  677 + deductionTypes: (this.form.deductionTypes || []).join(',') || null,
  678 + supplierStockMaterial: this.form.supplierStockMaterial || null,
  679 + logisticsStockMaterial: this.form.logisticsStockMaterial || null,
  680 + plateNumber: this.form.plateNumber || null,
  681 + businessSummary: this.computedBusinessSummary || null,
  682 + specialNote: this.form.specialNote || null,
  683 + documentRemark: this.computedDocumentRemark || null
  684 + }
  685 + },
386 686 async onSubmit() {
387 687 if (!this.validate()) return
388 688 const confirmRes = await new Promise(resolve => {
389 689 uni.showModal({ title: '提示', content: '确定保存修改吗?', confirmText: '确定', cancelText: '取消', success: resolve })
390 690 })
391 691 if (!(confirmRes && confirmRes.confirm)) return
392   - const clean = (obj) => {
393   - const out = {}
394   - Object.keys(obj || {}).forEach(k => {
395   - const v = obj[k]
396   - const isEmptyString = typeof v === 'string' && v.trim() === ''
397   - const isUndef = v === undefined || v === null
398   - if (!(isEmptyString || isUndef)) out[k] = v
399   - })
400   - return out
401   - }
402   - const payload = clean({
403   - ...this.form,
404   - id: this.id,
405   - planDetailList: this.planList.map(it => clean(it)),
406   - weighDetailList: this.weighList.map(it => clean(it)),
407   - applyQuantityList: this.applyList.map(it => clean(it))
408   - })
409 692 uni.showLoading({ title: '保存中...', mask: true })
410   - weighTicketUpdateApi(payload)
  693 + weighTicketUpdateApi(this.buildSubmitData())
411 694 .then(() => {
412 695 uni.hideLoading()
413 696 uni.showToast({ title: '保存成功', icon: 'none' })
... ... @@ -424,6 +707,67 @@ export default {
424 707 }
425 708 }
426 709 }
  710 +
  711 +// 生成前端临时行 id
  712 +function genRowId() {
  713 + return 'row_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8)
  714 +}
  715 +
  716 +// 数字转中文大写(对齐 PC 端 useWeightTicketForm.ts)
  717 +function numberToChineseUpper(num) {
  718 + if (num === null || num === undefined || num === '' || isNaN(Number(num))) return '零'
  719 + const n = Number(num)
  720 + if (!isFinite(n)) return '零'
  721 +
  722 + const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
  723 + const smallUnits = ['', '拾', '佰', '仟']
  724 + const bigUnits = ['', '万', '亿', '兆']
  725 +
  726 + const negative = n < 0
  727 + const abs = Math.abs(n)
  728 + const parts = abs.toString().split('.')
  729 + const intStrRaw = parts[0]
  730 + const decStrRaw = parts[1] || ''
  731 + const s = intStrRaw
  732 + const len = s.length
  733 +
  734 + let intPart = ''
  735 + let zeroFlag = false
  736 + for (let i = 0; i < len; i++) {
  737 + const d = parseInt(s[i], 10)
  738 + const pos = len - 1 - i
  739 + const unitIdx = pos % 4
  740 + const groupIdx = Math.floor(pos / 4)
  741 + if (d !== 0) {
  742 + if (zeroFlag) {
  743 + intPart += digit[0]
  744 + zeroFlag = false
  745 + }
  746 + intPart += digit[d] + smallUnits[unitIdx]
  747 + } else {
  748 + zeroFlag = true
  749 + }
  750 + if (unitIdx === 0) {
  751 + const start = Math.max(0, len - (groupIdx + 1) * 4)
  752 + const end = len - groupIdx * 4
  753 + const groupDigits = s.slice(start, end)
  754 + if (!/^0+$/.test(groupDigits)) {
  755 + intPart += bigUnits[groupIdx] || ''
  756 + }
  757 + }
  758 + }
  759 + if (intPart === '') intPart = digit[0]
  760 +
  761 + let result = intPart
  762 + if (decStrRaw) {
  763 + const decChars = decStrRaw
  764 + .split('')
  765 + .map(c => digit[parseInt(c, 10)])
  766 + .join('')
  767 + result += '点' + decChars
  768 + }
  769 + return (negative ? '负' : '') + result
  770 +}
427 771 </script>
428 772
429 773 <style lang="scss" scoped>
... ... @@ -584,6 +928,18 @@ export default {
584 928 text-align: left;
585 929 }
586 930
  931 +.picker-value {
  932 + font-size: 32rpx;
  933 + color: rgba(0, 0, 0, 0.9);
  934 + line-height: 48rpx;
  935 + text-align: right;
  936 + min-height: 48rpx;
  937 +
  938 + &.placeholder {
  939 + color: rgba(0, 0, 0, 0.4);
  940 + }
  941 +}
  942 +
587 943 .sub-table,
588 944 .remark-section {
589 945 margin-top: 20rpx;
... ... @@ -671,19 +1027,15 @@ export default {
671 1027 padding: 32rpx;
672 1028 }
673 1029
674   -.remark-group {
675   - margin-bottom: 32rpx;
  1030 +.form-group {
  1031 + margin: 24rpx 0 32rpx;
676 1032
677   - &:last-child {
678   - margin-bottom: 0;
679   - }
680   -
681   - .remark-label {
  1033 + .item-title {
682 1034 display: flex;
683 1035 align-items: center;
684 1036 font-size: 32rpx;
685 1037 color: rgba(0, 0, 0, 0.9);
686   - margin-bottom: 16rpx;
  1038 + margin-bottom: 20rpx;
687 1039 position: relative;
688 1040 padding-left: 16rpx;
689 1041
... ... @@ -694,4 +1046,20 @@ export default {
694 1046 }
695 1047 }
696 1048 }
  1049 +
  1050 +.remark-group {
  1051 + margin-bottom: 32rpx;
  1052 +
  1053 + &:last-child {
  1054 + margin-bottom: 0;
  1055 + }
  1056 +
  1057 + .remark-label {
  1058 + display: flex;
  1059 + align-items: center;
  1060 + font-size: 32rpx;
  1061 + color: rgba(0, 0, 0, 0.9);
  1062 + margin-bottom: 16rpx;
  1063 + }
  1064 +}
697 1065 </style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <!-- 编号(F+原编号,只读) -->
  6 + <uni-list-item class="select-item" :class="form.code ? 'is-filled' : 'is-empty'">
  7 + <template v-slot:body>
  8 + <view class="item-title"><text class="required">*</text><text>编号</text></view>
  9 + </template>
  10 + <template v-slot:footer>
  11 + <text class="readonly-text value-code">{{ form.code || '-' }}</text>
  12 + </template>
  13 + </uni-list-item>
  14 +
  15 + <!-- 类型(只读) -->
  16 + <uni-list-item title="类型">
  17 + <template v-slot:footer>
  18 + <uni-easyinput :value="getTypeLabel() || form.type" :inputBorder="false" disabled />
  19 + </template>
  20 + </uni-list-item>
  21 +
  22 + <!-- 计划编号(只读) -->
  23 + <uni-list-item title="计划编号">
  24 + <template v-slot:footer>
  25 + <uni-easyinput :value="planCodeText" :inputBorder="false" disabled />
  26 + </template>
  27 + </uni-list-item>
  28 +
  29 + <!-- 单位(只读) -->
  30 + <uni-list-item title="单位">
  31 + <template v-slot:footer>
  32 + <uni-easyinput :value="form.unit" :inputBorder="false" disabled />
  33 + </template>
  34 + </uni-list-item>
  35 +
  36 + <!-- 供料单位(只读) -->
  37 + <uni-list-item title="供料单位">
  38 + <template v-slot:footer>
  39 + <uni-easyinput :value="form.supplierName" :inputBorder="false" disabled />
  40 + </template>
  41 + </uni-list-item>
  42 +
  43 + <!-- 收料单位(只读) -->
  44 + <uni-list-item title="收料单位">
  45 + <template v-slot:footer>
  46 + <uni-easyinput :value="form.companyShortName" :inputBorder="false" disabled />
  47 + </template>
  48 + </uni-list-item>
  49 + </uni-list>
  50 +
  51 + <!-- 计划单明细(只读) -->
  52 + <view class="sub-table">
  53 + <view class="header">
  54 + <image class="opCollapse" src="/static/images/title.png" />
  55 + <text class="title">计划单明细</text>
  56 + </view>
  57 + <view class="add-list">
  58 + <view v-if="!planList.length" class="empty-tip">暂无明细</view>
  59 + <view v-for="(item, idx) in planList" :key="'p-' + (item.id || idx)" class="block">
  60 + <uni-list>
  61 + <uni-list-item title="计划品种">
  62 + <template v-slot:footer>
  63 + <uni-easyinput :value="item.productName" :inputBorder="false" disabled />
  64 + </template>
  65 + </uni-list-item>
  66 + <uni-list-item title="计划数量">
  67 + <template v-slot:footer>
  68 + <uni-easyinput :value="item.quantity" :inputBorder="false" disabled />
  69 + </template>
  70 + </uni-list-item>
  71 + <uni-list-item title="计划编号">
  72 + <template v-slot:footer>
  73 + <uni-easyinput :value="item.planCode" :inputBorder="false" disabled />
  74 + </template>
  75 + </uni-list-item>
  76 + </uni-list>
  77 + </view>
  78 + </view>
  79 + </view>
  80 +
  81 + <!-- 过磅明细(仅过磅数量可编辑) -->
  82 + <view class="sub-table">
  83 + <view class="header">
  84 + <image class="opCollapse" src="/static/images/title.png" />
  85 + <text class="title">过磅明细</text>
  86 + </view>
  87 + <view class="add-list">
  88 + <view v-if="!weighList.length" class="empty-tip">暂无明细</view>
  89 + <view v-for="(item, idx) in weighList" :key="'w-' + (item.id || idx)" class="block">
  90 + <uni-list>
  91 + <uni-list-item title="过磅品种">
  92 + <template v-slot:footer>
  93 + <uni-easyinput :value="item.productName" :inputBorder="false" disabled />
  94 + </template>
  95 + </uni-list-item>
  96 + <uni-list-item title="开票品名">
  97 + <template v-slot:footer>
  98 + <uni-easyinput :value="item.invoiceProductName" :inputBorder="false" disabled />
  99 + </template>
  100 + </uni-list-item>
  101 + <uni-list-item title="过磅数量">
  102 + <template v-slot:body>
  103 + <view class="item-title"><text class="required">*</text><text>过磅数量</text></view>
  104 + </template>
  105 + <template v-slot:footer>
  106 + <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入过磅数量" />
  107 + </template>
  108 + </uni-list-item>
  109 + <uni-list-item title="计划编号">
  110 + <template v-slot:footer>
  111 + <uni-easyinput :value="item.planCode" :inputBorder="false" disabled />
  112 + </template>
  113 + </uni-list-item>
  114 + </uni-list>
  115 + </view>
  116 + </view>
  117 + </view>
  118 +
  119 + <!-- 申请数量明细(仅买断展示,只读) -->
  120 + <view v-if="isBuyout" class="sub-table">
  121 + <view class="header">
  122 + <image class="opCollapse" src="/static/images/title.png" />
  123 + <text class="title">申请数量明细</text>
  124 + </view>
  125 + <view class="add-list">
  126 + <view v-if="!applyList.length" class="empty-tip">暂无明细</view>
  127 + <view v-for="(item, idx) in applyList" :key="'a-' + (item.id || idx)" class="block">
  128 + <uni-list>
  129 + <uni-list-item title="类别">
  130 + <template v-slot:footer>
  131 + <uni-easyinput :value="item.category" :inputBorder="false" disabled />
  132 + </template>
  133 + </uni-list-item>
  134 + <uni-list-item title="数量">
  135 + <template v-slot:footer>
  136 + <uni-easyinput :value="item.quantity" :inputBorder="false" disabled />
  137 + </template>
  138 + </uni-list-item>
  139 + </uni-list>
  140 + </view>
  141 + </view>
  142 + </view>
  143 +
  144 + <!-- 其他信息 -->
  145 + <view class="remark-section">
  146 + <view class="header">
  147 + <image class="opCollapse" src="/static/images/title.png" />
  148 + <text class="title">其他信息</text>
  149 + </view>
  150 + <view class="section-content">
  151 + <uni-list>
  152 + <uni-list-item title="物料名称">
  153 + <template v-slot:footer>
  154 + <uni-easyinput :value="materialName" :inputBorder="false" disabled />
  155 + </template>
  156 + </uni-list-item>
  157 + <uni-list-item>
  158 + <template v-slot:body>
  159 + <view class="item-title"><text class="required">*</text><text>过磅总重</text></view>
  160 + </template>
  161 + <template v-slot:footer>
  162 + <uni-easyinput v-model="form.totalGrossWeight" type="digit" :inputBorder="false" placeholder="请输入过磅总重" />
  163 + </template>
  164 + </uni-list-item>
  165 + <uni-list-item>
  166 + <template v-slot:body>
  167 + <view class="item-title"><text class="required">*</text><text>实际伙重</text></view>
  168 + </template>
  169 + <template v-slot:footer>
  170 + <uni-easyinput v-model="form.actualImpurityWeight" type="digit" :inputBorder="false" placeholder="请输入实际伙重" />
  171 + </template>
  172 + </uni-list-item>
  173 + <uni-list-item title="货物净重">
  174 + <template v-slot:footer>
  175 + <uni-easyinput :value="computedNetWeight" :inputBorder="false" disabled />
  176 + </template>
  177 + </uni-list-item>
  178 + <uni-list-item title="净重大写">
  179 + <template v-slot:footer>
  180 + <uni-easyinput :value="computedNetWeightUpper" :inputBorder="false" disabled />
  181 + </template>
  182 + </uni-list-item>
  183 + </uni-list>
  184 +
  185 + <view class="form-group">
  186 + <view class="item-title"><text class="required">*</text><text>来料扣杂类型</text></view>
  187 + <uni-data-checkbox
  188 + mode="tag"
  189 + :multiple="true"
  190 + :value-field="'value'"
  191 + :text-field="'text'"
  192 + v-model="form.deductionTypes"
  193 + :localdata="deductionTypeOptions"
  194 + />
  195 + </view>
  196 +
  197 + <uni-list>
  198 + <uni-list-item>
  199 + <template v-slot:body>
  200 + <view class="item-title"><text class="required">*</text><text>供应存欠料</text></view>
  201 + </template>
  202 + <template v-slot:footer>
  203 + <uni-easyinput v-model="form.supplierStockMaterial" :inputBorder="false" placeholder="请输入供应存欠料" />
  204 + </template>
  205 + </uni-list-item>
  206 + <uni-list-item>
  207 + <template v-slot:body>
  208 + <view class="item-title"><text class="required">*</text><text>物流存欠料</text></view>
  209 + </template>
  210 + <template v-slot:footer>
  211 + <uni-easyinput v-model="form.logisticsStockMaterial" :inputBorder="false" placeholder="请输入物流存欠料" />
  212 + </template>
  213 + </uni-list-item>
  214 + <uni-list-item>
  215 + <template v-slot:body>
  216 + <view class="item-title"><text class="required">*</text><text>车牌号</text></view>
  217 + </template>
  218 + <template v-slot:footer>
  219 + <uni-easyinput v-model="form.plateNumber" :inputBorder="false" placeholder="请输入车牌号" />
  220 + </template>
  221 + </uni-list-item>
  222 + </uni-list>
  223 + </view>
  224 + </view>
  225 +
  226 + <!-- 备注模块 -->
  227 + <view class="remark-section">
  228 + <view class="header">
  229 + <image class="opCollapse" src="/static/images/title.png" />
  230 + <text class="title">备注</text>
  231 + </view>
  232 + <view class="section-content">
  233 + <view class="remark-group">
  234 + <view class="remark-label">业务摘要</view>
  235 + <uni-easyinput :value="computedBusinessSummary" type="textarea" :inputBorder="false" disabled autoHeight />
  236 + </view>
  237 + <view class="remark-group">
  238 + <view class="remark-label">特别说明</view>
  239 + <uni-easyinput v-model="form.specialNote" type="textarea" :inputBorder="false" placeholder="请输入特别说明" maxlength="2000" />
  240 + </view>
  241 + <view class="remark-group">
  242 + <view class="remark-label">单据备注</view>
  243 + <uni-easyinput :value="computedDocumentRemark" type="textarea" :inputBorder="false" disabled autoHeight />
  244 + </view>
  245 + </view>
  246 + </view>
  247 +
  248 + <!-- 附件信息 -->
  249 + <view class="remark-section">
  250 + <view class="header">
  251 + <image class="opCollapse" src="/static/images/title.png" />
  252 + <text class="title">复磅附件</text>
  253 + <view class="ops">
  254 + <view class="upload-btn" @click="selectFile">上传文件</view>
  255 + </view>
  256 + </view>
  257 + <view class="section-content">
  258 + <view v-if="!fileList.length" class="empty-tip">暂无附件,请上传</view>
  259 + <view v-for="(file, idx) in fileList" :key="file.id || idx" class="file-item">
  260 + <text class="file-name">{{ file.name || '未命名文件' }}</text>
  261 + <text class="file-del" @click="removeFile(idx)">删除</text>
  262 + </view>
  263 + </view>
  264 + </view>
  265 +
  266 + <view class="footer">
  267 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
  268 + </view>
  269 + </scroll-view>
  270 + </view>
  271 +</template>
  272 +
  273 +<script>
  274 +import { weighTicketDetailApi, weighTicketCreateApi } from '@/api/procure-manage/weighTicket.js'
  275 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
  276 +import upload from '@/utils/upload'
  277 +import { uploadFileApi } from '@/api/base.js'
  278 +
  279 +export default {
  280 + name: 'ReweighWeightTicket',
  281 + data() {
  282 + return {
  283 + // 原过磅单ID
  284 + id: '',
  285 + form: {
  286 + code: '',
  287 + type: '',
  288 + planCode: '',
  289 + unit: '',
  290 + supplierName: '',
  291 + companyShortName: '',
  292 + totalGrossWeight: '',
  293 + actualImpurityWeight: '',
  294 + deductionTypes: [],
  295 + supplierStockMaterial: '',
  296 + logisticsStockMaterial: '',
  297 + plateNumber: '',
  298 + specialNote: ''
  299 + },
  300 + dicOptions: { TYPE: [], DEDUCTION_TYPE: [] },
  301 + // 选中计划行(供 planIds 提交与 isBuyout 判断)
  302 + selectedPlanRows: [],
  303 + buyout: false,
  304 + // 计划单明细(只读)
  305 + planList: [],
  306 + // 过磅明细(仅数量可编辑)
  307 + weighList: [],
  308 + // 申请数量明细(只读)
  309 + applyList: [],
  310 + // 复磅附件 [{ id, name }]
  311 + fileList: []
  312 + }
  313 + },
  314 + computed: {
  315 + deductionTypeOptions() {
  316 + return (this.dicOptions.DEDUCTION_TYPE || []).map(it => ({
  317 + value: it.code != null ? String(it.code) : '',
  318 + text: it.name || ''
  319 + })).filter(it => it.value)
  320 + },
  321 + isBuyout() {
  322 + return this.buyout || (this.applyList || []).length > 0
  323 + },
  324 + planCodeText() {
  325 + const codes = Array.from(new Set((this.planList || []).map(it => it.planCode).filter(Boolean)))
  326 + return codes.join(',')
  327 + },
  328 + // 物料名称:过磅明细开票品名去重顿号拼接
  329 + materialName() {
  330 + const names = (this.weighList || [])
  331 + .map(item => item.invoiceProductName)
  332 + .filter(name => !!name)
  333 + return Array.from(new Set(names)).join('、')
  334 + },
  335 + // 货物净重 = 过磅总重 - 实际伙重(保留两位)
  336 + computedNetWeight() {
  337 + const gross = this.form.totalGrossWeight
  338 + if (gross === undefined || gross === null || gross === '') return ''
  339 + const num = Number(gross) - Number(this.form.actualImpurityWeight || 0)
  340 + return String(Math.round(num * 100) / 100)
  341 + },
  342 + // 净重大写:中文大写
  343 + computedNetWeightUpper() {
  344 + const net = this.computedNetWeight
  345 + if (net === '' || net === null || net === undefined) return ''
  346 + return numberToChineseUpper(net)
  347 + },
  348 + // 业务摘要
  349 + computedBusinessSummary() {
  350 + const parts = []
  351 + const weightItems = (this.weighList || [])
  352 + .map(item => {
  353 + const name = item.productName || ''
  354 + const qty = item.quantity === null || item.quantity === undefined ? '' : item.quantity
  355 + if (!name && qty === '') return ''
  356 + return `${name}${qty}公斤`
  357 + })
  358 + .filter(text => !!text)
  359 + if (weightItems.length) parts.push(weightItems.join('、'))
  360 + const deductionNames = (this.form.deductionTypes || [])
  361 + .map(code => getDicName('DEDUCTION_TYPE', code, this.dicOptions.DEDUCTION_TYPE))
  362 + .filter(name => !!name)
  363 + if (deductionNames.length) parts.push(deductionNames.join('、'))
  364 + if (this.form.supplierStockMaterial) parts.push(`供应存欠料:${this.form.supplierStockMaterial}`)
  365 + if (this.form.logisticsStockMaterial) parts.push(`物流存欠料:${this.form.logisticsStockMaterial}`)
  366 + if (this.form.plateNumber) parts.push(`车牌号:${this.form.plateNumber}`)
  367 + return parts.join(',')
  368 + },
  369 + // 单据备注 = 业务摘要 + 特别说明
  370 + computedDocumentRemark() {
  371 + const summary = this.computedBusinessSummary || ''
  372 + const note = this.form.specialNote || ''
  373 + return [summary, note].filter(text => !!text).join('\n')
  374 + }
  375 + },
  376 + onLoad(query) {
  377 + this.id = (query && query.id) || ''
  378 + },
  379 + created() {
  380 + this.loadDicData()
  381 + this.loadDetail()
  382 + },
  383 + methods: {
  384 + async loadDicData() {
  385 + try {
  386 + const results = await getDicByCodes(['TYPE', 'DEDUCTION_TYPE'])
  387 + this.dicOptions.TYPE = (results.TYPE && results.TYPE.data) || []
  388 + this.dicOptions.DEDUCTION_TYPE = (results.DEDUCTION_TYPE && results.DEDUCTION_TYPE.data) || []
  389 + } catch (e) {
  390 + this.dicOptions = { TYPE: [], DEDUCTION_TYPE: [] }
  391 + }
  392 + },
  393 + getTypeLabel() {
  394 + const found = (this.dicOptions.TYPE || []).find(it => String(it.code) === String(this.form.type))
  395 + return found ? found.name : ''
  396 + },
  397 + loadDetail() {
  398 + if (!this.id) {
  399 + uni.showToast({ title: '缺少单据ID', icon: 'none' })
  400 + return
  401 + }
  402 + uni.showLoading({ title: '加载中...', mask: true })
  403 + weighTicketDetailApi({ id: this.id })
  404 + .then(res => {
  405 + const data = res && res.data ? (res.data.data || res.data) : {}
  406 + this.buyout = !!data.buyout
  407 + this.form = {
  408 + // 复磅编号 = F + 原编号
  409 + code: 'F' + (data.code || ''),
  410 + type: data.type != null ? String(data.type) : '',
  411 + planCode: data.planCode || data.planCodes || '',
  412 + unit: data.unit || '',
  413 + supplierName: data.supplierName || '',
  414 + companyShortName: data.companyShortName || '',
  415 + totalGrossWeight: data.totalGrossWeight == null ? '' : String(data.totalGrossWeight),
  416 + actualImpurityWeight: data.actualImpurityWeight == null ? '' : String(data.actualImpurityWeight),
  417 + deductionTypes: data.deductionTypes ? String(data.deductionTypes).split(',').filter(Boolean) : [],
  418 + supplierStockMaterial: data.supplierStockMaterial || '',
  419 + logisticsStockMaterial: data.logisticsStockMaterial || '',
  420 + plateNumber: data.plateNumber || '',
  421 + specialNote: data.specialNote || ''
  422 + }
  423 + this.planList = (Array.isArray(data.planLines) ? data.planLines : []).map(it => ({ ...it }))
  424 + // 选中计划行对象(buyout 供 isBuyout 判断,id 供 planIds 提交使用)
  425 + this.selectedPlanRows = this.planList.map(item => ({
  426 + id: item.procurementPlanId || item.id,
  427 + buyout: this.buyout
  428 + }))
  429 + this.weighList = (Array.isArray(data.weighLines) ? data.weighLines : []).map(it => ({ ...it, id: it.id || genRowId() }))
  430 + this.applyList = (Array.isArray(data.applyLines) ? data.applyLines : []).map(it => ({ ...it, id: it.id || genRowId() }))
  431 + // 已传复磅附件回显(多文件逗号分隔)
  432 + const ids = String(data.reweighAttachmentFileIds || '').split(',').filter(Boolean)
  433 + const names = String(data.reweighAttachmentFileNames || '').split(',').filter(Boolean)
  434 + this.fileList = ids.map((fid, i) => ({ id: fid, name: names[i] || '' }))
  435 + })
  436 + .catch(() => {
  437 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  438 + })
  439 + .finally(() => {
  440 + uni.hideLoading()
  441 + })
  442 + },
  443 + // 选择并上传复磅附件(多文件逐个追加)
  444 + selectFile() {
  445 + if (typeof uni !== 'undefined' && typeof uni.chooseMessageFile === 'function') {
  446 + uni.chooseMessageFile({
  447 + count: 9,
  448 + type: 'all',
  449 + success: async res => {
  450 + const files = (res && res.tempFiles) || []
  451 + if (!files.length) return
  452 + uni.showLoading({ title: '上传中...', mask: true })
  453 + try {
  454 + for (const f of files) {
  455 + const name = f.name || 'file'
  456 + const r = await upload({ url: '/sw/filebox/uploadFile', filePath: f.path, name: 'file' })
  457 + const d = r && r.data ? r.data : r
  458 + const fid = (d && (d.id || d.fileId || (d.data && (d.data.id || d.data.fileId))))
  459 + ? String(d.id || d.fileId || d.data.id || d.data.fileId)
  460 + : ''
  461 + if (fid) this.fileList.push({ id: fid, name })
  462 + }
  463 + } catch (e) {
  464 + uni.showToast({ title: '上传失败', icon: 'none' })
  465 + } finally {
  466 + uni.hideLoading()
  467 + }
  468 + },
  469 + fail: () => {
  470 + uni.showToast({ title: '未选择文件', icon: 'none' })
  471 + }
  472 + })
  473 + return
  474 + }
  475 + // H5 兜底:动态创建原生 input
  476 + try {
  477 + const self = this
  478 + const input = document.createElement('input')
  479 + input.type = 'file'
  480 + input.multiple = true
  481 + input.style.position = 'fixed'
  482 + input.style.left = '-9999px'
  483 + document.body.appendChild(input)
  484 + input.addEventListener('change', self.onFileChange)
  485 + input.click()
  486 + setTimeout(() => { document.body.removeChild(input) }, 1000)
  487 + } catch (e) {
  488 + uni.showToast({ title: '无法打开文件选择', icon: 'none' })
  489 + }
  490 + },
  491 + async onFileChange(e) {
  492 + const files = (e && e.target && e.target.files) || []
  493 + if (!files.length) return
  494 + uni.showLoading({ title: '上传中...', mask: true })
  495 + try {
  496 + for (const file of files) {
  497 + const name = file.name || ''
  498 + const fd = new FormData()
  499 + fd.append('file', file, name)
  500 + const d = await uploadFileApi(fd)
  501 + const data = d && d.data ? d.data : d
  502 + const fid = (data && (data.id || data.fileId || (data.data && (data.data.id || data.data.fileId))))
  503 + ? String(data.id || data.fileId || data.data.id || data.data.fileId)
  504 + : ''
  505 + if (fid) this.fileList.push({ id: fid, name })
  506 + }
  507 + } catch (e2) {
  508 + uni.showToast({ title: '上传失败', icon: 'none' })
  509 + } finally {
  510 + uni.hideLoading()
  511 + if (e && e.target) e.target.value = ''
  512 + }
  513 + },
  514 + removeFile(idx) {
  515 + this.fileList.splice(idx, 1)
  516 + },
  517 + validate() {
  518 + const f = this.form
  519 + if (f.totalGrossWeight === '' || f.totalGrossWeight == null) return this.tip('请输入过磅总重')
  520 + if (f.actualImpurityWeight === '' || f.actualImpurityWeight == null) return this.tip('请输入实际伙重')
  521 + if (!f.deductionTypes || !f.deductionTypes.length) return this.tip('请选择来料扣杂类型')
  522 + if (!f.supplierStockMaterial) return this.tip('请输入供应存欠料')
  523 + if (!f.logisticsStockMaterial) return this.tip('请输入物流存欠料')
  524 + if (!f.plateNumber) return this.tip('请输入车牌号')
  525 + // 过磅明细:过磅数量必填
  526 + const weightList = this.weighList || []
  527 + for (let i = 0; i < weightList.length; i++) {
  528 + const row = weightList[i]
  529 + if (row.quantity === '' || row.quantity == null) return this.tip(`过磅明细第${i + 1}行:请输入过磅数量`)
  530 + }
  531 + // 复磅附件必传
  532 + if (!this.fileList.length) return this.tip('请上传复磅附件')
  533 + return true
  534 + },
  535 + tip(msg) {
  536 + uni.showToast({ title: msg, icon: 'none' })
  537 + return false
  538 + },
  539 + async onSubmit() {
  540 + if (!this.validate()) return
  541 + const confirmRes = await new Promise(resolve => {
  542 + uni.showModal({ title: '提示', content: '确定保存复磅单吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  543 + })
  544 + if (!(confirmRes && confirmRes.confirm)) return
  545 + const ids = this.fileList.map(it => it.id).filter(Boolean)
  546 + const names = this.fileList.map(it => it.name).filter(Boolean)
  547 + const payload = {
  548 + originWeighingTicketId: this.id,
  549 + code: this.form.code,
  550 + type: this.form.type,
  551 + planIds: (this.selectedPlanRows || []).map(it => it.id),
  552 + weighLines: (this.weighList || []).map(row => ({
  553 + procurementPlanId: row.procurementPlanId || '',
  554 + planCode: row.planCode || '',
  555 + productId: row.productId || '',
  556 + invoiceProductName: row.invoiceProductName || '',
  557 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  558 + itemId: row.id
  559 + })),
  560 + applyLines: (this.applyList || []).map(row => ({
  561 + category: row.category || '',
  562 + breedRelationshipId: row.breedRelationshipId || '',
  563 + quantity: row.quantity === null || row.quantity === undefined ? '' : String(row.quantity),
  564 + itemId: row.breedRelationshipId
  565 + })),
  566 + unit: this.form.unit,
  567 + materialName: this.materialName || null,
  568 + totalGrossWeight: this.form.totalGrossWeight === '' ? null : Number(this.form.totalGrossWeight),
  569 + actualImpurityWeight: this.form.actualImpurityWeight === '' ? null : Number(this.form.actualImpurityWeight),
  570 + netWeight: this.computedNetWeight === '' ? null : Number(this.computedNetWeight),
  571 + netWeightUpper: this.computedNetWeightUpper || null,
  572 + deductionTypes: (this.form.deductionTypes || []).join(',') || null,
  573 + supplierStockMaterial: this.form.supplierStockMaterial || null,
  574 + logisticsStockMaterial: this.form.logisticsStockMaterial || null,
  575 + plateNumber: this.form.plateNumber || null,
  576 + businessSummary: this.computedBusinessSummary || null,
  577 + specialNote: this.form.specialNote || null,
  578 + documentRemark: this.computedDocumentRemark || null,
  579 + reweighAttachmentFileIds: ids.join(','),
  580 + reweighAttachmentFileNames: names.join(',')
  581 + }
  582 + uni.showLoading({ title: '保存中...', mask: true })
  583 + weighTicketCreateApi(payload)
  584 + .then(() => {
  585 + uni.hideLoading()
  586 + uni.showToast({ title: '保存成功', icon: 'none' })
  587 + try {
  588 + uni.setStorageSync('WEIGHT_TICKET_LIST_NEED_REFRESH', '1')
  589 + uni.setStorageSync('WEIGHT_TICKET_DETAIL_NEED_REFRESH', '1')
  590 + } catch (e) {}
  591 + setTimeout(() => { uni.navigateBack() }, 400)
  592 + })
  593 + .catch(e => {
  594 + uni.hideLoading()
  595 + uni.showToast({ title: (e && (e.msg || e.message)) || '保存失败', icon: 'none' })
  596 + })
  597 + }
  598 + }
  599 +}
  600 +
  601 +// 生成前端临时行 id
  602 +function genRowId() {
  603 + return 'row_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8)
  604 +}
  605 +
  606 +// 数字转中文大写(对齐 PC 端 useWeightTicketForm.ts)
  607 +function numberToChineseUpper(num) {
  608 + if (num === null || num === undefined || num === '' || isNaN(Number(num))) return '零'
  609 + const n = Number(num)
  610 + if (!isFinite(n)) return '零'
  611 +
  612 + const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
  613 + const smallUnits = ['', '拾', '佰', '仟']
  614 + const bigUnits = ['', '万', '亿', '兆']
  615 +
  616 + const negative = n < 0
  617 + const abs = Math.abs(n)
  618 + const parts = abs.toString().split('.')
  619 + const intStrRaw = parts[0]
  620 + const decStrRaw = parts[1] || ''
  621 + const s = intStrRaw
  622 + const len = s.length
  623 +
  624 + let intPart = ''
  625 + let zeroFlag = false
  626 + for (let i = 0; i < len; i++) {
  627 + const d = parseInt(s[i], 10)
  628 + const pos = len - 1 - i
  629 + const unitIdx = pos % 4
  630 + const groupIdx = Math.floor(pos / 4)
  631 + if (d !== 0) {
  632 + if (zeroFlag) {
  633 + intPart += digit[0]
  634 + zeroFlag = false
  635 + }
  636 + intPart += digit[d] + smallUnits[unitIdx]
  637 + } else {
  638 + zeroFlag = true
  639 + }
  640 + if (unitIdx === 0) {
  641 + const start = Math.max(0, len - (groupIdx + 1) * 4)
  642 + const end = len - groupIdx * 4
  643 + const groupDigits = s.slice(start, end)
  644 + if (!/^0+$/.test(groupDigits)) {
  645 + intPart += bigUnits[groupIdx] || ''
  646 + }
  647 + }
  648 + }
  649 + if (intPart === '') intPart = digit[0]
  650 +
  651 + let result = intPart
  652 + if (decStrRaw) {
  653 + const decChars = decStrRaw
  654 + .split('')
  655 + .map(c => digit[parseInt(c, 10)])
  656 + .join('')
  657 + result += '点' + decChars
  658 + }
  659 + return (negative ? '负' : '') + result
  660 +}
  661 +</script>
  662 +
  663 +<style lang="scss" scoped>
  664 +.page {
  665 + display: flex;
  666 + flex-direction: column;
  667 + height: 100%;
  668 +}
  669 +
  670 +.scroll {
  671 + flex: 1;
  672 + padding: 12rpx 0 200rpx !important;
  673 +}
  674 +
  675 +.footer {
  676 + z-index: 2;
  677 + position: fixed;
  678 + left: 0;
  679 + right: 0;
  680 + bottom: 0;
  681 + padding: 32rpx;
  682 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  683 + background: #fff;
  684 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  685 +
  686 + .btn {
  687 + height: 80rpx;
  688 + line-height: 80rpx;
  689 + border-radius: 12rpx;
  690 + font-size: 32rpx;
  691 + }
  692 +
  693 + .submit {
  694 + background: $theme-primary;
  695 + color: #fff;
  696 + }
  697 +}
  698 +
  699 +::v-deep .uni-list {
  700 + .uni-easyinput {
  701 + display: flex;
  702 +
  703 + .uni-input-input {
  704 + color: rgba(0, 0, 0, 0.9);
  705 + }
  706 + }
  707 +
  708 + .uni-input-placeholder {
  709 + z-index: 1;
  710 + }
  711 +
  712 + .uni-input-input {
  713 + background-color: #ffffff;
  714 + }
  715 +
  716 + background: transparent;
  717 +
  718 + &-item {
  719 + &__extra-text {
  720 + font-size: 32rpx;
  721 + }
  722 +
  723 + &__content-title {
  724 + font-size: 32rpx;
  725 + color: rgba(0, 0, 0, 0.9);
  726 + }
  727 +
  728 + &__container {
  729 + padding: 32rpx;
  730 +
  731 + .uni-easyinput {
  732 + &__placeholder-class {
  733 + font-size: 32rpx;
  734 + color: rgba(0, 0, 0, 0.4);
  735 + }
  736 +
  737 + &__content {
  738 + border: none;
  739 + background-color: #ffffff !important;
  740 +
  741 + &-input {
  742 + padding-left: 0 !important;
  743 + height: 48rpx;
  744 + line-height: 48rpx;
  745 + font-size: 32rpx;
  746 + }
  747 + }
  748 + }
  749 +
  750 + .item-title,
  751 + .uni-list-item__content {
  752 + flex: none;
  753 + min-height: 48rpx;
  754 + line-height: 48rpx;
  755 + font-size: 32rpx;
  756 + position: relative;
  757 + width: 240rpx;
  758 + margin-right: 32rpx;
  759 + color: rgba(0, 0, 0, 0.9);
  760 +
  761 + .required {
  762 + color: red;
  763 + position: absolute;
  764 + top: 50%;
  765 + transform: translateY(-50%);
  766 + left: -16rpx;
  767 + }
  768 + }
  769 + }
  770 + }
  771 +}
  772 +
  773 +.readonly-text {
  774 + color: rgba(0, 0, 0, 0.9);
  775 + font-size: 32rpx;
  776 + line-height: 48rpx;
  777 + text-align: right;
  778 + white-space: pre-wrap;
  779 + word-break: break-all;
  780 +}
  781 +
  782 +.value-code {
  783 + width: 260rpx;
  784 + text-align: left;
  785 +}
  786 +
  787 +.sub-table,
  788 +.remark-section {
  789 + margin-top: 20rpx;
  790 + background: #fff;
  791 +}
  792 +
  793 +.header {
  794 + display: flex;
  795 + align-items: center;
  796 + padding: 24rpx 32rpx;
  797 + border-bottom: 1rpx solid #f0f0f0;
  798 +
  799 + .opCollapse {
  800 + width: 32rpx;
  801 + height: 28rpx;
  802 + margin-right: 16rpx;
  803 + margin-top: 8rpx;
  804 + }
  805 +
  806 + .title {
  807 + font-size: 32rpx;
  808 + color: rgba(0, 0, 0, 0.9);
  809 + font-weight: 600;
  810 + flex: 1;
  811 + }
  812 +
  813 + .ops {
  814 + display: flex;
  815 + align-items: center;
  816 +
  817 + .upload-btn {
  818 + height: 56rpx;
  819 + line-height: 56rpx;
  820 + padding: 0 24rpx;
  821 + font-size: 26rpx;
  822 + border-radius: 8rpx;
  823 + background: $theme-primary;
  824 + color: #fff;
  825 + }
  826 + }
  827 +}
  828 +
  829 +.add-list {
  830 + padding: 0 0 16rpx;
  831 +
  832 + .empty-tip {
  833 + text-align: center;
  834 + padding: 60rpx 0;
  835 + color: rgba(0, 0, 0, 0.4);
  836 + font-size: 28rpx;
  837 + }
  838 +
  839 + .block {
  840 + border-bottom: 1rpx solid #f0f0f0;
  841 +
  842 + &:last-child {
  843 + border-bottom: none;
  844 + }
  845 + }
  846 +}
  847 +
  848 +.section-content {
  849 + padding: 32rpx;
  850 +
  851 + .empty-tip {
  852 + text-align: center;
  853 + padding: 40rpx 0;
  854 + color: rgba(0, 0, 0, 0.4);
  855 + font-size: 28rpx;
  856 + }
  857 +
  858 + .file-item {
  859 + display: flex;
  860 + align-items: center;
  861 + justify-content: space-between;
  862 + padding: 20rpx 24rpx;
  863 + margin-bottom: 16rpx;
  864 + background: #fafafa;
  865 + border-radius: 8rpx;
  866 +
  867 + &:last-child {
  868 + margin-bottom: 0;
  869 + }
  870 +
  871 + .file-name {
  872 + flex: 1;
  873 + font-size: 28rpx;
  874 + color: rgba(0, 0, 0, 0.9);
  875 + word-break: break-all;
  876 + margin-right: 20rpx;
  877 + }
  878 +
  879 + .file-del {
  880 + flex-shrink: 0;
  881 + font-size: 26rpx;
  882 + color: #d54941;
  883 + }
  884 + }
  885 +}
  886 +
  887 +.form-group {
  888 + margin: 24rpx 0 32rpx;
  889 +
  890 + .item-title {
  891 + display: flex;
  892 + align-items: center;
  893 + font-size: 32rpx;
  894 + color: rgba(0, 0, 0, 0.9);
  895 + margin-bottom: 20rpx;
  896 + position: relative;
  897 + padding-left: 16rpx;
  898 +
  899 + .required {
  900 + color: red;
  901 + position: absolute;
  902 + left: 0;
  903 + }
  904 + }
  905 +}
  906 +
  907 +.remark-group {
  908 + margin-bottom: 32rpx;
  909 +
  910 + &:last-child {
  911 + margin-bottom: 0;
  912 + }
  913 +
  914 + .remark-label {
  915 + display: flex;
  916 + align-items: center;
  917 + font-size: 32rpx;
  918 + color: rgba(0, 0, 0, 0.9);
  919 + margin-bottom: 16rpx;
  920 + }
  921 +}
  922 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <view class="detail-page">
  5 + <!-- 基础信息 -->
  6 + <view class="section">
  7 + <text class="row company">{{ form.code || '-' }}</text>
  8 + <view class="row"><text class="label">类型</text><text class="value">{{ typeName }}</text></view>
  9 + <view class="row"><text class="label">计划编号</text><text class="value">{{ planCodeText || '-' }}</text></view>
  10 + <view class="row"><text class="label">单位</text><text class="value">{{ form.unit || '-' }}</text></view>
  11 + <view class="row"><text class="label">供料单位</text><text class="value">{{ form.supplierName || '-' }}</text></view>
  12 + <view class="row"><text class="label">收料单位</text><text class="value">{{ form.companyShortName || '-' }}</text></view>
  13 + </view>
  14 +
  15 + <!-- 计划单明细 -->
  16 + <view class="sub-table">
  17 + <view class="sub-header">
  18 + <image class="sub-bar" src="/static/images/title.png" />
  19 + <text class="sub-title">计划单明细</text>
  20 + </view>
  21 + <view class="sub-body">
  22 + <view v-if="!planList.length" class="empty-tip">暂无明细</view>
  23 + <view v-for="(item, idx) in planList" :key="'p-' + (item.id || idx)" class="card">
  24 + <view class="card-index">第 {{ idx + 1 }} 行</view>
  25 + <view class="row"><text class="label">计划品种</text><text class="value">{{ item.productName || '-' }}</text></view>
  26 + <view class="row"><text class="label">计划数量</text><text class="value">{{ formatNumber(item.quantity) }}</text></view>
  27 + <view class="row"><text class="label">计划编号</text><text class="value">{{ item.planCode || '-' }}</text></view>
  28 + </view>
  29 + </view>
  30 + </view>
  31 +
  32 + <!-- 过磅明细 -->
  33 + <view class="sub-table">
  34 + <view class="sub-header">
  35 + <image class="sub-bar" src="/static/images/title.png" />
  36 + <text class="sub-title">过磅明细</text>
  37 + </view>
  38 + <view class="sub-body">
  39 + <view v-if="!weighList.length" class="empty-tip">暂无明细</view>
  40 + <view v-for="(item, idx) in weighList" :key="'w-' + (item.id || idx)" class="card">
  41 + <view class="card-index">第 {{ idx + 1 }} 行</view>
  42 + <view class="row"><text class="label">过磅品种</text><text class="value">{{ item.productName || '-' }}</text></view>
  43 + <view class="row"><text class="label">开票品名</text><text class="value">{{ item.invoiceProductName || '-' }}</text></view>
  44 + <view class="row"><text class="label">过磅数量</text><text class="value">{{ formatNumber(item.quantity) }}</text></view>
  45 + <view class="row"><text class="label">计划编号</text><text class="value">{{ item.planCode || '-' }}</text></view>
  46 + </view>
  47 + </view>
  48 + </view>
  49 +
  50 + <!-- 申请数量明细(仅买断展示) -->
  51 + <view v-if="isBuyout" class="sub-table">
  52 + <view class="sub-header">
  53 + <image class="sub-bar" src="/static/images/title.png" />
  54 + <text class="sub-title">申请数量明细</text>
  55 + </view>
  56 + <view class="sub-body">
  57 + <view v-if="!applyList.length" class="empty-tip">暂无明细</view>
  58 + <view v-for="(item, idx) in applyList" :key="'a-' + (item.id || idx)" class="card">
  59 + <view class="card-index">第 {{ idx + 1 }} 行</view>
  60 + <view class="row"><text class="label">类别</text><text class="value">{{ item.category || '-' }}</text></view>
  61 + <view class="row"><text class="label">数量</text><text class="value">{{ formatNumber(item.quantity) }}</text></view>
  62 + </view>
  63 + </view>
  64 + </view>
  65 +
  66 + <!-- 其他信息 -->
  67 + <view class="title-header">
  68 + <image class="title-header_icon" src="/static/images/title.png" />
  69 + <span>其他信息</span>
  70 + </view>
  71 + <view class="section">
  72 + <view class="row"><text class="label">物料名称</text><text class="value">{{ form.materialName || '-' }}</text></view>
  73 + <view class="row"><text class="label">过磅总重</text><text class="value">{{ formatNumber(form.totalGrossWeight) }}</text></view>
  74 + <view class="row"><text class="label">实际伙重</text><text class="value">{{ formatNumber(form.actualImpurityWeight) }}</text></view>
  75 + <view class="row"><text class="label">货物净重</text><text class="value">{{ formatNumber(form.netWeight) }}</text></view>
  76 + <view class="row"><text class="label">净重大写</text><text class="value">{{ form.netWeightUpper || '-' }}</text></view>
  77 + <view class="row"><text class="label">来料扣杂类型</text><text class="value">{{ deductionNames }}</text></view>
  78 + <view class="row"><text class="label">供应存欠料</text><text class="value">{{ form.supplierStockMaterial || '-' }}</text></view>
  79 + <view class="row"><text class="label">物流存欠料</text><text class="value">{{ form.logisticsStockMaterial || '-' }}</text></view>
  80 + <view class="row"><text class="label">车牌号</text><text class="value">{{ form.plateNumber || '-' }}</text></view>
  81 + </view>
  82 +
  83 + <!-- 业务摘要 -->
  84 + <view class="title-header">
  85 + <image class="title-header_icon" src="/static/images/title.png" />
  86 + <span>业务摘要</span>
  87 + </view>
  88 + <view class="section">
  89 + <text class="remark-text">{{ form.businessSummary || '-' }}</text>
  90 + </view>
  91 +
  92 + <!-- 特别说明 -->
  93 + <view class="title-header">
  94 + <image class="title-header_icon" src="/static/images/title.png" />
  95 + <span>特别说明</span>
  96 + </view>
  97 + <view class="section">
  98 + <text class="remark-text">{{ form.specialNote || '-' }}</text>
  99 + </view>
  100 +
  101 + <!-- 单据备注 -->
  102 + <view class="title-header">
  103 + <image class="title-header_icon" src="/static/images/title.png" />
  104 + <span>单据备注</span>
  105 + </view>
  106 + <view class="section">
  107 + <text class="remark-text">{{ form.documentRemark || '-' }}</text>
  108 + </view>
  109 +
  110 + <!-- 附件信息 -->
  111 + <view class="sub-table">
  112 + <view class="sub-header">
  113 + <image class="sub-bar" src="/static/images/title.png" />
  114 + <text class="sub-title">{{ mode === 'delay' ? '延期说明' : '签字凭证' }}</text>
  115 + <view class="upload-btn" @click="selectFile">上传文件</view>
  116 + </view>
  117 + <view class="sub-body">
  118 + <view v-if="mode === 'sign'" class="driver-row">
  119 + <view class="driver-label"><text class="required">*</text><text>驾驶员姓名</text></view>
  120 + <input
  121 + class="driver-input"
  122 + v-model="driver"
  123 + placeholder="请输入驾驶员姓名"
  124 + placeholder-style="color: rgba(0,0,0,0.4); font-size: 28rpx;"
  125 + />
  126 + </view>
  127 + <view v-if="!fileList.length" class="empty-tip">暂无附件,请上传</view>
  128 + <view v-for="(file, idx) in fileList" :key="file.id || idx" class="file-item">
  129 + <text class="file-name">{{ file.name || '未命名文件' }}</text>
  130 + <text class="file-del" @click="removeFile(idx)">删除</text>
  131 + </view>
  132 + </view>
  133 + </view>
  134 + </view>
  135 + </scroll-view>
  136 + <view class="footer">
  137 + <button class="btn submit" type="primary" @click="onSubmit">保存</button>
  138 + </view>
  139 + </view>
  140 +</template>
  141 +
  142 +<script>
  143 +import {
  144 + weighTicketDetailApi,
  145 + weighTicketUploadSignApi,
  146 + weighTicketUploadDelayApi
  147 +} from '@/api/procure-manage/weighTicket.js'
  148 +import { getDicByCodes, getDicName } from '@/utils/dic.js'
  149 +import upload from '@/utils/upload'
  150 +import { uploadFileApi } from '@/api/base.js'
  151 +
  152 +export default {
  153 + name: 'WeightTicketUpload',
  154 + data() {
  155 + return {
  156 + id: '',
  157 + // 上传类型:sign-签字凭证;delay-延期说明
  158 + mode: 'sign',
  159 + form: {
  160 + code: '',
  161 + type: '',
  162 + unit: '',
  163 + supplierName: '',
  164 + companyShortName: '',
  165 + materialName: '',
  166 + totalGrossWeight: '',
  167 + actualImpurityWeight: '',
  168 + netWeight: '',
  169 + netWeightUpper: '',
  170 + deductionTypes: '',
  171 + supplierStockMaterial: '',
  172 + logisticsStockMaterial: '',
  173 + plateNumber: '',
  174 + businessSummary: '',
  175 + specialNote: '',
  176 + documentRemark: ''
  177 + },
  178 + planList: [],
  179 + weighList: [],
  180 + applyList: [],
  181 + buyout: false,
  182 + // 附件列表 [{ id, name }]
  183 + fileList: [],
  184 + // 驾驶员姓名(仅签字凭证必填)
  185 + driver: '',
  186 + dicOptions: { TYPE: [], DEDUCTION_TYPE: [] }
  187 + }
  188 + },
  189 + computed: {
  190 + typeName() {
  191 + const v = this.form.type == null ? '' : String(this.form.type)
  192 + if (!v) return '-'
  193 + return getDicName('TYPE', v, this.dicOptions.TYPE) || v
  194 + },
  195 + planCodeText() {
  196 + const codes = Array.from(new Set((this.planList || []).map(it => it.planCode).filter(Boolean)))
  197 + return codes.join(',')
  198 + },
  199 + deductionNames() {
  200 + const codes = (this.form.deductionTypes == null ? '' : String(this.form.deductionTypes)).split(',').filter(Boolean)
  201 + const names = codes.map(code => getDicName('DEDUCTION_TYPE', code, this.dicOptions.DEDUCTION_TYPE)).filter(Boolean)
  202 + return names.length ? names.join('、') : '-'
  203 + },
  204 + isBuyout() {
  205 + return this.buyout || (this.applyList || []).length > 0
  206 + }
  207 + },
  208 + onLoad(query) {
  209 + this.id = (query && query.id) || ''
  210 + this.mode = query && query.mode === 'delay' ? 'delay' : 'sign'
  211 + uni.setNavigationBarTitle({
  212 + title: this.mode === 'delay' ? '上传延期说明' : '上传签字凭证'
  213 + })
  214 + },
  215 + created() {
  216 + this.loadDicData()
  217 + this.loadDetail()
  218 + },
  219 + methods: {
  220 + async loadDicData() {
  221 + try {
  222 + const results = await getDicByCodes(['TYPE', 'DEDUCTION_TYPE'])
  223 + this.dicOptions.TYPE = (results.TYPE && results.TYPE.data) || []
  224 + this.dicOptions.DEDUCTION_TYPE = (results.DEDUCTION_TYPE && results.DEDUCTION_TYPE.data) || []
  225 + } catch (e) {
  226 + this.dicOptions = { TYPE: [], DEDUCTION_TYPE: [] }
  227 + }
  228 + },
  229 + loadDetail() {
  230 + if (!this.id) {
  231 + uni.showToast({ title: '缺少单据ID', icon: 'none' })
  232 + return
  233 + }
  234 + uni.showLoading({ title: '加载中...', mask: true })
  235 + weighTicketDetailApi({ id: this.id })
  236 + .then(res => {
  237 + const data = res && res.data ? (res.data.data || res.data) : {}
  238 + this.form = {
  239 + code: data.code || '',
  240 + type: data.type != null ? String(data.type) : '',
  241 + unit: data.unit || '',
  242 + supplierName: data.supplierName || '',
  243 + companyShortName: data.companyShortName || '',
  244 + materialName: data.materialName || '',
  245 + totalGrossWeight: data.totalGrossWeight == null ? '' : String(data.totalGrossWeight),
  246 + actualImpurityWeight: data.actualImpurityWeight == null ? '' : String(data.actualImpurityWeight),
  247 + netWeight: data.netWeight == null ? '' : String(data.netWeight),
  248 + netWeightUpper: data.netWeightUpper || '',
  249 + deductionTypes: data.deductionTypes || '',
  250 + supplierStockMaterial: data.supplierStockMaterial || '',
  251 + logisticsStockMaterial: data.logisticsStockMaterial || '',
  252 + plateNumber: data.plateNumber || '',
  253 + businessSummary: data.businessSummary || '',
  254 + specialNote: data.specialNote || '',
  255 + documentRemark: data.documentRemark || ''
  256 + }
  257 + // 驾驶员姓名回显(仅签字凭证)
  258 + this.driver = data.driver || ''
  259 + this.buyout = !!data.buyout
  260 + this.planList = Array.isArray(data.planLines) ? data.planLines.map(x => ({ ...x })) : []
  261 + this.weighList = Array.isArray(data.weighLines) ? data.weighLines.map(x => ({ ...x })) : []
  262 + this.applyList = Array.isArray(data.applyLines) ? data.applyLines.map(x => ({ ...x })) : []
  263 + // 已传附件回显(多文件逗号分隔)
  264 + const key = this.mode === 'delay' ? 'delayFile' : 'signFile'
  265 + const ids = String(data[key + 'Id'] || '').split(',').filter(Boolean)
  266 + const names = String(data[key + 'Name'] || '').split(',').filter(Boolean)
  267 + this.fileList = ids.map((id, i) => ({ id, name: names[i] || '' }))
  268 + })
  269 + .catch(() => {
  270 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  271 + })
  272 + .finally(() => {
  273 + uni.hideLoading()
  274 + })
  275 + },
  276 + // 选择并上传文件(多文件逐个追加)
  277 + selectFile() {
  278 + if (typeof uni !== 'undefined' && typeof uni.chooseMessageFile === 'function') {
  279 + uni.chooseMessageFile({
  280 + count: 9,
  281 + type: 'all',
  282 + success: async res => {
  283 + const files = (res && res.tempFiles) || []
  284 + if (!files.length) return
  285 + uni.showLoading({ title: '上传中...', mask: true })
  286 + try {
  287 + for (const f of files) {
  288 + const name = f.name || 'file'
  289 + const r = await upload({ url: '/sw/filebox/uploadFile', filePath: f.path, name: 'file' })
  290 + const d = r && r.data ? r.data : r
  291 + const id = (d && (d.id || d.fileId || (d.data && (d.data.id || d.data.fileId))))
  292 + ? String(d.id || d.fileId || d.data.id || d.data.fileId)
  293 + : ''
  294 + if (id) this.fileList.push({ id, name })
  295 + }
  296 + } catch (e) {
  297 + uni.showToast({ title: '上传失败', icon: 'none' })
  298 + } finally {
  299 + uni.hideLoading()
  300 + }
  301 + },
  302 + fail: () => {
  303 + uni.showToast({ title: '未选择文件', icon: 'none' })
  304 + }
  305 + })
  306 + return
  307 + }
  308 + // H5 兜底:动态创建原生 input
  309 + try {
  310 + const self = this
  311 + const input = document.createElement('input')
  312 + input.type = 'file'
  313 + input.multiple = true
  314 + input.style.position = 'fixed'
  315 + input.style.left = '-9999px'
  316 + document.body.appendChild(input)
  317 + input.addEventListener('change', self.onFileChange)
  318 + input.click()
  319 + setTimeout(() => { document.body.removeChild(input) }, 1000)
  320 + } catch (e) {
  321 + uni.showToast({ title: '无法打开文件选择', icon: 'none' })
  322 + }
  323 + },
  324 + async onFileChange(e) {
  325 + const files = (e && e.target && e.target.files) || []
  326 + if (!files.length) return
  327 + uni.showLoading({ title: '上传中...', mask: true })
  328 + try {
  329 + for (const file of files) {
  330 + const name = file.name || ''
  331 + const fd = new FormData()
  332 + fd.append('file', file, name)
  333 + const d = await uploadFileApi(fd)
  334 + const data = d && d.data ? d.data : d
  335 + const id = (data && (data.id || data.fileId || (data.data && (data.data.id || data.data.fileId))))
  336 + ? String(data.id || data.fileId || data.data.id || data.data.fileId)
  337 + : ''
  338 + if (id) this.fileList.push({ id, name })
  339 + }
  340 + } catch (e2) {
  341 + uni.showToast({ title: '上传失败', icon: 'none' })
  342 + } finally {
  343 + uni.hideLoading()
  344 + if (e && e.target) e.target.value = ''
  345 + }
  346 + },
  347 + removeFile(idx) {
  348 + this.fileList.splice(idx, 1)
  349 + },
  350 + formatNumber(v) {
  351 + if (v === null || v === undefined || v === '') return '-'
  352 + const n = Number(v)
  353 + if (isNaN(n)) return String(v)
  354 + return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  355 + },
  356 + async onSubmit() {
  357 + const ids = this.fileList.map(it => it.id).filter(Boolean)
  358 + const names = this.fileList.map(it => it.name).filter(Boolean)
  359 + if (!ids.length || !names.length) {
  360 + uni.showToast({ title: this.mode === 'delay' ? '请上传延期说明附件' : '请上传签字凭证', icon: 'none' })
  361 + return
  362 + }
  363 + // 签字凭证:驾驶员姓名必填
  364 + const driver = (this.driver || '').trim()
  365 + if (this.mode === 'sign' && !driver) {
  366 + uni.showToast({ title: '请输入驾驶员姓名', icon: 'none' })
  367 + return
  368 + }
  369 + const confirmRes = await new Promise(resolve => {
  370 + uni.showModal({ title: '提示', content: '确定保存附件吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  371 + })
  372 + if (!(confirmRes && confirmRes.confirm)) return
  373 + const data = {
  374 + id: this.id,
  375 + fileIds: ids.join(','),
  376 + fileNames: names.join(',')
  377 + }
  378 + if (this.mode === 'sign') {
  379 + data.driver = driver
  380 + }
  381 + uni.showLoading({ title: '保存中...', mask: true })
  382 + const api = this.mode === 'delay' ? weighTicketUploadDelayApi : weighTicketUploadSignApi
  383 + api(data)
  384 + .then(() => {
  385 + uni.hideLoading()
  386 + uni.showToast({ title: '保存成功', icon: 'none' })
  387 + try {
  388 + uni.setStorageSync('WEIGHT_TICKET_LIST_NEED_REFRESH', '1')
  389 + uni.setStorageSync('WEIGHT_TICKET_DETAIL_NEED_REFRESH', '1')
  390 + } catch (e) {}
  391 + setTimeout(() => { uni.navigateBack() }, 400)
  392 + })
  393 + .catch(e => {
  394 + uni.hideLoading()
  395 + uni.showToast({ title: (e && (e.msg || e.message)) || '保存失败', icon: 'none' })
  396 + })
  397 + }
  398 + }
  399 +}
  400 +</script>
  401 +
  402 +<style lang="scss" scoped>
  403 +.page {
  404 + display: flex;
  405 + flex-direction: column;
  406 + height: 100%;
  407 +}
  408 +
  409 +.scroll {
  410 + flex: 1;
  411 + padding: 8rpx 0 176rpx;
  412 +}
  413 +
  414 +.detail-page {
  415 + background: #f3f3f3;
  416 +}
  417 +
  418 +.footer {
  419 + z-index: 2;
  420 + position: fixed;
  421 + left: 0;
  422 + right: 0;
  423 + bottom: 0;
  424 + padding: 32rpx;
  425 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  426 + background: #fff;
  427 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  428 +
  429 + .btn {
  430 + height: 80rpx;
  431 + line-height: 80rpx;
  432 + border-radius: 12rpx;
  433 + font-size: 32rpx;
  434 + }
  435 +
  436 + .submit {
  437 + background: $theme-primary;
  438 + color: #fff;
  439 + }
  440 +}
  441 +
  442 +.section {
  443 + padding: 32rpx;
  444 + background: #fff;
  445 + margin-bottom: 20rpx;
  446 +}
  447 +
  448 +.title-header {
  449 + display: flex;
  450 + align-items: center;
  451 + padding: 24rpx 32rpx 12rpx;
  452 + background-color: #ffffff;
  453 +
  454 + .title-header_icon {
  455 + width: 36rpx;
  456 + height: 36rpx;
  457 + margin-right: 12rpx;
  458 + }
  459 +
  460 + span {
  461 + font-size: 34rpx;
  462 + font-weight: 600;
  463 + color: rgba(0, 0, 0, 0.9);
  464 + }
  465 +}
  466 +
  467 +.row {
  468 + display: flex;
  469 + margin-bottom: 28rpx;
  470 +
  471 + &:last-child {
  472 + margin-bottom: 0;
  473 + }
  474 +
  475 + &.company {
  476 + font-size: 36rpx;
  477 + font-weight: 600;
  478 + color: rgba(0, 0, 0, 0.9);
  479 + padding-top: 8rpx;
  480 + line-height: 50rpx;
  481 + }
  482 +
  483 + .label {
  484 + max-width: 420rpx;
  485 + margin-right: 20rpx;
  486 + line-height: 32rpx;
  487 + font-size: 28rpx;
  488 + color: rgba(0, 0, 0, 0.6);
  489 + }
  490 +
  491 + .value {
  492 + flex: 1;
  493 + line-height: 32rpx;
  494 + font-size: 28rpx;
  495 + color: rgba(0, 0, 0, 0.9);
  496 + text-align: right;
  497 + word-break: break-all;
  498 + }
  499 +}
  500 +
  501 +.remark-text {
  502 + font-size: 28rpx;
  503 + color: rgba(0, 0, 0, 0.9);
  504 + line-height: 1.6;
  505 + white-space: pre-wrap;
  506 +}
  507 +
  508 +.sub-table {
  509 + background: #fff;
  510 + margin-bottom: 20rpx;
  511 +}
  512 +
  513 +.sub-header {
  514 + display: flex;
  515 + align-items: center;
  516 + padding: 16rpx 0 16rpx 32rpx;
  517 +
  518 + .sub-bar {
  519 + width: 6rpx;
  520 + height: 32rpx;
  521 + margin-right: 14rpx;
  522 + }
  523 +
  524 + .sub-title {
  525 + font-size: 30rpx;
  526 + font-weight: 600;
  527 + color: rgba(0, 0, 0, 0.9);
  528 + flex: 1;
  529 + line-height: 42rpx;
  530 + }
  531 +
  532 + .upload-btn {
  533 + margin-right: 32rpx;
  534 + height: 56rpx;
  535 + line-height: 56rpx;
  536 + padding: 0 24rpx;
  537 + font-size: 26rpx;
  538 + border-radius: 8rpx;
  539 + background: $theme-primary;
  540 + color: #fff;
  541 + }
  542 +}
  543 +
  544 +.sub-body {
  545 + padding: 0 32rpx 24rpx;
  546 +
  547 + .empty-tip {
  548 + text-align: center;
  549 + padding: 40rpx 0;
  550 + color: rgba(0, 0, 0, 0.4);
  551 + font-size: 28rpx;
  552 + }
  553 +
  554 + .driver-row {
  555 + display: flex;
  556 + align-items: center;
  557 + justify-content: space-between;
  558 + min-height: 88rpx;
  559 + padding: 8rpx 0;
  560 + margin-bottom: 8rpx;
  561 + border-bottom: 1rpx dashed #f2f2f2;
  562 +
  563 + .driver-label {
  564 + display: flex;
  565 + align-items: center;
  566 + flex-shrink: 0;
  567 + width: 220rpx;
  568 + font-size: 28rpx;
  569 + color: rgba(0, 0, 0, 0.6);
  570 + line-height: 40rpx;
  571 +
  572 + .required {
  573 + color: #d54941;
  574 + margin-right: 6rpx;
  575 + }
  576 + }
  577 +
  578 + .driver-input {
  579 + flex: 1;
  580 + height: 56rpx;
  581 + line-height: 56rpx;
  582 + font-size: 28rpx;
  583 + color: rgba(0, 0, 0, 0.9);
  584 + text-align: right;
  585 + }
  586 + }
  587 +
  588 + .card {
  589 + position: relative;
  590 + background: #fafafa;
  591 + border-radius: 12rpx;
  592 + padding: 32rpx 28rpx 20rpx;
  593 + margin-bottom: 24rpx;
  594 +
  595 + .card-index {
  596 + position: absolute;
  597 + top: 0;
  598 + right: 0;
  599 + background: rgba(24, 144, 255, 0.08);
  600 + color: $theme-primary;
  601 + font-size: 22rpx;
  602 + line-height: 36rpx;
  603 + padding: 0 16rpx;
  604 + border-bottom-left-radius: 12rpx;
  605 + border-top-right-radius: 12rpx;
  606 + }
  607 +
  608 + .row {
  609 + align-items: center;
  610 + justify-content: space-between;
  611 + padding: 14rpx 0;
  612 + margin-bottom: 0;
  613 + border-bottom: 1rpx dashed #f2f2f2;
  614 +
  615 + &:last-child {
  616 + border-bottom: none;
  617 + }
  618 +
  619 + .label {
  620 + flex-shrink: 0;
  621 + width: 220rpx;
  622 + max-width: none;
  623 + margin-right: 0;
  624 + font-size: 28rpx;
  625 + color: rgba(0, 0, 0, 0.6);
  626 + line-height: 40rpx;
  627 + }
  628 +
  629 + .value {
  630 + flex: 1;
  631 + text-align: right;
  632 + font-size: 28rpx;
  633 + color: rgba(0, 0, 0, 0.9);
  634 + line-height: 40rpx;
  635 + overflow: hidden;
  636 + text-overflow: ellipsis;
  637 + white-space: nowrap;
  638 + }
  639 + }
  640 + }
  641 +
  642 + .file-item {
  643 + display: flex;
  644 + align-items: center;
  645 + justify-content: space-between;
  646 + padding: 20rpx 24rpx;
  647 + margin-bottom: 16rpx;
  648 + background: #fafafa;
  649 + border-radius: 8rpx;
  650 +
  651 + &:last-child {
  652 + margin-bottom: 0;
  653 + }
  654 +
  655 + .file-name {
  656 + flex: 1;
  657 + font-size: 28rpx;
  658 + color: rgba(0, 0, 0, 0.9);
  659 + word-break: break-all;
  660 + margin-right: 20rpx;
  661 + }
  662 +
  663 + .file-del {
  664 + flex-shrink: 0;
  665 + font-size: 26rpx;
  666 + color: #d54941;
  667 + }
  668 + }
  669 +}
  670 +</style>
... ...