Showing
29 changed files
with
7943 additions
and
4 deletions
api/procure-manage/salesmanQuotation.js
0 → 100644
| 1 | +import request from '@/utils/request' | |
| 2 | +import { ContentTypeEnum } from '@/utils/httpEnum' | |
| 3 | + | |
| 4 | +export function salesmanQuotationQueryApi(params) { | |
| 5 | + return request({ | |
| 6 | + url: '/procurement/salesmanQuotation/query', | |
| 7 | + method: 'get', | |
| 8 | + params | |
| 9 | + }) | |
| 10 | +} | |
| 11 | + | |
| 12 | +// 生成编号 | |
| 13 | +export function salesmanQuotationGenerateCodeApi() { | |
| 14 | + return request({ | |
| 15 | + url: '/procurement/salesmanQuotation/generateCode', | |
| 16 | + method: 'get' | |
| 17 | + }) | |
| 18 | +} | |
| 19 | + | |
| 20 | +// 新增保存 | |
| 21 | +export function salesmanQuotationSaveApi(data) { | |
| 22 | + return request({ | |
| 23 | + url: '/procurement/salesmanQuotation', | |
| 24 | + method: 'post', | |
| 25 | + data, | |
| 26 | + contentType: ContentTypeEnum.JSON | |
| 27 | + }) | |
| 28 | +} | |
| 29 | + | |
| 30 | +// 详情 | |
| 31 | +export function salesmanQuotationDetailApi(params) { | |
| 32 | + return request({ | |
| 33 | + url: '/procurement/salesmanQuotation', | |
| 34 | + method: 'get', | |
| 35 | + params | |
| 36 | + }) | |
| 37 | +} | |
| 38 | + | |
| 39 | +// 审核详情 | |
| 40 | +export function salesmanQuotationGetExamineByIdApi(id) { | |
| 41 | + return request({ | |
| 42 | + url: '/procurement/salesmanQuotation/getExamineById', | |
| 43 | + method: 'get', | |
| 44 | + params: { id } | |
| 45 | + }) | |
| 46 | +} | |
| 47 | + | |
| 48 | +// 编辑 | |
| 49 | +export function salesmanQuotationUpdateApi(data) { | |
| 50 | + return request({ | |
| 51 | + url: '/procurement/salesmanQuotation', | |
| 52 | + method: 'put', | |
| 53 | + data, | |
| 54 | + contentType: ContentTypeEnum.JSON | |
| 55 | + }) | |
| 56 | +} | |
| 57 | + | |
| 58 | +// 品种关系查询 | |
| 59 | +export function breedRelationshipQueryApi(params) { | |
| 60 | + return request({ | |
| 61 | + url: '/baseData/breedRelationship/query', | |
| 62 | + method: 'get', | |
| 63 | + params | |
| 64 | + }) | |
| 65 | +} | |
| 66 | + | |
| 67 | +// 取消 | |
| 68 | +export function salesmanQuotationCancelApi(params) { | |
| 69 | + return request({ | |
| 70 | + url: '/procurement/salesmanQuotation/cancel', | |
| 71 | + method: 'post', | |
| 72 | + params | |
| 73 | + }) | |
| 74 | +} | ... | ... |
api/procure-manage/salesmanQuotationPeer.js
0 → 100644
| 1 | +import request from '@/utils/request' | |
| 2 | +import { ContentTypeEnum } from '@/utils/httpEnum' | |
| 3 | + | |
| 4 | +export function salesmanQuotationPeerQueryApi(params) { | |
| 5 | + return request({ | |
| 6 | + url: '/procurement/salesmanQuotationPeer/query', | |
| 7 | + method: 'get', | |
| 8 | + params | |
| 9 | + }) | |
| 10 | +} | |
| 11 | + | |
| 12 | +export function salesmanQuotationPeerSaveApi(data) { | |
| 13 | + return request({ | |
| 14 | + url: '/procurement/salesmanQuotationPeer', | |
| 15 | + method: 'post', | |
| 16 | + data, | |
| 17 | + contentType: ContentTypeEnum.JSON | |
| 18 | + }) | |
| 19 | +} | |
| 20 | + | |
| 21 | +export function salesmanQuotationPeerGetApi(id) { | |
| 22 | + return request({ | |
| 23 | + url: '/procurement/salesmanQuotationPeer', | |
| 24 | + method: 'get', | |
| 25 | + params: { id } | |
| 26 | + }) | |
| 27 | +} | |
| 28 | + | |
| 29 | +export function salesmanQuotationPeerUpdateApi(data) { | |
| 30 | + return request({ | |
| 31 | + url: '/procurement/salesmanQuotationPeer', | |
| 32 | + method: 'put', | |
| 33 | + data, | |
| 34 | + contentType: ContentTypeEnum.JSON | |
| 35 | + }) | |
| 36 | +} | |
| 37 | + | |
| 38 | +export function salesmanQuotationPeerDeleteApi(id) { | |
| 39 | + return request({ | |
| 40 | + url: '/procurement/salesmanQuotationPeer', | |
| 41 | + method: 'delete', | |
| 42 | + params: { id } | |
| 43 | + }) | |
| 44 | +} | ... | ... |
api/procure-manage/supplierQuotationDeal.js
0 → 100644
| 1 | +import request from '@/utils/request' | |
| 2 | +import { ContentTypeEnum } from '@/utils/httpEnum' | |
| 3 | + | |
| 4 | +export function supplierQuotationDealQueryApi(params) { | |
| 5 | + return request({ | |
| 6 | + url: '/procurement/supplierQuotationDeal/query', | |
| 7 | + method: 'get', | |
| 8 | + params | |
| 9 | + }) | |
| 10 | +} | |
| 11 | + | |
| 12 | +export function supplierQuotationDealGetApi(id) { | |
| 13 | + return request({ | |
| 14 | + url: '/procurement/supplierQuotationDeal', | |
| 15 | + method: 'get', | |
| 16 | + params: { id } | |
| 17 | + }) | |
| 18 | +} | |
| 19 | + | |
| 20 | +// 可分配人员 | |
| 21 | +export function supplierQuotationDealAssignUsersApi(params) { | |
| 22 | + return request({ | |
| 23 | + url: '/procurement/supplierQuotationDeal/assignUsers', | |
| 24 | + method: 'get', | |
| 25 | + params | |
| 26 | + }) | |
| 27 | +} | |
| 28 | + | |
| 29 | +// 分配 | |
| 30 | +export function supplierQuotationDealAssignApi(data) { | |
| 31 | + return request({ | |
| 32 | + url: '/procurement/supplierQuotationDeal/assign', | |
| 33 | + method: 'put', | |
| 34 | + data, | |
| 35 | + contentType: ContentTypeEnum.JSON | |
| 36 | + }) | |
| 37 | +} | ... | ... |
| 1 | +import request from '@/utils/request' | |
| 2 | +import { ContentTypeEnum } from '@/utils/httpEnum' | |
| 3 | + | |
| 4 | +export function supplierQuotationProjectQueryApi(params) { | |
| 5 | + return request({ | |
| 6 | + url: '/procurement/supplierQuotationProject/query', | |
| 7 | + method: 'get', | |
| 8 | + params | |
| 9 | + }) | |
| 10 | +} | |
| 11 | + | |
| 12 | +// 新增报价项目 | |
| 13 | +export function supplierQuotationProjectSaveApi(data) { | |
| 14 | + return request({ | |
| 15 | + url: '/procurement/supplierQuotationProject', | |
| 16 | + method: 'post', | |
| 17 | + data, | |
| 18 | + contentType: ContentTypeEnum.JSON | |
| 19 | + }) | |
| 20 | +} | |
| 21 | + | |
| 22 | +// 详情 | |
| 23 | +export function supplierQuotationProjectDetailApi(params) { | |
| 24 | + return request({ | |
| 25 | + url: '/procurement/supplierQuotationProject', | |
| 26 | + method: 'get', | |
| 27 | + params | |
| 28 | + }) | |
| 29 | +} | |
| 30 | + | |
| 31 | +// 修改 | |
| 32 | +export function supplierQuotationProjectUpdateApi(data) { | |
| 33 | + return request({ | |
| 34 | + url: '/procurement/supplierQuotationProject', | |
| 35 | + method: 'put', | |
| 36 | + data, | |
| 37 | + contentType: ContentTypeEnum.JSON | |
| 38 | + }) | |
| 39 | +} | |
| 40 | + | |
| 41 | +// 报价 | |
| 42 | +export function supplierQuotationProjectQuoteApi(data) { | |
| 43 | + return request({ | |
| 44 | + url: '/procurement/supplierQuotationProjectQuote', | |
| 45 | + method: 'post', | |
| 46 | + data, | |
| 47 | + contentType: ContentTypeEnum.JSON | |
| 48 | + }) | |
| 49 | +} | |
| 50 | + | |
| 51 | +// 发布(更新状态) | |
| 52 | +export function supplierQuotationProjectUpdateStatusApi(params) { | |
| 53 | + return request({ | |
| 54 | + url: '/procurement/supplierQuotationProject/updateStatus', | |
| 55 | + method: 'get', | |
| 56 | + params | |
| 57 | + }) | |
| 58 | +} | ... | ... |
| ... | ... | @@ -276,6 +276,143 @@ |
| 276 | 276 | } |
| 277 | 277 | }, |
| 278 | 278 | { |
| 279 | + "path": "pages/sales-price/index", | |
| 280 | + "style": { | |
| 281 | + "navigationBarTitleText": "业务员报价单", | |
| 282 | + "navigationBarBackgroundColor": "#ffffff", | |
| 283 | + "navigationBarTextStyle": "black" | |
| 284 | + } | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "path": "pages/sales-price/add", | |
| 288 | + "style": { | |
| 289 | + "navigationBarTitleText": "新增业务员报价单", | |
| 290 | + "navigationBarBackgroundColor": "#ffffff", | |
| 291 | + "navigationBarTextStyle": "black" | |
| 292 | + } | |
| 293 | + }, | |
| 294 | + { | |
| 295 | + "path": "pages/sales-price/modify", | |
| 296 | + "style": { | |
| 297 | + "navigationBarTitleText": "编辑业务员报价单", | |
| 298 | + "navigationBarBackgroundColor": "#ffffff", | |
| 299 | + "navigationBarTextStyle": "black" | |
| 300 | + } | |
| 301 | + }, | |
| 302 | + { | |
| 303 | + "path": "pages/sales-price/detail", | |
| 304 | + "style": { | |
| 305 | + "navigationBarTitleText": "查看业务员报价单", | |
| 306 | + "navigationBarBackgroundColor": "#ffffff", | |
| 307 | + "navigationBarTextStyle": "black" | |
| 308 | + } | |
| 309 | + }, | |
| 310 | + { | |
| 311 | + "path": "pages/sales-price/copy", | |
| 312 | + "style": { | |
| 313 | + "navigationBarTitleText": "复制业务员报价单", | |
| 314 | + "navigationBarBackgroundColor": "#ffffff", | |
| 315 | + "navigationBarTextStyle": "black" | |
| 316 | + } | |
| 317 | + }, | |
| 318 | + | |
| 319 | + { | |
| 320 | + "path": "pages/supplier-price/index", | |
| 321 | + "style": { | |
| 322 | + "navigationBarTitleText": "报价项目", | |
| 323 | + "navigationBarBackgroundColor": "#ffffff", | |
| 324 | + "navigationBarTextStyle": "black" | |
| 325 | + } | |
| 326 | + }, | |
| 327 | + { | |
| 328 | + "path": "pages/supplier-price/add", | |
| 329 | + "style": { | |
| 330 | + "navigationBarTitleText": "新增报价项目", | |
| 331 | + "navigationBarBackgroundColor": "#ffffff", | |
| 332 | + "navigationBarTextStyle": "black" | |
| 333 | + } | |
| 334 | + }, | |
| 335 | + { | |
| 336 | + "path": "pages/supplier-price/detail", | |
| 337 | + "style": { | |
| 338 | + "navigationBarTitleText": "查看报价项目", | |
| 339 | + "navigationBarBackgroundColor": "#ffffff", | |
| 340 | + "navigationBarTextStyle": "black" | |
| 341 | + } | |
| 342 | + }, | |
| 343 | + { | |
| 344 | + "path": "pages/supplier-price/modify", | |
| 345 | + "style": { | |
| 346 | + "navigationBarTitleText": "编辑报价项目", | |
| 347 | + "navigationBarBackgroundColor": "#ffffff", | |
| 348 | + "navigationBarTextStyle": "black" | |
| 349 | + } | |
| 350 | + }, | |
| 351 | + { | |
| 352 | + "path": "pages/supplier-price/quote", | |
| 353 | + "style": { | |
| 354 | + "navigationBarTitleText": "报价", | |
| 355 | + "navigationBarBackgroundColor": "#ffffff", | |
| 356 | + "navigationBarTextStyle": "black" | |
| 357 | + } | |
| 358 | + }, | |
| 359 | + { | |
| 360 | + "path": "pages/quote-manage/index", | |
| 361 | + "style": { | |
| 362 | + "navigationBarTitleText": "报价管理", | |
| 363 | + "navigationBarBackgroundColor": "#ffffff", | |
| 364 | + "navigationBarTextStyle": "black" | |
| 365 | + } | |
| 366 | + }, | |
| 367 | + { | |
| 368 | + "path": "pages/quote-manage/detail", | |
| 369 | + "style": { | |
| 370 | + "navigationBarTitleText": "报价管理详情", | |
| 371 | + "navigationBarBackgroundColor": "#ffffff", | |
| 372 | + "navigationBarTextStyle": "black" | |
| 373 | + } | |
| 374 | + }, | |
| 375 | + { | |
| 376 | + "path": "pages/quote-manage/assign", | |
| 377 | + "style": { | |
| 378 | + "navigationBarTitleText": "分配负责人", | |
| 379 | + "navigationBarBackgroundColor": "#ffffff", | |
| 380 | + "navigationBarTextStyle": "black" | |
| 381 | + } | |
| 382 | + }, | |
| 383 | + { | |
| 384 | + "path": "pages/peer-info/index", | |
| 385 | + "style": { | |
| 386 | + "navigationBarTitleText": "同行信息", | |
| 387 | + "navigationBarBackgroundColor": "#ffffff", | |
| 388 | + "navigationBarTextStyle": "black" | |
| 389 | + } | |
| 390 | + }, | |
| 391 | + { | |
| 392 | + "path": "pages/peer-info/add", | |
| 393 | + "style": { | |
| 394 | + "navigationBarTitleText": "新增同行信息", | |
| 395 | + "navigationBarBackgroundColor": "#ffffff", | |
| 396 | + "navigationBarTextStyle": "black" | |
| 397 | + } | |
| 398 | + }, | |
| 399 | + { | |
| 400 | + "path": "pages/peer-info/modify", | |
| 401 | + "style": { | |
| 402 | + "navigationBarTitleText": "编辑同行信息", | |
| 403 | + "navigationBarBackgroundColor": "#ffffff", | |
| 404 | + "navigationBarTextStyle": "black" | |
| 405 | + } | |
| 406 | + }, | |
| 407 | + { | |
| 408 | + "path": "pages/peer-info/detail", | |
| 409 | + "style": { | |
| 410 | + "navigationBarTitleText": "查看同行信息", | |
| 411 | + "navigationBarBackgroundColor": "#ffffff", | |
| 412 | + "navigationBarTextStyle": "black" | |
| 413 | + } | |
| 414 | + }, | |
| 415 | + { | |
| 279 | 416 | "path": "pages/order_list/index", |
| 280 | 417 | "style": { |
| 281 | 418 | "navigationBarTitleText": "订货单列表", | ... | ... |
| ... | ... | @@ -94,7 +94,7 @@ |
| 94 | 94 | <template v-slot:footer> |
| 95 | 95 | <uni-easyinput v-model="item.unitPrice" type="digit" :inputBorder="false" |
| 96 | 96 | placeholder="请输入单价" @input="onImmediateChange(idx)" |
| 97 | - @blur="onNumberBlur(idx, 'unitPrice', 0)" /> | |
| 97 | + @blur="onNumberBlur(idx, 'unitPrice', 9)" /> | |
| 98 | 98 | </template> |
| 99 | 99 | </uni-list-item> |
| 100 | 100 | <!-- <uni-list-item title="不含税金额"> | ... | ... |
| ... | ... | @@ -86,7 +86,7 @@ |
| 86 | 86 | </uni-list-item> |
| 87 | 87 | <uni-list-item title="单价"> |
| 88 | 88 | <template v-slot:footer> |
| 89 | - <uni-easyinput v-model="item.unitPrice" type="digit" :inputBorder="false" placeholder="请输入单价" @input="onImmediateChange(idx)" @blur="onNumberBlur(idx, 'unitPrice', 0)" /> | |
| 89 | + <uni-easyinput v-model="item.unitPrice" type="digit" :inputBorder="false" placeholder="请输入单价" @input="onImmediateChange(idx)" @blur="onNumberBlur(idx, 'unitPrice', 9)" /> | |
| 90 | 90 | </template> |
| 91 | 91 | </uni-list-item> |
| 92 | 92 | <uni-list-item title="不含税金额"> | ... | ... |
| ... | ... | @@ -152,12 +152,35 @@ export default { |
| 152 | 152 | }, |
| 153 | 153 | { |
| 154 | 154 | text: '外贸客户资信管理', |
| 155 | - icon: '/static/images/index/order_list.png', | |
| 155 | + icon: '/static/images/index/change_list.png', | |
| 156 | 156 | link: '/pages/foreign_trade/index', |
| 157 | 157 | name: 'ForeignTrade' |
| 158 | + }, | |
| 159 | + { | |
| 160 | + text: '业务员报价单', | |
| 161 | + icon: '/static/images/index/revoke_list.png', | |
| 162 | + link: '/pages/sales-price/index', | |
| 163 | + name: 'SalesPrice' | |
| 164 | + }, | |
| 165 | + { | |
| 166 | + text: '报价项目', | |
| 167 | + icon: '/static/images/index/invoice.png', | |
| 168 | + link: '/pages/supplier-price/index', | |
| 169 | + name: 'SupplierPrice' | |
| 170 | + }, | |
| 171 | + { | |
| 172 | + text: '同行信息', | |
| 173 | + icon: '/static/images/index/draft_order.png', | |
| 174 | + link: '/pages/peer-info/index', | |
| 175 | + name: 'PeerInfo' | |
| 176 | + }, | |
| 177 | + { | |
| 178 | + text: '报价管理', | |
| 179 | + icon: '/static/images/index/delay_invoice.png', | |
| 180 | + link: '/pages/quote-manage/index', | |
| 181 | + name: 'QuoteManage' | |
| 158 | 182 | } |
| 159 | 183 | ] |
| 160 | - | |
| 161 | 184 | }, |
| 162 | 185 | { |
| 163 | 186 | title: '订货单管理', | ... | ... |
pages/peer-info/add.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <uni-list-item> | |
| 6 | + <template v-slot:body> | |
| 7 | + <view class="item-title"><text class="required">*</text><text>同行名称</text></view> | |
| 8 | + </template> | |
| 9 | + <template v-slot:footer> | |
| 10 | + <uni-easyinput v-model="form.peerName" placeholder="请输入同行名称" :inputBorder="false" /> | |
| 11 | + </template> | |
| 12 | + </uni-list-item> | |
| 13 | + | |
| 14 | + <uni-list-item class="select-item" :class="form.peerProductName ? 'is-filled' : 'is-empty'" clickable | |
| 15 | + @click="openSheet('peerProductId')" :rightText="displayLabel('peerProductName')" showArrow> | |
| 16 | + <template v-slot:body> | |
| 17 | + <view class="item-title"><text class="required">*</text><text>同行品种</text></view> | |
| 18 | + </template> | |
| 19 | + </uni-list-item> | |
| 20 | + | |
| 21 | + <uni-list-item> | |
| 22 | + <template v-slot:body> | |
| 23 | + <view class="item-title"><text class="required">*</text><text>同行报价</text></view> | |
| 24 | + </template> | |
| 25 | + <template v-slot:footer> | |
| 26 | + <uni-easyinput type="digit" v-model="form.peerPrice" placeholder="请输入同行报价" :inputBorder="false" /> | |
| 27 | + </template> | |
| 28 | + </uni-list-item> | |
| 29 | + </uni-list> | |
| 30 | + </scroll-view> | |
| 31 | + | |
| 32 | + <view class="footer"> | |
| 33 | + <button class="btn submit" type="primary" :disabled="submitting" @click="onSubmit">{{ submitting ? '提交中...' : '提交' }}</button> | |
| 34 | + </view> | |
| 35 | + | |
| 36 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value" | |
| 37 | + @confirm="onSheetConfirm" /> | |
| 38 | + </view> | |
| 39 | +</template> | |
| 40 | + | |
| 41 | +<script> | |
| 42 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 43 | +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 44 | +import { salesmanQuotationPeerSaveApi } from '@/api/procure-manage/salesmanQuotationPeer.js' | |
| 45 | + | |
| 46 | +export default { | |
| 47 | + name: 'PeerInfoAdd', | |
| 48 | + components: { SingleSelectSheet }, | |
| 49 | + data() { | |
| 50 | + return { | |
| 51 | + submitting: false, | |
| 52 | + productVarietyList: [], | |
| 53 | + sheet: { visible: false, title: '请选择', options: [], field: '', value: '' }, | |
| 54 | + form: { | |
| 55 | + peerName: '', | |
| 56 | + peerProductId: '', | |
| 57 | + peerProductName: '', | |
| 58 | + peerPrice: '' | |
| 59 | + } | |
| 60 | + } | |
| 61 | + }, | |
| 62 | + created() { | |
| 63 | + this.loadProductVariety() | |
| 64 | + }, | |
| 65 | + methods: { | |
| 66 | + async loadProductVariety() { | |
| 67 | + try { | |
| 68 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 69 | + const _data = res && res.data ? res.data : {} | |
| 70 | + const list = _data.datas || _data.list || _data.records || [] | |
| 71 | + this.productVarietyList = list.map(it => ({ | |
| 72 | + label: it.productName || it.name || '', | |
| 73 | + value: it.productId || it.id || '' | |
| 74 | + })).filter(it => it.value) | |
| 75 | + } catch (e) { | |
| 76 | + this.productVarietyList = [] | |
| 77 | + } | |
| 78 | + }, | |
| 79 | + displayLabel(field) { | |
| 80 | + const map = { peerProductName: '请选择同行品种' } | |
| 81 | + const val = this.form[field] | |
| 82 | + return val ? String(val) : (map[field] || '请选择') | |
| 83 | + }, | |
| 84 | + openSheet(field) { | |
| 85 | + if (field === 'peerProductId') { | |
| 86 | + const current = this.form.peerProductId | |
| 87 | + const match = (this.productVarietyList || []).find(o => String(o.value) === String(current)) | |
| 88 | + this.sheet = { | |
| 89 | + visible: true, | |
| 90 | + title: '同行品种', | |
| 91 | + options: this.productVarietyList || [], | |
| 92 | + field, | |
| 93 | + value: match ? match.value : '' | |
| 94 | + } | |
| 95 | + } | |
| 96 | + }, | |
| 97 | + onSheetConfirm({ value, label }) { | |
| 98 | + const field = this.sheet.field | |
| 99 | + if (!field) return | |
| 100 | + if (field === 'peerProductId') { | |
| 101 | + this.form.peerProductId = value || '' | |
| 102 | + this.form.peerProductName = label || '' | |
| 103 | + } | |
| 104 | + }, | |
| 105 | + validate() { | |
| 106 | + const checks = [ | |
| 107 | + { key: 'peerName', label: '同行名称' }, | |
| 108 | + { key: 'peerProductId', label: '同行品种' }, | |
| 109 | + { key: 'peerPrice', label: '同行报价' } | |
| 110 | + ] | |
| 111 | + for (const it of checks) { | |
| 112 | + const val = this.form[it.key] | |
| 113 | + const empty = (val === undefined || val === null || (typeof val === 'string' && String(val).trim() === '')) | |
| 114 | + if (empty) { | |
| 115 | + uni.showToast({ title: `${it.label}必填`, icon: 'none' }) | |
| 116 | + return false | |
| 117 | + } | |
| 118 | + } | |
| 119 | + return true | |
| 120 | + }, | |
| 121 | + buildPayload() { | |
| 122 | + return { | |
| 123 | + peerName: this.form.peerName, | |
| 124 | + peerProductId: this.form.peerProductId, | |
| 125 | + peerPrice: this.form.peerPrice | |
| 126 | + } | |
| 127 | + }, | |
| 128 | + async onSubmit() { | |
| 129 | + if (this.submitting) return | |
| 130 | + if (!this.validate()) return | |
| 131 | + this.submitting = true | |
| 132 | + const payload = this.buildPayload() | |
| 133 | + try { | |
| 134 | + await salesmanQuotationPeerSaveApi(payload) | |
| 135 | + uni.showToast({ title: '保存成功', icon: 'success' }) | |
| 136 | + try { uni.setStorageSync('PEER_INFO_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 137 | + setTimeout(() => { uni.redirectTo({ url: '/pages/peer-info/index' }) }, 300) | |
| 138 | + } catch (e) { | |
| 139 | + uni.showToast({ title: (e && e.msg) || '保存失败', icon: 'none' }) | |
| 140 | + } finally { | |
| 141 | + this.submitting = false | |
| 142 | + } | |
| 143 | + } | |
| 144 | + } | |
| 145 | +} | |
| 146 | +</script> | |
| 147 | + | |
| 148 | +<style lang="scss" scoped> | |
| 149 | +.page { | |
| 150 | + display: flex; | |
| 151 | + flex-direction: column; | |
| 152 | + height: 100%; | |
| 153 | +} | |
| 154 | + | |
| 155 | +.scroll { | |
| 156 | + flex: 1; | |
| 157 | + padding: 0rpx 0 160rpx; | |
| 158 | + background-color: #f3f3f3; | |
| 159 | +} | |
| 160 | + | |
| 161 | +.footer { | |
| 162 | + position: fixed; | |
| 163 | + left: 0; | |
| 164 | + right: 0; | |
| 165 | + bottom: 0; | |
| 166 | + padding: 32rpx; | |
| 167 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 168 | + background: #fff; | |
| 169 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 170 | + z-index: 10; | |
| 171 | + | |
| 172 | + .btn { | |
| 173 | + height: 80rpx; | |
| 174 | + line-height: 80rpx; | |
| 175 | + border-radius: 12rpx; | |
| 176 | + font-size: 32rpx; | |
| 177 | + } | |
| 178 | + | |
| 179 | + .submit { | |
| 180 | + background: $theme-primary; | |
| 181 | + color: #fff; | |
| 182 | + } | |
| 183 | +} | |
| 184 | + | |
| 185 | +.title-header { | |
| 186 | + display: flex; | |
| 187 | + align-items: center; | |
| 188 | + padding: 24rpx 32rpx 12rpx; | |
| 189 | + background-color: #ffffff; | |
| 190 | + | |
| 191 | + .title-header_icon { | |
| 192 | + width: 36rpx; | |
| 193 | + height: 36rpx; | |
| 194 | + margin-right: 12rpx; | |
| 195 | + } | |
| 196 | + | |
| 197 | + span { | |
| 198 | + font-size: 34rpx; | |
| 199 | + font-weight: 600; | |
| 200 | + color: rgba(0, 0, 0, 0.9); | |
| 201 | + } | |
| 202 | +} | |
| 203 | + | |
| 204 | +.mgb10 { | |
| 205 | + margin-bottom: 20rpx; | |
| 206 | +} | |
| 207 | + | |
| 208 | +::v-deep .uni-list { | |
| 209 | + .uni-easyinput { | |
| 210 | + display: flex; | |
| 211 | + | |
| 212 | + .uni-input-input { | |
| 213 | + color: rgba(0, 0, 0, 0.9); | |
| 214 | + } | |
| 215 | + } | |
| 216 | + | |
| 217 | + .uni-input-placeholder { | |
| 218 | + z-index: 1; | |
| 219 | + } | |
| 220 | + | |
| 221 | + .uni-input-input { | |
| 222 | + background-color: #ffffff; | |
| 223 | + } | |
| 224 | + | |
| 225 | + background: transparent; | |
| 226 | + | |
| 227 | + &-item { | |
| 228 | + &__extra-text { | |
| 229 | + font-size: 32rpx; | |
| 230 | + } | |
| 231 | + | |
| 232 | + &__content-title { | |
| 233 | + font-size: 32rpx; | |
| 234 | + color: rgba(0, 0, 0, 0.9); | |
| 235 | + } | |
| 236 | + | |
| 237 | + &__container { | |
| 238 | + padding: 32rpx; | |
| 239 | + | |
| 240 | + .uni-easyinput { | |
| 241 | + &__placeholder-class { | |
| 242 | + font-size: 32rpx; | |
| 243 | + color: rgba(0, 0, 0, 0.4); | |
| 244 | + } | |
| 245 | + | |
| 246 | + &__content { | |
| 247 | + border: none; | |
| 248 | + background-color: #ffffff !important; | |
| 249 | + | |
| 250 | + &-input { | |
| 251 | + padding-left: 0 !important; | |
| 252 | + height: 48rpx; | |
| 253 | + line-height: 48rpx; | |
| 254 | + font-size: 32rpx; | |
| 255 | + } | |
| 256 | + | |
| 257 | + .content-clear-icon { | |
| 258 | + font-size: 44rpx !important; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + } | |
| 262 | + | |
| 263 | + .item-title, | |
| 264 | + .uni-list-item__content { | |
| 265 | + flex: none; | |
| 266 | + min-height: 48rpx; | |
| 267 | + line-height: 48rpx; | |
| 268 | + font-size: 32rpx; | |
| 269 | + position: relative; | |
| 270 | + width: 210rpx; | |
| 271 | + margin-right: 32rpx; | |
| 272 | + color: rgba(0, 0, 0, 0.9); | |
| 273 | + | |
| 274 | + .required { | |
| 275 | + color: red; | |
| 276 | + position: absolute; | |
| 277 | + top: 50%; | |
| 278 | + transform: translateY(-50%); | |
| 279 | + left: -16rpx; | |
| 280 | + } | |
| 281 | + } | |
| 282 | + } | |
| 283 | + | |
| 284 | + &.select-item { | |
| 285 | + &.is-empty { | |
| 286 | + .uni-list-item__extra-text { | |
| 287 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 288 | + } | |
| 289 | + } | |
| 290 | + | |
| 291 | + &.is-filled { | |
| 292 | + .uni-list-item__extra-text { | |
| 293 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 294 | + } | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + &.mgb10 { | |
| 299 | + margin-bottom: 20rpx; | |
| 300 | + } | |
| 301 | + } | |
| 302 | +} | |
| 303 | +</style> | ... | ... |
pages/peer-info/detail.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <view class="detail-page"> | |
| 5 | + <view class="section"> | |
| 6 | + <text class="row company">{{ safeText(form.peerName) }}</text> | |
| 7 | + <view class="row"><text class="label">同行品种</text><text class="value">{{ safeText(form.peerProductName) }}</text></view> | |
| 8 | + <view class="row"><text class="label">同行报价</text><text class="value">{{ safeText(form.peerPrice) }}</text></view> | |
| 9 | + <view class="row"><text class="label">创建人</text><text class="value">{{ safeText(form.createBy) }}</text></view> | |
| 10 | + <view class="row"><text class="label">创建时间</text><text class="value">{{ formatDateTime(form.createTime) }}</text></view> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + </scroll-view> | |
| 14 | + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" /> | |
| 15 | + </view> | |
| 16 | +</template> | |
| 17 | + | |
| 18 | +<script> | |
| 19 | +import DetailButtons from '@/components/detail-buttons/index.vue' | |
| 20 | +import { salesmanQuotationPeerGetApi, salesmanQuotationPeerDeleteApi } from '@/api/procure-manage/salesmanQuotationPeer.js' | |
| 21 | + | |
| 22 | +export default { | |
| 23 | + name: 'PeerInfoDetail', | |
| 24 | + components: { DetailButtons }, | |
| 25 | + data() { | |
| 26 | + return { | |
| 27 | + id: '', | |
| 28 | + loading: false, | |
| 29 | + form: {}, | |
| 30 | + buttons: [ | |
| 31 | + { text: '编辑', visible: true, variant: 'outline', event: 'edit' }, | |
| 32 | + { text: '删除', visible: true, variant: 'danger', event: 'delete' } | |
| 33 | + ] | |
| 34 | + } | |
| 35 | + }, | |
| 36 | + computed: { | |
| 37 | + displayButtons() { | |
| 38 | + const isToday = this.isToday(this.form.createTime); | |
| 39 | + return [ | |
| 40 | + { ...this.buttons[0], visible: isToday && this.$auth.hasPermi('procure-manage:peer-info:modify') }, | |
| 41 | + { ...this.buttons[1], visible: isToday && this.$auth.hasPermi('procure-manage:peer-info:delete') } | |
| 42 | + ] | |
| 43 | + } | |
| 44 | + }, | |
| 45 | + onShow() { | |
| 46 | + const options = (this.$route && this.$route.query) ? this.$route.query : {} | |
| 47 | + const id = options && options.id ? String(options.id) : '' | |
| 48 | + if (id && id !== this.id) { | |
| 49 | + this.id = id | |
| 50 | + } | |
| 51 | + if (this.id) this.loadDetail() | |
| 52 | + }, | |
| 53 | + methods: { | |
| 54 | + isToday(dateStr) { | |
| 55 | + if (!dateStr) return false; | |
| 56 | + const date = new Date(dateStr); | |
| 57 | + const today = new Date(); | |
| 58 | + return ( | |
| 59 | + date.getFullYear() === today.getFullYear() && | |
| 60 | + date.getMonth() === today.getMonth() && | |
| 61 | + date.getDate() === today.getDate() | |
| 62 | + ); | |
| 63 | + }, | |
| 64 | + safeText(v) { | |
| 65 | + const s = v == null ? '' : String(v) | |
| 66 | + return s.trim() ? s : '' | |
| 67 | + }, | |
| 68 | + formatDateTime(v) { | |
| 69 | + const s = v == null ? '' : String(v).trim() | |
| 70 | + if (!s) return '-' | |
| 71 | + if (s.length >= 16) return s.slice(0, 16) | |
| 72 | + if (s.length >= 10) return s.slice(0, 10) | |
| 73 | + return s | |
| 74 | + }, | |
| 75 | + handleButtonClick(btn) { | |
| 76 | + if (!btn || btn.disabled) return | |
| 77 | + const e = btn.event || '' | |
| 78 | + if (e === 'edit') return this.onEdit() | |
| 79 | + if (e === 'delete') return this.onDelete() | |
| 80 | + }, | |
| 81 | + onEdit() { | |
| 82 | + uni.navigateTo({ url: '/pages/peer-info/modify?id=' + this.id }) | |
| 83 | + }, | |
| 84 | + onDelete() { | |
| 85 | + const id = this.id || (this.form && this.form.id) || '' | |
| 86 | + if (!id) return | |
| 87 | + uni.showModal({ | |
| 88 | + title: '系统提示', | |
| 89 | + content: '是否确定删除该同行信息?', | |
| 90 | + confirmText: '确定', | |
| 91 | + cancelText: '取消', | |
| 92 | + success: (res) => { | |
| 93 | + if (res && res.confirm) { | |
| 94 | + salesmanQuotationPeerDeleteApi(id).then(() => { | |
| 95 | + uni.showToast({ title: '已删除', icon: 'none' }) | |
| 96 | + try { uni.setStorageSync('PEER_INFO_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 97 | + setTimeout(() => { uni.redirectTo({ url: '/pages/peer-info/index' }) }, 300) | |
| 98 | + }).catch((e) => { | |
| 99 | + uni.showToast({ title: (e && e.msg) || '删除失败', icon: 'none' }) | |
| 100 | + }) | |
| 101 | + } | |
| 102 | + } | |
| 103 | + }) | |
| 104 | + }, | |
| 105 | + async loadDetail() { | |
| 106 | + this.loading = true | |
| 107 | + try { | |
| 108 | + const res = await salesmanQuotationPeerGetApi(this.id) | |
| 109 | + this.form = (res && res.data) ? res.data : {} | |
| 110 | + } catch (e) { | |
| 111 | + this.form = {} | |
| 112 | + uni.showToast({ title: '详情加载失败', icon: 'none' }) | |
| 113 | + } finally { | |
| 114 | + this.loading = false | |
| 115 | + } | |
| 116 | + } | |
| 117 | + } | |
| 118 | +} | |
| 119 | +</script> | |
| 120 | + | |
| 121 | +<style lang="scss" scoped> | |
| 122 | +.page { | |
| 123 | + display: flex; | |
| 124 | + flex-direction: column; | |
| 125 | + height: 100%; | |
| 126 | +} | |
| 127 | + | |
| 128 | +.scroll { | |
| 129 | + flex: 1; | |
| 130 | + padding: 8rpx 0 144rpx; | |
| 131 | +} | |
| 132 | + | |
| 133 | +.detail-page { | |
| 134 | + background: #f3f3f3; | |
| 135 | +} | |
| 136 | + | |
| 137 | +.section { | |
| 138 | + padding: 32rpx; | |
| 139 | + background: #fff; | |
| 140 | + margin-bottom: 20rpx; | |
| 141 | +} | |
| 142 | + | |
| 143 | +.row { | |
| 144 | + display: flex; | |
| 145 | + margin-bottom: 28rpx; | |
| 146 | + | |
| 147 | + &:last-child { | |
| 148 | + margin-bottom: 0; | |
| 149 | + } | |
| 150 | + | |
| 151 | + &.company { | |
| 152 | + font-size: 36rpx; | |
| 153 | + font-weight: 600; | |
| 154 | + color: rgba(0, 0, 0, 0.9); | |
| 155 | + padding-top: 8rpx; | |
| 156 | + line-height: 50rpx; | |
| 157 | + margin-bottom: 28rpx; | |
| 158 | + } | |
| 159 | + | |
| 160 | + .label { | |
| 161 | + max-width: 420rpx; | |
| 162 | + margin-right: 20rpx; | |
| 163 | + line-height: 32rpx; | |
| 164 | + font-size: 28rpx; | |
| 165 | + color: rgba(0, 0, 0, 0.6); | |
| 166 | + } | |
| 167 | + | |
| 168 | + .value { | |
| 169 | + flex: 1; | |
| 170 | + line-height: 32rpx; | |
| 171 | + font-size: 28rpx; | |
| 172 | + color: rgba(0, 0, 0, 0.9); | |
| 173 | + text-align: right; | |
| 174 | + word-break: break-all; | |
| 175 | + | |
| 176 | + &.pre { | |
| 177 | + white-space: pre-wrap; | |
| 178 | + } | |
| 179 | + } | |
| 180 | +} | |
| 181 | +</style> | ... | ... |
pages/peer-info/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <view class="dev-list-fixed"> | |
| 4 | + <view class="search-row"> | |
| 5 | + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入同行名称" clearButton="auto" | |
| 6 | + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search" | |
| 7 | + @input="onSearchInput" /> | |
| 8 | + <view class="tool-icons"> | |
| 9 | + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" /> | |
| 10 | + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" /> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + </view> | |
| 14 | + | |
| 15 | + <view class="list-box"> | |
| 16 | + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id" | |
| 17 | + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError"> | |
| 18 | + <template v-slot="{ item }"> | |
| 19 | + <view class="card" @click.stop="onCardClick(item)"> | |
| 20 | + <view class="card-header"> | |
| 21 | + <text class="title omit2">{{ item.peerName || '-' }}</text> | |
| 22 | + </view> | |
| 23 | + <view class="info-row"> | |
| 24 | + <text>同行品种</text><text>{{ item.peerProductName || '-' }}</text> | |
| 25 | + </view> | |
| 26 | + <view class="info-row"> | |
| 27 | + <text>同行报价</text><text>{{ item.peerPrice != null ? item.peerPrice : '-' }}</text> | |
| 28 | + </view> | |
| 29 | + <view class="info-row"> | |
| 30 | + <text>创建人</text><text>{{ item.createBy || '-' }}</text> | |
| 31 | + </view> | |
| 32 | + <view class="info-row"> | |
| 33 | + <text>创建时间</text><text>{{ formatDateTime(item.createTime) }}</text> | |
| 34 | + </view> | |
| 35 | + </view> | |
| 36 | + </template> | |
| 37 | + </card-list> | |
| 38 | + </view> | |
| 39 | + | |
| 40 | + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset" | |
| 41 | + @confirm="onFilterConfirm"> | |
| 42 | + <template v-slot="{ model }"> | |
| 43 | + <view class="filter-form"> | |
| 44 | + <view class="form-item"> | |
| 45 | + <view class="label">同行名称</view> | |
| 46 | + <uni-easyinput v-model="model.peerName" placeholder="请输入同行名称" :inputBorder="false" | |
| 47 | + placeholderStyle="font-size:14px" @input="onPeerNameInput" /> | |
| 48 | + </view> | |
| 49 | + <view class="form-item"> | |
| 50 | + <view class="label">同行品种名称</view> | |
| 51 | + <uni-easyinput v-model="model.peerProductName" placeholder="请输入同行品种名称" :inputBorder="false" | |
| 52 | + placeholderStyle="font-size:14px" @input="onPeerProductNameInput" /> | |
| 53 | + </view> | |
| 54 | + <view class="form-item"> | |
| 55 | + <view class="label">创建日期</view> | |
| 56 | + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" /> | |
| 57 | + </view> | |
| 58 | + </view> | |
| 59 | + </template> | |
| 60 | + </filter-modal> | |
| 61 | + </view> | |
| 62 | +</template> | |
| 63 | + | |
| 64 | +<script> | |
| 65 | +import CardList from '@/components/card/index.vue' | |
| 66 | +import FilterModal from '@/components/filter/index.vue' | |
| 67 | +import { salesmanQuotationPeerQueryApi } from '@/api/procure-manage/salesmanQuotationPeer.js' | |
| 68 | + | |
| 69 | +export default { | |
| 70 | + name: 'PeerInfoList', | |
| 71 | + components: { CardList, FilterModal }, | |
| 72 | + data() { | |
| 73 | + return { | |
| 74 | + searchKeyword: '', | |
| 75 | + searchKeywordDebounced: '', | |
| 76 | + searchDebounceTimer: null, | |
| 77 | + query: { | |
| 78 | + peerName: '', | |
| 79 | + peerProductName: '', | |
| 80 | + dateRange: [] | |
| 81 | + }, | |
| 82 | + extraParams: {}, | |
| 83 | + currentItems: [], | |
| 84 | + filterVisible: false, | |
| 85 | + filterForm: { | |
| 86 | + peerName: '', | |
| 87 | + peerProductName: '', | |
| 88 | + dateRange: [] | |
| 89 | + } | |
| 90 | + } | |
| 91 | + }, | |
| 92 | + watch: { | |
| 93 | + searchKeywordDebounced(v) { | |
| 94 | + this.query = { ...this.query, peerName: v || '' } | |
| 95 | + } | |
| 96 | + }, | |
| 97 | + onShow() { | |
| 98 | + let needRefresh = '' | |
| 99 | + try { needRefresh = uni.getStorageSync('PEER_INFO_LIST_NEED_REFRESH') } catch (e) {} | |
| 100 | + if (!needRefresh) return | |
| 101 | + try { uni.removeStorageSync('PEER_INFO_LIST_NEED_REFRESH') } catch (e) {} | |
| 102 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) { | |
| 103 | + this.$refs.cardRef.reload() | |
| 104 | + } | |
| 105 | + }, | |
| 106 | + onReachBottom() { | |
| 107 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) { | |
| 108 | + this.$refs.cardRef.onLoadMore() | |
| 109 | + } | |
| 110 | + }, | |
| 111 | + beforeDestroy() { | |
| 112 | + if (this.searchDebounceTimer) { | |
| 113 | + clearTimeout(this.searchDebounceTimer) | |
| 114 | + this.searchDebounceTimer = null | |
| 115 | + } | |
| 116 | + }, | |
| 117 | + methods: { | |
| 118 | + formatDateTime(v) { | |
| 119 | + const s = v == null ? '' : String(v).trim() | |
| 120 | + if (!s) return '-' | |
| 121 | + if (s.length >= 16) return s.slice(0, 16) | |
| 122 | + if (s.length >= 10) return s.slice(0, 10) | |
| 123 | + return s | |
| 124 | + }, | |
| 125 | + onAdd() { | |
| 126 | + uni.navigateTo({ url: '/pages/peer-info/add' }) | |
| 127 | + }, | |
| 128 | + onCardLoaded({ items }) { | |
| 129 | + this.currentItems = items | |
| 130 | + }, | |
| 131 | + onCardError() { | |
| 132 | + uni.showToast({ title: '列表加载失败', icon: 'none' }) | |
| 133 | + }, | |
| 134 | + onSearchInput() { | |
| 135 | + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer) | |
| 136 | + this.searchDebounceTimer = setTimeout(() => { | |
| 137 | + this.searchKeywordDebounced = this.searchKeyword | |
| 138 | + this.searchDebounceTimer = null | |
| 139 | + }, 1200) | |
| 140 | + }, | |
| 141 | + search(e) { | |
| 142 | + const val = e && e.value != null ? e.value : this.searchKeyword | |
| 143 | + this.searchKeyword = val | |
| 144 | + this.searchKeywordDebounced = val | |
| 145 | + }, | |
| 146 | + openFilter() { | |
| 147 | + this.filterForm = { | |
| 148 | + peerName: this.query.peerName || '', | |
| 149 | + peerProductName: this.query.peerProductName || '', | |
| 150 | + dateRange: Array.isArray(this.query.dateRange) ? this.query.dateRange.slice(0) : [] | |
| 151 | + } | |
| 152 | + this.filterVisible = true | |
| 153 | + }, | |
| 154 | + onFilterReset(payload) { | |
| 155 | + this.filterForm = payload | |
| 156 | + }, | |
| 157 | + onFilterConfirm(payload) { | |
| 158 | + this.query = { | |
| 159 | + peerName: this.searchKeywordDebounced || payload.peerName || '', | |
| 160 | + peerProductName: payload.peerProductName || '', | |
| 161 | + dateRange: Array.isArray(payload.dateRange) ? payload.dateRange : [] | |
| 162 | + } | |
| 163 | + }, | |
| 164 | + onPeerNameInput(val) { | |
| 165 | + this.filterForm.peerName = val | |
| 166 | + }, | |
| 167 | + onPeerProductNameInput(val) { | |
| 168 | + this.filterForm.peerProductName = val | |
| 169 | + }, | |
| 170 | + fetchList({ pageIndex, pageSize, query }) { | |
| 171 | + const q = query || {} | |
| 172 | + const range = Array.isArray(q.dateRange) ? q.dateRange : [] | |
| 173 | + const params = { | |
| 174 | + pageIndex, | |
| 175 | + pageSize, | |
| 176 | + peerName: q.peerName || '', | |
| 177 | + peerProductName: q.peerProductName || '', | |
| 178 | + createDateStart: (range && range.length === 2) ? range[0] : '', | |
| 179 | + createDateEnd: (range && range.length === 2) ? range[1] : '' | |
| 180 | + } | |
| 181 | + return salesmanQuotationPeerQueryApi(params).then(res => { | |
| 182 | + const _data = res && res.data ? res.data : {} | |
| 183 | + let records = _data.datas || _data.list || _data.records || [] | |
| 184 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 185 | + const hasNext = _data.hasNext || false | |
| 186 | + records = records.map(it => ({ ...it })) | |
| 187 | + return { records, totalCount, hasNext } | |
| 188 | + }).catch(() => { | |
| 189 | + this.onCardError() | |
| 190 | + return { records: [], totalCount: 0, hasNext: false } | |
| 191 | + }) | |
| 192 | + }, | |
| 193 | + onCardClick(item) { | |
| 194 | + uni.navigateTo({ url: '/pages/peer-info/detail?id=' + item.id }) | |
| 195 | + } | |
| 196 | + } | |
| 197 | +} | |
| 198 | +</script> | |
| 199 | + | |
| 200 | +<style lang="scss" scoped> | |
| 201 | +.page { | |
| 202 | + display: flex; | |
| 203 | + flex-direction: column; | |
| 204 | + height: 100vh; | |
| 205 | +} | |
| 206 | + | |
| 207 | +.dev-list-fixed { | |
| 208 | + position: fixed; | |
| 209 | + top: 96rpx; | |
| 210 | + left: 0; | |
| 211 | + right: 0; | |
| 212 | + z-index: 2; | |
| 213 | + background: #fff; | |
| 214 | + | |
| 215 | + .search-row { | |
| 216 | + display: flex; | |
| 217 | + align-items: center; | |
| 218 | + padding: 16rpx 32rpx; | |
| 219 | + | |
| 220 | + .uni-searchbar { | |
| 221 | + padding: 0; | |
| 222 | + flex: 1; | |
| 223 | + } | |
| 224 | + | |
| 225 | + .tool-icons { | |
| 226 | + display: flex; | |
| 227 | + | |
| 228 | + .tool-icon { | |
| 229 | + width: 48rpx; | |
| 230 | + height: 48rpx; | |
| 231 | + display: block; | |
| 232 | + margin-left: 32rpx; | |
| 233 | + } | |
| 234 | + } | |
| 235 | + } | |
| 236 | +} | |
| 237 | + | |
| 238 | +::v-deep .uni-searchbar__box { | |
| 239 | + height: 80rpx !important; | |
| 240 | + justify-content: start; | |
| 241 | + | |
| 242 | + .uni-searchbar__box-search-input { | |
| 243 | + font-size: 32rpx !important; | |
| 244 | + } | |
| 245 | +} | |
| 246 | + | |
| 247 | +.list-box { | |
| 248 | + flex: 1; | |
| 249 | + padding-top: 140rpx; | |
| 250 | + | |
| 251 | + .card { | |
| 252 | + position: relative; | |
| 253 | + } | |
| 254 | + | |
| 255 | + .card-header { | |
| 256 | + margin-bottom: 28rpx; | |
| 257 | + position: relative; | |
| 258 | + | |
| 259 | + .title { | |
| 260 | + font-size: 36rpx; | |
| 261 | + font-weight: 600; | |
| 262 | + line-height: 50rpx; | |
| 263 | + color: rgba(0, 0, 0, 0.9); | |
| 264 | + width: 100%; | |
| 265 | + } | |
| 266 | + } | |
| 267 | + | |
| 268 | + .info-row { | |
| 269 | + display: flex; | |
| 270 | + align-items: center; | |
| 271 | + color: rgba(0, 0, 0, 0.6); | |
| 272 | + font-size: 28rpx; | |
| 273 | + margin-bottom: 24rpx; | |
| 274 | + height: 32rpx; | |
| 275 | + | |
| 276 | + &:last-child { | |
| 277 | + margin-bottom: 0; | |
| 278 | + } | |
| 279 | + | |
| 280 | + text { | |
| 281 | + width: 50%; | |
| 282 | + | |
| 283 | + &:last-child { | |
| 284 | + color: rgba(0, 0, 0, 0.9); | |
| 285 | + width: 50%; | |
| 286 | + } | |
| 287 | + } | |
| 288 | + } | |
| 289 | +} | |
| 290 | + | |
| 291 | +.filter-form { | |
| 292 | + .form-item { | |
| 293 | + margin-bottom: 24rpx; | |
| 294 | + } | |
| 295 | + | |
| 296 | + .label { | |
| 297 | + margin-bottom: 20rpx; | |
| 298 | + color: rgba(0, 0, 0, 0.9); | |
| 299 | + height: 44rpx; | |
| 300 | + line-height: 44rpx; | |
| 301 | + font-size: 30rpx; | |
| 302 | + } | |
| 303 | + | |
| 304 | + .uni-easyinput { | |
| 305 | + border: 1rpx solid #f3f3f3; | |
| 306 | + } | |
| 307 | +} | |
| 308 | +</style> | ... | ... |
pages/peer-info/modify.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <view class="title-header"> | |
| 6 | + <image class="title-header_icon" src="/static/images/title.png" /> | |
| 7 | + <span>同行信息</span> | |
| 8 | + </view> | |
| 9 | + | |
| 10 | + <uni-list-item> | |
| 11 | + <template v-slot:body> | |
| 12 | + <view class="item-title"><text class="required">*</text><text>同行名称</text></view> | |
| 13 | + </template> | |
| 14 | + <template v-slot:footer> | |
| 15 | + <uni-easyinput v-model="form.peerName" placeholder="请输入同行名称" :inputBorder="false" /> | |
| 16 | + </template> | |
| 17 | + </uni-list-item> | |
| 18 | + | |
| 19 | + <uni-list-item class="select-item" :class="form.peerProductName ? 'is-filled' : 'is-empty'" clickable | |
| 20 | + @click="openSheet('peerProductId')" :rightText="displayLabel('peerProductName')" showArrow> | |
| 21 | + <template v-slot:body> | |
| 22 | + <view class="item-title"><text class="required">*</text><text>同行品种</text></view> | |
| 23 | + </template> | |
| 24 | + </uni-list-item> | |
| 25 | + | |
| 26 | + <uni-list-item> | |
| 27 | + <template v-slot:body> | |
| 28 | + <view class="item-title"><text class="required">*</text><text>同行报价</text></view> | |
| 29 | + </template> | |
| 30 | + <template v-slot:footer> | |
| 31 | + <uni-easyinput type="digit" v-model="form.peerPrice" placeholder="请输入同行报价" :inputBorder="false" /> | |
| 32 | + </template> | |
| 33 | + </uni-list-item> | |
| 34 | + </uni-list> | |
| 35 | + </scroll-view> | |
| 36 | + | |
| 37 | + <view class="footer"> | |
| 38 | + <button class="btn submit" type="primary" :disabled="submitting" @click="onSubmit">{{ submitting ? '提交中...' : '提交' }}</button> | |
| 39 | + </view> | |
| 40 | + | |
| 41 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value" | |
| 42 | + @confirm="onSheetConfirm" /> | |
| 43 | + </view> | |
| 44 | +</template> | |
| 45 | + | |
| 46 | +<script> | |
| 47 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 48 | +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 49 | +import { salesmanQuotationPeerGetApi, salesmanQuotationPeerUpdateApi } from '@/api/procure-manage/salesmanQuotationPeer.js' | |
| 50 | + | |
| 51 | +export default { | |
| 52 | + name: 'PeerInfoModify', | |
| 53 | + components: { SingleSelectSheet }, | |
| 54 | + data() { | |
| 55 | + return { | |
| 56 | + id: '', | |
| 57 | + submitting: false, | |
| 58 | + loading: false, | |
| 59 | + productVarietyList: [], | |
| 60 | + sheet: { visible: false, title: '请选择', options: [], field: '', value: '' }, | |
| 61 | + form: { | |
| 62 | + id: '', | |
| 63 | + peerName: '', | |
| 64 | + peerProductId: '', | |
| 65 | + peerProductName: '', | |
| 66 | + peerPrice: '' | |
| 67 | + } | |
| 68 | + } | |
| 69 | + }, | |
| 70 | + onLoad(query) { | |
| 71 | + this.id = (query && query.id) ? String(query.id) : '' | |
| 72 | + }, | |
| 73 | + created() { | |
| 74 | + this.loadProductVariety() | |
| 75 | + if (this.id) this.loadDetail() | |
| 76 | + }, | |
| 77 | + methods: { | |
| 78 | + async loadProductVariety() { | |
| 79 | + try { | |
| 80 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 81 | + const _data = res && res.data ? res.data : {} | |
| 82 | + const list = _data.datas || _data.list || _data.records || [] | |
| 83 | + this.productVarietyList = list.map(it => ({ | |
| 84 | + label: it.productName || it.name || '', | |
| 85 | + value: it.productId || it.id || '' | |
| 86 | + })).filter(it => it.value) | |
| 87 | + } catch (e) { | |
| 88 | + this.productVarietyList = [] | |
| 89 | + } | |
| 90 | + }, | |
| 91 | + async loadDetail() { | |
| 92 | + this.loading = true | |
| 93 | + try { | |
| 94 | + const res = await salesmanQuotationPeerGetApi(this.id) | |
| 95 | + const data = (res && res.data) ? res.data : {} | |
| 96 | + this.form = { | |
| 97 | + id: data.id || this.id, | |
| 98 | + peerName: data.peerName || '', | |
| 99 | + peerProductId: data.peerProductId || '', | |
| 100 | + peerProductName: data.peerProductName || this.getOptionLabel(this.productVarietyList, data.peerProductId), | |
| 101 | + peerPrice: data.peerPrice != null ? String(data.peerPrice) : '' | |
| 102 | + } | |
| 103 | + } catch (e) { | |
| 104 | + uni.showToast({ title: '加载失败', icon: 'none' }) | |
| 105 | + } finally { | |
| 106 | + this.loading = false | |
| 107 | + } | |
| 108 | + }, | |
| 109 | + getOptionLabel(options, value) { | |
| 110 | + const v = value != null ? String(value) : '' | |
| 111 | + if (!v) return '' | |
| 112 | + const match = (options || []).find(o => String(o.value) === v) | |
| 113 | + return match ? String(match.label || '') : '' | |
| 114 | + }, | |
| 115 | + displayLabel(field) { | |
| 116 | + const map = { peerProductName: '请选择同行品种' } | |
| 117 | + const val = this.form[field] | |
| 118 | + return val ? String(val) : (map[field] || '请选择') | |
| 119 | + }, | |
| 120 | + openSheet(field) { | |
| 121 | + if (field === 'peerProductId') { | |
| 122 | + const current = this.form.peerProductId | |
| 123 | + const match = (this.productVarietyList || []).find(o => String(o.value) === String(current)) | |
| 124 | + this.sheet = { | |
| 125 | + visible: true, | |
| 126 | + title: '同行品种', | |
| 127 | + options: this.productVarietyList || [], | |
| 128 | + field, | |
| 129 | + value: match ? match.value : '' | |
| 130 | + } | |
| 131 | + } | |
| 132 | + }, | |
| 133 | + onSheetConfirm({ value, label }) { | |
| 134 | + const field = this.sheet.field | |
| 135 | + if (!field) return | |
| 136 | + if (field === 'peerProductId') { | |
| 137 | + this.form.peerProductId = value || '' | |
| 138 | + this.form.peerProductName = label || '' | |
| 139 | + } | |
| 140 | + }, | |
| 141 | + validate() { | |
| 142 | + const checks = [ | |
| 143 | + { key: 'peerName', label: '同行名称' }, | |
| 144 | + { key: 'peerProductId', label: '同行品种' }, | |
| 145 | + { key: 'peerPrice', label: '同行报价' } | |
| 146 | + ] | |
| 147 | + for (const it of checks) { | |
| 148 | + const val = this.form[it.key] | |
| 149 | + const empty = (val === undefined || val === null || (typeof val === 'string' && String(val).trim() === '')) | |
| 150 | + if (empty) { | |
| 151 | + uni.showToast({ title: `${it.label}必填`, icon: 'none' }) | |
| 152 | + return false | |
| 153 | + } | |
| 154 | + } | |
| 155 | + return true | |
| 156 | + }, | |
| 157 | + buildPayload() { | |
| 158 | + return { | |
| 159 | + id: this.form.id, | |
| 160 | + peerName: this.form.peerName, | |
| 161 | + peerProductId: this.form.peerProductId, | |
| 162 | + peerPrice: this.form.peerPrice | |
| 163 | + } | |
| 164 | + }, | |
| 165 | + async onSubmit() { | |
| 166 | + if (this.submitting) return | |
| 167 | + if (!this.validate()) return | |
| 168 | + this.submitting = true | |
| 169 | + const payload = this.buildPayload() | |
| 170 | + try { | |
| 171 | + await salesmanQuotationPeerUpdateApi(payload) | |
| 172 | + uni.showToast({ title: '保存成功', icon: 'success' }) | |
| 173 | + try { uni.setStorageSync('PEER_INFO_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 174 | + setTimeout(() => { uni.redirectTo({ url: '/pages/peer-info/index' }) }, 300) | |
| 175 | + } catch (e) { | |
| 176 | + uni.showToast({ title: (e && e.msg) || '保存失败', icon: 'none' }) | |
| 177 | + } finally { | |
| 178 | + this.submitting = false | |
| 179 | + } | |
| 180 | + } | |
| 181 | + } | |
| 182 | +} | |
| 183 | +</script> | |
| 184 | + | |
| 185 | +<style lang="scss" scoped> | |
| 186 | +.page { | |
| 187 | + display: flex; | |
| 188 | + flex-direction: column; | |
| 189 | + height: 100%; | |
| 190 | +} | |
| 191 | + | |
| 192 | +.scroll { | |
| 193 | + flex: 1; | |
| 194 | + padding: 0rpx 0 160rpx; | |
| 195 | + background-color: #f3f3f3; | |
| 196 | +} | |
| 197 | + | |
| 198 | +.footer { | |
| 199 | + position: fixed; | |
| 200 | + left: 0; | |
| 201 | + right: 0; | |
| 202 | + bottom: 0; | |
| 203 | + padding: 32rpx; | |
| 204 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 205 | + background: #fff; | |
| 206 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 207 | + z-index: 10; | |
| 208 | + | |
| 209 | + .btn { | |
| 210 | + height: 80rpx; | |
| 211 | + line-height: 80rpx; | |
| 212 | + border-radius: 12rpx; | |
| 213 | + font-size: 32rpx; | |
| 214 | + } | |
| 215 | + | |
| 216 | + .submit { | |
| 217 | + background: $theme-primary; | |
| 218 | + color: #fff; | |
| 219 | + } | |
| 220 | +} | |
| 221 | + | |
| 222 | +.title-header { | |
| 223 | + display: flex; | |
| 224 | + align-items: center; | |
| 225 | + padding: 24rpx 32rpx 12rpx; | |
| 226 | + background-color: #ffffff; | |
| 227 | + | |
| 228 | + .title-header_icon { | |
| 229 | + width: 36rpx; | |
| 230 | + height: 36rpx; | |
| 231 | + margin-right: 12rpx; | |
| 232 | + } | |
| 233 | + | |
| 234 | + span { | |
| 235 | + font-size: 34rpx; | |
| 236 | + font-weight: 600; | |
| 237 | + color: rgba(0, 0, 0, 0.9); | |
| 238 | + } | |
| 239 | +} | |
| 240 | + | |
| 241 | +.mgb10 { | |
| 242 | + margin-bottom: 20rpx; | |
| 243 | +} | |
| 244 | + | |
| 245 | +::v-deep .uni-list { | |
| 246 | + .uni-easyinput { | |
| 247 | + display: flex; | |
| 248 | + | |
| 249 | + .uni-input-input { | |
| 250 | + color: rgba(0, 0, 0, 0.9); | |
| 251 | + } | |
| 252 | + } | |
| 253 | + | |
| 254 | + .uni-input-placeholder { | |
| 255 | + z-index: 1; | |
| 256 | + } | |
| 257 | + | |
| 258 | + .uni-input-input { | |
| 259 | + background-color: #ffffff; | |
| 260 | + } | |
| 261 | + | |
| 262 | + background: transparent; | |
| 263 | + | |
| 264 | + &-item { | |
| 265 | + &__extra-text { | |
| 266 | + font-size: 32rpx; | |
| 267 | + } | |
| 268 | + | |
| 269 | + &__content-title { | |
| 270 | + font-size: 32rpx; | |
| 271 | + color: rgba(0, 0, 0, 0.9); | |
| 272 | + } | |
| 273 | + | |
| 274 | + &__container { | |
| 275 | + padding: 32rpx; | |
| 276 | + | |
| 277 | + .uni-easyinput { | |
| 278 | + &__placeholder-class { | |
| 279 | + font-size: 32rpx; | |
| 280 | + color: rgba(0, 0, 0, 0.4); | |
| 281 | + } | |
| 282 | + | |
| 283 | + &__content { | |
| 284 | + border: none; | |
| 285 | + background-color: #ffffff !important; | |
| 286 | + | |
| 287 | + &-input { | |
| 288 | + padding-left: 0 !important; | |
| 289 | + height: 48rpx; | |
| 290 | + line-height: 48rpx; | |
| 291 | + font-size: 32rpx; | |
| 292 | + } | |
| 293 | + | |
| 294 | + .content-clear-icon { | |
| 295 | + font-size: 44rpx !important; | |
| 296 | + } | |
| 297 | + } | |
| 298 | + } | |
| 299 | + | |
| 300 | + .item-title, | |
| 301 | + .uni-list-item__content { | |
| 302 | + flex: none; | |
| 303 | + min-height: 48rpx; | |
| 304 | + line-height: 48rpx; | |
| 305 | + font-size: 32rpx; | |
| 306 | + position: relative; | |
| 307 | + width: 210rpx; | |
| 308 | + margin-right: 32rpx; | |
| 309 | + color: rgba(0, 0, 0, 0.9); | |
| 310 | + | |
| 311 | + .required { | |
| 312 | + color: red; | |
| 313 | + position: absolute; | |
| 314 | + top: 50%; | |
| 315 | + transform: translateY(-50%); | |
| 316 | + left: -16rpx; | |
| 317 | + } | |
| 318 | + } | |
| 319 | + } | |
| 320 | + | |
| 321 | + &.select-item { | |
| 322 | + &.is-empty { | |
| 323 | + .uni-list-item__extra-text { | |
| 324 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 325 | + } | |
| 326 | + } | |
| 327 | + | |
| 328 | + &.is-filled { | |
| 329 | + .uni-list-item__extra-text { | |
| 330 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 331 | + } | |
| 332 | + } | |
| 333 | + } | |
| 334 | + | |
| 335 | + &.mgb10 { | |
| 336 | + margin-bottom: 20rpx; | |
| 337 | + } | |
| 338 | + } | |
| 339 | +} | |
| 340 | +</style> | ... | ... |
pages/quote-manage/assign.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <view class="section" v-if="users.length > 0"> | |
| 5 | + <view | |
| 6 | + v-for="it in users" | |
| 7 | + :key="it.id" | |
| 8 | + class="card" | |
| 9 | + :class="{ 'is-selected': selectedId === it.id }" | |
| 10 | + @click="onSelect(it)" | |
| 11 | + > | |
| 12 | + <view class="card-row"> | |
| 13 | + <text class="card-name">{{ safeText(it.name) }}</text> | |
| 14 | + <text class="card-check" v-if="selectedId === it.id">✓</text> | |
| 15 | + </view> | |
| 16 | + <view class="card-row sub"> | |
| 17 | + <text class="card-label">部门:</text> | |
| 18 | + <text class="card-value">{{ safeText(it.deptName) }}</text> | |
| 19 | + </view> | |
| 20 | + <view class="card-row sub"> | |
| 21 | + <text class="card-label">角色:</text> | |
| 22 | + <text class="card-value">{{ safeText(it.roleName) }}</text> | |
| 23 | + </view> | |
| 24 | + <view class="card-row sub"> | |
| 25 | + <text class="card-label">用户名:</text> | |
| 26 | + <text class="card-value">{{ safeText(it.username) }}</text> | |
| 27 | + </view> | |
| 28 | + </view> | |
| 29 | + </view> | |
| 30 | + <view v-else-if="!loading" class="empty">暂无可分配人员</view> | |
| 31 | + </scroll-view> | |
| 32 | + | |
| 33 | + <view class="footer"> | |
| 34 | + <button class="btn submit" :disabled="!selectedId" @click="onSubmit">确定分配</button> | |
| 35 | + </view> | |
| 36 | + </view> | |
| 37 | +</template> | |
| 38 | + | |
| 39 | +<script> | |
| 40 | +import { supplierQuotationDealAssignUsersApi, supplierQuotationDealAssignApi } from '@/api/procure-manage/supplierQuotationDeal.js' | |
| 41 | + | |
| 42 | +export default { | |
| 43 | + name: 'AssignQuoteManage', | |
| 44 | + data() { | |
| 45 | + return { | |
| 46 | + id: '', | |
| 47 | + purchaseDepartment: '', | |
| 48 | + loading: false, | |
| 49 | + users: [], | |
| 50 | + selectedId: '' | |
| 51 | + } | |
| 52 | + }, | |
| 53 | + onLoad(options) { | |
| 54 | + this.id = (options && options.id) ? options.id : '' | |
| 55 | + this.purchaseDepartment = (options && options.purchaseDepartment) ? options.purchaseDepartment : '' | |
| 56 | + if (!this.id) { | |
| 57 | + uni.showToast({ title: '缺少主键ID', icon: 'none' }) | |
| 58 | + return | |
| 59 | + } | |
| 60 | + this.loadUsers() | |
| 61 | + }, | |
| 62 | + methods: { | |
| 63 | + safeText(v) { | |
| 64 | + const s = v == null ? '' : String(v) | |
| 65 | + return s.trim() ? s : '-' | |
| 66 | + }, | |
| 67 | + async loadUsers() { | |
| 68 | + this.loading = true | |
| 69 | + try { | |
| 70 | + const res = await supplierQuotationDealAssignUsersApi({ purchaseDepartment: this.purchaseDepartment }) | |
| 71 | + const data = (res && res.data) ? res.data : [] | |
| 72 | + this.users = Array.isArray(data) ? data : [] | |
| 73 | + } catch (e) { | |
| 74 | + this.users = [] | |
| 75 | + uni.showToast({ title: '加载人员失败', icon: 'none' }) | |
| 76 | + } finally { | |
| 77 | + this.loading = false | |
| 78 | + } | |
| 79 | + }, | |
| 80 | + onSelect(it) { | |
| 81 | + this.selectedId = it.id | |
| 82 | + }, | |
| 83 | + async onSubmit() { | |
| 84 | + if (!this.selectedId) { | |
| 85 | + uni.showToast({ title: '请选择负责人', icon: 'none' }) | |
| 86 | + return | |
| 87 | + } | |
| 88 | + const confirmRes = await new Promise(resolve => { | |
| 89 | + uni.showModal({ title: '提示', content: '确定分配给该负责人吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 90 | + }) | |
| 91 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 92 | + try { | |
| 93 | + await supplierQuotationDealAssignApi({ id: this.id, principalId: this.selectedId }) | |
| 94 | + uni.showToast({ title: '分配成功', icon: 'none' }) | |
| 95 | + try { uni.setStorageSync('QUOTE_MANAGE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 96 | + setTimeout(() => { uni.navigateBack() }, 400) | |
| 97 | + } catch (e) { | |
| 98 | + uni.showToast({ title: e.msg || '分配失败', icon: 'none' }) | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | +} | |
| 103 | +</script> | |
| 104 | + | |
| 105 | +<style lang="scss" scoped> | |
| 106 | +.page { | |
| 107 | + display: flex; | |
| 108 | + flex-direction: column; | |
| 109 | + height: 100%; | |
| 110 | + background: #f3f3f3; | |
| 111 | +} | |
| 112 | + | |
| 113 | +.scroll { | |
| 114 | + flex: 1; | |
| 115 | + padding: 20rpx 0 200rpx; | |
| 116 | +} | |
| 117 | + | |
| 118 | +.section { | |
| 119 | + padding: 0 24rpx; | |
| 120 | +} | |
| 121 | + | |
| 122 | +.card { | |
| 123 | + background: #fff; | |
| 124 | + border-radius: 12rpx; | |
| 125 | + padding: 28rpx 32rpx; | |
| 126 | + margin-bottom: 20rpx; | |
| 127 | + border: 2rpx solid transparent; | |
| 128 | + | |
| 129 | + &.is-selected { | |
| 130 | + border-color: $theme-primary; | |
| 131 | + background: rgba($theme-primary, 0.04); | |
| 132 | + } | |
| 133 | + | |
| 134 | + .card-row { | |
| 135 | + display: flex; | |
| 136 | + align-items: center; | |
| 137 | + margin-bottom: 12rpx; | |
| 138 | + | |
| 139 | + &.sub { | |
| 140 | + margin-bottom: 8rpx; | |
| 141 | + } | |
| 142 | + | |
| 143 | + &:last-child { | |
| 144 | + margin-bottom: 0; | |
| 145 | + } | |
| 146 | + } | |
| 147 | + | |
| 148 | + .card-name { | |
| 149 | + flex: 1; | |
| 150 | + font-size: 32rpx; | |
| 151 | + font-weight: 600; | |
| 152 | + color: rgba(0, 0, 0, 0.9); | |
| 153 | + } | |
| 154 | + | |
| 155 | + .card-check { | |
| 156 | + color: $theme-primary; | |
| 157 | + font-size: 36rpx; | |
| 158 | + font-weight: 700; | |
| 159 | + } | |
| 160 | + | |
| 161 | + .card-label { | |
| 162 | + font-size: 28rpx; | |
| 163 | + color: rgba(0, 0, 0, 0.6); | |
| 164 | + } | |
| 165 | + | |
| 166 | + .card-value { | |
| 167 | + flex: 1; | |
| 168 | + font-size: 28rpx; | |
| 169 | + color: rgba(0, 0, 0, 0.9); | |
| 170 | + } | |
| 171 | +} | |
| 172 | + | |
| 173 | +.empty { | |
| 174 | + text-align: center; | |
| 175 | + color: rgba(0, 0, 0, 0.4); | |
| 176 | + font-size: 28rpx; | |
| 177 | + padding: 120rpx 0; | |
| 178 | +} | |
| 179 | + | |
| 180 | +.footer { | |
| 181 | + z-index: 2; | |
| 182 | + position: fixed; | |
| 183 | + left: 0; | |
| 184 | + right: 0; | |
| 185 | + bottom: 0; | |
| 186 | + padding: 32rpx; | |
| 187 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 188 | + background: #fff; | |
| 189 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 190 | + | |
| 191 | + .btn { | |
| 192 | + height: 80rpx; | |
| 193 | + line-height: 80rpx; | |
| 194 | + border-radius: 12rpx; | |
| 195 | + font-size: 32rpx; | |
| 196 | + } | |
| 197 | + | |
| 198 | + .submit { | |
| 199 | + background: $theme-primary; | |
| 200 | + color: #fff; | |
| 201 | + | |
| 202 | + &[disabled] { | |
| 203 | + opacity: 0.5; | |
| 204 | + } | |
| 205 | + } | |
| 206 | +} | |
| 207 | +</style> | ... | ... |
pages/quote-manage/detail.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <view class="detail-page"> | |
| 5 | + <view class="section"> | |
| 6 | + <text class="row company">{{ safeText(form.projectName) }}</text> | |
| 7 | + <view class="row"><text class="label">项目编号</text><text class="value">{{ safeText(form.projectCode) }}</text></view> | |
| 8 | + <view class="row"><text class="label">报价时间</text><text class="value">{{ formatDateTimeMinute(form.quoteTime) }}</text></view> | |
| 9 | + <view class="row"><text class="label">供应商名称</text><text class="value">{{ safeText(form.supplierName) }}</text></view> | |
| 10 | + <view class="row"><text class="label">品种</text><text class="value">{{ safeText(form.productName) }}</text></view> | |
| 11 | + <view class="row"><text class="label">数量</text><text class="value">{{ safeText(form.quantity) }}</text></view> | |
| 12 | + <view class="row"><text class="label">价格</text><text class="value">{{ safeText(form.price) }}</text></view> | |
| 13 | + <view class="row"><text class="label">采购处</text><text class="value">{{ safeText(form.purchaseDepartmentName) }}</text></view> | |
| 14 | + <view class="row"><text class="label">负责人</text><text class="value">{{ safeText(form.principalName) }}</text></view> | |
| 15 | + <view class="row"><text class="label">分配时间</text><text class="value">{{ formatDateTimeMinute(form.assignTime) }}</text></view> | |
| 16 | + </view> | |
| 17 | + </view> | |
| 18 | + </scroll-view> | |
| 19 | + | |
| 20 | + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" /> | |
| 21 | + </view> | |
| 22 | +</template> | |
| 23 | + | |
| 24 | +<script> | |
| 25 | +import DetailButtons from '@/components/detail-buttons/index.vue' | |
| 26 | +import { supplierQuotationDealGetApi } from '@/api/procure-manage/supplierQuotationDeal.js' | |
| 27 | + | |
| 28 | +export default { | |
| 29 | + name: 'QuoteManageDetail', | |
| 30 | + components: { DetailButtons }, | |
| 31 | + data() { | |
| 32 | + return { | |
| 33 | + id: '', | |
| 34 | + loading: false, | |
| 35 | + form: {}, | |
| 36 | + buttons: [{ | |
| 37 | + text: '分配', | |
| 38 | + visible: true, | |
| 39 | + variant: 'primary', | |
| 40 | + event: 'assign' | |
| 41 | + }] | |
| 42 | + } | |
| 43 | + }, | |
| 44 | + computed: { | |
| 45 | + displayButtons() { | |
| 46 | + return [ | |
| 47 | + { ...this.buttons[0], visible: !this.form.principalId && this.$auth.hasPermi('procure-manage:quote-manage:assign') } | |
| 48 | + ] | |
| 49 | + } | |
| 50 | + }, | |
| 51 | + onShow() { | |
| 52 | + const options = (this.$route && this.$route.query) ? this.$route.query : {} | |
| 53 | + const id = options && options.id ? String(options.id) : '' | |
| 54 | + this.id = id | |
| 55 | + this.loadDetail() | |
| 56 | + }, | |
| 57 | + methods: { | |
| 58 | + safeText(v) { | |
| 59 | + const s = v == null ? '' : String(v) | |
| 60 | + return s.trim() ? s : '-' | |
| 61 | + }, | |
| 62 | + formatDateTimeMinute(v) { | |
| 63 | + const s = v == null ? '' : String(v).trim() | |
| 64 | + if (!s) return '-' | |
| 65 | + if (s.length >= 16) return s.slice(0, 16) | |
| 66 | + if (s.length >= 10) return s.slice(0, 10) | |
| 67 | + return s | |
| 68 | + }, | |
| 69 | + async loadDetail() { | |
| 70 | + this.loading = true | |
| 71 | + try { | |
| 72 | + const res = await supplierQuotationDealGetApi(this.id) | |
| 73 | + const data = (res && res.data) ? res.data : {} | |
| 74 | + this.form = data | |
| 75 | + } catch (e) { | |
| 76 | + this.form = {} | |
| 77 | + uni.showToast({ title: '详情加载失败', icon: 'none' }) | |
| 78 | + } finally { | |
| 79 | + this.loading = false | |
| 80 | + } | |
| 81 | + }, | |
| 82 | + handleButtonClick(btn) { | |
| 83 | + if (!btn || btn.disabled) return | |
| 84 | + const e = btn.event || '' | |
| 85 | + if (e === 'assign') return this.onAssign() | |
| 86 | + }, | |
| 87 | + onAssign() { | |
| 88 | + const pd = this.form.purchaseDepartment || this.form.purchaseDepartmentId || '' | |
| 89 | + uni.navigateTo({ url: '/pages/quote-manage/assign?id=' + this.id + '&purchaseDepartment=' + pd }) | |
| 90 | + } | |
| 91 | + } | |
| 92 | +} | |
| 93 | +</script> | |
| 94 | + | |
| 95 | +<style lang="scss" scoped> | |
| 96 | +.page { | |
| 97 | + display: flex; | |
| 98 | + flex-direction: column; | |
| 99 | + height: 100%; | |
| 100 | +} | |
| 101 | + | |
| 102 | +.scroll { | |
| 103 | + flex: 1; | |
| 104 | + padding: 8rpx 0 144rpx; | |
| 105 | +} | |
| 106 | + | |
| 107 | +.detail-page { | |
| 108 | + background: #f3f3f3; | |
| 109 | +} | |
| 110 | + | |
| 111 | +.section { | |
| 112 | + padding: 32rpx; | |
| 113 | + background: #fff; | |
| 114 | + margin-bottom: 20rpx; | |
| 115 | +} | |
| 116 | + | |
| 117 | +.row { | |
| 118 | + display: flex; | |
| 119 | + margin-bottom: 28rpx; | |
| 120 | + | |
| 121 | + &:last-child { | |
| 122 | + margin-bottom: 0; | |
| 123 | + } | |
| 124 | + | |
| 125 | + &.company { | |
| 126 | + font-size: 36rpx; | |
| 127 | + font-weight: 600; | |
| 128 | + color: rgba(0, 0, 0, 0.9); | |
| 129 | + padding-top: 8rpx; | |
| 130 | + line-height: 50rpx; | |
| 131 | + margin-bottom: 32rpx; | |
| 132 | + } | |
| 133 | + | |
| 134 | + .label { | |
| 135 | + max-width: 420rpx; | |
| 136 | + margin-right: 20rpx; | |
| 137 | + line-height: 32rpx; | |
| 138 | + font-size: 28rpx; | |
| 139 | + color: rgba(0, 0, 0, 0.6); | |
| 140 | + } | |
| 141 | + | |
| 142 | + .value { | |
| 143 | + flex: 1; | |
| 144 | + line-height: 32rpx; | |
| 145 | + font-size: 28rpx; | |
| 146 | + color: rgba(0, 0, 0, 0.9); | |
| 147 | + text-align: right; | |
| 148 | + word-break: break-all; | |
| 149 | + } | |
| 150 | +} | |
| 151 | +</style> | ... | ... |
pages/quote-manage/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <view class="dev-list-fixed"> | |
| 4 | + <view class="search-row"> | |
| 5 | + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入项目名称" clearButton="auto" | |
| 6 | + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search" | |
| 7 | + @input="onSearchInput" /> | |
| 8 | + <view class="tool-icons"> | |
| 9 | + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" /> | |
| 10 | + </view> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + | |
| 14 | + <view class="list-box"> | |
| 15 | + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id" | |
| 16 | + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError"> | |
| 17 | + <template v-slot="{ item }"> | |
| 18 | + <view class="card" @click.stop="onCardClick(item)"> | |
| 19 | + <view class="card-header"> | |
| 20 | + <text class="title omit2">{{ item.projectName || '-' }}</text> | |
| 21 | + </view> | |
| 22 | + <view class="info-row"> | |
| 23 | + <text>项目编号</text><text>{{ item.projectCode || '-' }}</text> | |
| 24 | + </view> | |
| 25 | + <view class="info-row"> | |
| 26 | + <text>品种</text><text>{{ item.productName || '-' }}</text> | |
| 27 | + </view> | |
| 28 | + <view class="info-row"> | |
| 29 | + <text>供应商名称</text><text>{{ item.supplierName || '-' }}</text> | |
| 30 | + </view> | |
| 31 | + <view class="info-row"> | |
| 32 | + <text>价格</text><text>{{ item.price != null ? item.price : '-' }}</text> | |
| 33 | + </view> | |
| 34 | + </view> | |
| 35 | + </template> | |
| 36 | + </card-list> | |
| 37 | + </view> | |
| 38 | + | |
| 39 | + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset" | |
| 40 | + @confirm="onFilterConfirm"> | |
| 41 | + <template v-slot="{ model }"> | |
| 42 | + <view class="filter-form"> | |
| 43 | + <view class="form-item"> | |
| 44 | + <view class="label">项目编号</view> | |
| 45 | + <uni-easyinput v-model="model.projectCode" placeholder="请输入项目编号" :inputBorder="true" /> | |
| 46 | + </view> | |
| 47 | + <view class="form-item"> | |
| 48 | + <view class="label">供应商名称</view> | |
| 49 | + <uni-easyinput v-model="model.supplierName" placeholder="请输入供应商名称" :inputBorder="true" /> | |
| 50 | + </view> | |
| 51 | + </view> | |
| 52 | + </template> | |
| 53 | + </filter-modal> | |
| 54 | + </view> | |
| 55 | +</template> | |
| 56 | + | |
| 57 | +<script> | |
| 58 | +import CardList from '@/components/card/index.vue' | |
| 59 | +import FilterModal from '@/components/filter/index.vue' | |
| 60 | +import { supplierQuotationDealQueryApi } from '@/api/procure-manage/supplierQuotationDeal.js' | |
| 61 | + | |
| 62 | +export default { | |
| 63 | + name: 'QuoteManageList', | |
| 64 | + components: { CardList, FilterModal }, | |
| 65 | + data() { | |
| 66 | + return { | |
| 67 | + searchKeyword: '', | |
| 68 | + searchKeywordDebounced: '', | |
| 69 | + searchDebounceTimer: null, | |
| 70 | + query: { | |
| 71 | + projectName: '', | |
| 72 | + projectCode: '', | |
| 73 | + supplierName: '' | |
| 74 | + }, | |
| 75 | + extraParams: {}, | |
| 76 | + currentItems: [], | |
| 77 | + filterVisible: false, | |
| 78 | + filterForm: { | |
| 79 | + projectCode: '', | |
| 80 | + supplierName: '' | |
| 81 | + } | |
| 82 | + } | |
| 83 | + }, | |
| 84 | + watch: { | |
| 85 | + searchKeywordDebounced(v) { | |
| 86 | + this.query = { ...this.query, projectName: v || '' } | |
| 87 | + } | |
| 88 | + }, | |
| 89 | + onShow() { | |
| 90 | + let needRefresh = '' | |
| 91 | + try { needRefresh = uni.getStorageSync('QUOTE_MANAGE_LIST_NEED_REFRESH') } catch (e) {} | |
| 92 | + if (!needRefresh) return | |
| 93 | + try { uni.removeStorageSync('QUOTE_MANAGE_LIST_NEED_REFRESH') } catch (e) {} | |
| 94 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) { | |
| 95 | + this.$refs.cardRef.reload() | |
| 96 | + } | |
| 97 | + }, | |
| 98 | + onReachBottom() { | |
| 99 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) { | |
| 100 | + this.$refs.cardRef.onLoadMore() | |
| 101 | + } | |
| 102 | + }, | |
| 103 | + beforeDestroy() { | |
| 104 | + if (this.searchDebounceTimer) { | |
| 105 | + clearTimeout(this.searchDebounceTimer) | |
| 106 | + this.searchDebounceTimer = null | |
| 107 | + } | |
| 108 | + }, | |
| 109 | + methods: { | |
| 110 | + onCardLoaded({ items }) { | |
| 111 | + this.currentItems = items | |
| 112 | + }, | |
| 113 | + onCardError() { | |
| 114 | + uni.showToast({ title: '列表加载失败', icon: 'none' }) | |
| 115 | + }, | |
| 116 | + onSearchInput() { | |
| 117 | + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer) | |
| 118 | + this.searchDebounceTimer = setTimeout(() => { | |
| 119 | + this.searchKeywordDebounced = this.searchKeyword | |
| 120 | + this.searchDebounceTimer = null | |
| 121 | + }, 1200) | |
| 122 | + }, | |
| 123 | + search(e) { | |
| 124 | + const val = e && e.value != null ? e.value : this.searchKeyword | |
| 125 | + this.searchKeyword = val | |
| 126 | + this.searchKeywordDebounced = val | |
| 127 | + }, | |
| 128 | + openFilter() { | |
| 129 | + this.filterForm = { | |
| 130 | + projectCode: this.query.projectCode || '', | |
| 131 | + supplierName: this.query.supplierName || '' | |
| 132 | + } | |
| 133 | + this.filterVisible = true | |
| 134 | + }, | |
| 135 | + onFilterReset(payload) { | |
| 136 | + this.filterForm = payload | |
| 137 | + }, | |
| 138 | + onFilterConfirm(payload) { | |
| 139 | + this.query = { | |
| 140 | + projectName: this.query.projectName || '', | |
| 141 | + projectCode: payload.projectCode || '', | |
| 142 | + supplierName: payload.supplierName || '' | |
| 143 | + } | |
| 144 | + }, | |
| 145 | + onCardClick(item) { | |
| 146 | + uni.navigateTo({ url: '/pages/quote-manage/detail?id=' + item.id }) | |
| 147 | + }, | |
| 148 | + fetchList({ pageIndex, pageSize, query }) { | |
| 149 | + const q = query || {} | |
| 150 | + const params = { | |
| 151 | + pageIndex, | |
| 152 | + pageSize, | |
| 153 | + projectName: q.projectName || '', | |
| 154 | + projectCode: q.projectCode || '', | |
| 155 | + supplierName: q.supplierName || '' | |
| 156 | + } | |
| 157 | + return supplierQuotationDealQueryApi(params).then(res => { | |
| 158 | + const _data = res && res.data ? res.data : {} | |
| 159 | + let records = _data.datas || _data.list || _data.records || [] | |
| 160 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 161 | + const hasNext = _data.hasNext || false | |
| 162 | + records = records.map(it => ({ ...it })) | |
| 163 | + return { records, totalCount, hasNext } | |
| 164 | + }).catch(() => { | |
| 165 | + this.onCardError() | |
| 166 | + return { records: [], totalCount: 0, hasNext: false } | |
| 167 | + }) | |
| 168 | + } | |
| 169 | + } | |
| 170 | +} | |
| 171 | +</script> | |
| 172 | + | |
| 173 | +<style lang="scss" scoped> | |
| 174 | +.page { | |
| 175 | + display: flex; | |
| 176 | + flex-direction: column; | |
| 177 | + height: 100vh; | |
| 178 | +} | |
| 179 | + | |
| 180 | +.dev-list-fixed { | |
| 181 | + position: fixed; | |
| 182 | + top: 96rpx; | |
| 183 | + left: 0; | |
| 184 | + right: 0; | |
| 185 | + z-index: 2; | |
| 186 | + background: #fff; | |
| 187 | + | |
| 188 | + .search-row { | |
| 189 | + display: flex; | |
| 190 | + align-items: center; | |
| 191 | + padding: 16rpx 32rpx; | |
| 192 | + | |
| 193 | + .uni-searchbar { | |
| 194 | + padding: 0; | |
| 195 | + flex: 1; | |
| 196 | + } | |
| 197 | + | |
| 198 | + .tool-icons { | |
| 199 | + display: flex; | |
| 200 | + | |
| 201 | + .tool-icon { | |
| 202 | + width: 48rpx; | |
| 203 | + height: 48rpx; | |
| 204 | + display: block; | |
| 205 | + margin-left: 32rpx; | |
| 206 | + } | |
| 207 | + } | |
| 208 | + } | |
| 209 | +} | |
| 210 | + | |
| 211 | +::v-deep .uni-searchbar__box { | |
| 212 | + height: 80rpx !important; | |
| 213 | + justify-content: start; | |
| 214 | + | |
| 215 | + .uni-searchbar__box-search-input { | |
| 216 | + font-size: 32rpx !important; | |
| 217 | + } | |
| 218 | +} | |
| 219 | + | |
| 220 | +.list-box { | |
| 221 | + flex: 1; | |
| 222 | + padding-top: 140rpx; | |
| 223 | + | |
| 224 | + .card { | |
| 225 | + position: relative; | |
| 226 | + } | |
| 227 | + | |
| 228 | + .card-header { | |
| 229 | + margin-bottom: 28rpx; | |
| 230 | + position: relative; | |
| 231 | + | |
| 232 | + .title { | |
| 233 | + font-size: 36rpx; | |
| 234 | + font-weight: 600; | |
| 235 | + line-height: 50rpx; | |
| 236 | + color: rgba(0, 0, 0, 0.9); | |
| 237 | + width: 578rpx; | |
| 238 | + } | |
| 239 | + } | |
| 240 | + | |
| 241 | + .info-row { | |
| 242 | + display: flex; | |
| 243 | + align-items: center; | |
| 244 | + color: rgba(0, 0, 0, 0.6); | |
| 245 | + font-size: 28rpx; | |
| 246 | + margin-bottom: 24rpx; | |
| 247 | + height: 32rpx; | |
| 248 | + | |
| 249 | + &:last-child { | |
| 250 | + margin-bottom: 0; | |
| 251 | + } | |
| 252 | + | |
| 253 | + text { | |
| 254 | + width: 50%; | |
| 255 | + | |
| 256 | + &:last-child { | |
| 257 | + color: rgba(0, 0, 0, 0.9); | |
| 258 | + width: 50%; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + } | |
| 262 | +} | |
| 263 | + | |
| 264 | +.filter-form { | |
| 265 | + .form-item { | |
| 266 | + margin-bottom: 24rpx; | |
| 267 | + } | |
| 268 | + | |
| 269 | + .label { | |
| 270 | + margin-bottom: 20rpx; | |
| 271 | + color: rgba(0, 0, 0, 0.9); | |
| 272 | + height: 44rpx; | |
| 273 | + line-height: 44rpx; | |
| 274 | + font-size: 30rpx; | |
| 275 | + } | |
| 276 | +} | |
| 277 | +</style> | ... | ... |
pages/sales-price/add.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <!-- 编号 --> | |
| 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 | + <view class="serial-number-row"> | |
| 12 | + <text class="readonly-text value-code">{{ form.code }}</text> | |
| 13 | + <button class="generate-btn" @click="initCode">点此生成</button> | |
| 14 | + </view> | |
| 15 | + </template> | |
| 16 | + </uni-list-item> | |
| 17 | + | |
| 18 | + <!-- 供应商名称 --> | |
| 19 | + <uni-list-item class="select-item" :class="form.supplierId ? 'is-filled' : 'is-empty'" clickable | |
| 20 | + @click="openSupplierSheet" :rightText="form.supplierName || '请选择供应商名称'" showArrow> | |
| 21 | + <template v-slot:body> | |
| 22 | + <view class="item-title"><text class="required">*</text><text>供应商名称</text></view> | |
| 23 | + </template> | |
| 24 | + </uni-list-item> | |
| 25 | + | |
| 26 | + <!-- 采购处(带出,禁用) --> | |
| 27 | + <uni-list-item title="采购处"> | |
| 28 | + <template v-slot:footer> | |
| 29 | + <uni-easyinput v-model="form.purchaseDepartmentName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 30 | + </template> | |
| 31 | + </uni-list-item> | |
| 32 | + | |
| 33 | + <!-- 区域(带出,禁用) --> | |
| 34 | + <uni-list-item title="区域"> | |
| 35 | + <template v-slot:footer> | |
| 36 | + <uni-easyinput v-model="form.regionName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 37 | + </template> | |
| 38 | + </uni-list-item> | |
| 39 | + | |
| 40 | + <!-- 报价时效 --> | |
| 41 | + <uni-list-item> | |
| 42 | + <template v-slot:body> | |
| 43 | + <view class="item-title"><text class="required">*</text><text>报价时效</text></view> | |
| 44 | + </template> | |
| 45 | + <template v-slot:footer> | |
| 46 | + <uni-easyinput v-model="form.validityPeriod" :inputBorder="false" placeholder="请输入报价时效" /> | |
| 47 | + </template> | |
| 48 | + </uni-list-item> | |
| 49 | + | |
| 50 | + <!-- 报价人(当前登录用户,禁用) --> | |
| 51 | + <uni-list-item title="报价人"> | |
| 52 | + <template v-slot:footer> | |
| 53 | + <uni-easyinput v-model="form.quoterName" :inputBorder="false" disabled placeholder="自动获取" /> | |
| 54 | + </template> | |
| 55 | + </uni-list-item> | |
| 56 | + | |
| 57 | + <!-- 是否长单操作 --> | |
| 58 | + <uni-list-item class="select-item" :class="typeof form.longTermOperation === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 59 | + @click="openSheet('longTermOperation')" :rightText="boolText(form.longTermOperation)" showArrow> | |
| 60 | + <template v-slot:body> | |
| 61 | + <view class="item-title"><text class="required">*</text><text>是否长单操作</text></view> | |
| 62 | + </template> | |
| 63 | + </uni-list-item> | |
| 64 | + | |
| 65 | + <!-- 是否终端客户 --> | |
| 66 | + <uni-list-item class="select-item" :class="typeof form.terminalCustomer === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 67 | + @click="openSheet('terminalCustomer')" :rightText="boolText(form.terminalCustomer)" showArrow> | |
| 68 | + <template v-slot:body> | |
| 69 | + <view class="item-title"><text class="required">*</text><text>是否终端客户</text></view> | |
| 70 | + </template> | |
| 71 | + </uni-list-item> | |
| 72 | + | |
| 73 | + <!-- 是否未点价 --> | |
| 74 | + <uni-list-item class="select-item" :class="typeof form.priced === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 75 | + @click="openSheet('priced')" :rightText="boolText(form.priced)" showArrow> | |
| 76 | + <template v-slot:body> | |
| 77 | + <view class="item-title"><text class="required">*</text><text>是否未点价</text></view> | |
| 78 | + </template> | |
| 79 | + </uni-list-item> | |
| 80 | + | |
| 81 | + <!-- 点价截止日期 - 仅在 是否未点价 = 否 时显示且必填 --> | |
| 82 | + <uni-list-item v-if="form.priced === false" class="select-item" :class="form.pricingDeadline ? 'is-filled' : 'is-empty'"> | |
| 83 | + <template v-slot:body> | |
| 84 | + <view class="item-title"><text class="required">*</text><text>点价截止日期</text></view> | |
| 85 | + </template> | |
| 86 | + <template v-slot:footer> | |
| 87 | + <uni-datetime-picker type="date" v-model="form.pricingDeadline" /> | |
| 88 | + </template> | |
| 89 | + </uni-list-item> | |
| 90 | + | |
| 91 | + <!-- 其他费用凭票到厂另行结算 --> | |
| 92 | + <uni-list-item class="select-item" :class="typeof form.otherExpenseSettlement === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 93 | + @click="openSheet('otherExpenseSettlement')" :rightText="boolText(form.otherExpenseSettlement)" showArrow> | |
| 94 | + <template v-slot:body> | |
| 95 | + <view class="item-title"><text class="required">*</text><text>其他费用凭票到厂另行结算</text></view> | |
| 96 | + </template> | |
| 97 | + </uni-list-item> | |
| 98 | + | |
| 99 | + <!-- 交提货方式 --> | |
| 100 | + <uni-list-item class="select-item" :class="form.deliveryMode ? 'is-filled' : 'is-empty'" clickable | |
| 101 | + @click="openDictSheet('deliveryMode', 'DELIVERY_METHOD', '交提货方式')" :rightText="getDeliveryMethodLabel() || '请选择交提货方式'" showArrow> | |
| 102 | + <template v-slot:body> | |
| 103 | + <view class="item-title"><text class="required">*</text><text>交提货方式</text></view> | |
| 104 | + </template> | |
| 105 | + </uni-list-item> | |
| 106 | + | |
| 107 | + <!-- 是否通货买断 --> | |
| 108 | + <uni-list-item class="select-item" :class="typeof form.commodityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 109 | + @click="openSheet('commodityBuyout')" :rightText="boolText(form.commodityBuyout)" showArrow> | |
| 110 | + <template v-slot:body> | |
| 111 | + <view class="item-title"><text class="required">*</text><text>是否通货买断</text></view> | |
| 112 | + </template> | |
| 113 | + </uni-list-item> | |
| 114 | + | |
| 115 | + <!-- 是否质量买断 --> | |
| 116 | + <uni-list-item class="select-item" :class="typeof form.qualityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 117 | + @click="openSheet('qualityBuyout')" :rightText="boolText(form.qualityBuyout)" showArrow> | |
| 118 | + <template v-slot:body> | |
| 119 | + <view class="item-title"><text class="required">*</text><text>是否质量买断</text></view> | |
| 120 | + </template> | |
| 121 | + </uni-list-item> | |
| 122 | + | |
| 123 | + <!-- 是否数量买断 --> | |
| 124 | + <uni-list-item class="select-item" :class="typeof form.quantityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 125 | + @click="openSheet('quantityBuyout')" :rightText="boolText(form.quantityBuyout)" showArrow> | |
| 126 | + <template v-slot:body> | |
| 127 | + <view class="item-title"><text class="required">*</text><text>是否数量买断</text></view> | |
| 128 | + </template> | |
| 129 | + </uni-list-item> | |
| 130 | + | |
| 131 | + <!-- 是否送货上门 --> | |
| 132 | + <uni-list-item class="select-item" :class="typeof form.doorDelivery === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 133 | + @click="openSheet('doorDelivery')" :rightText="boolText(form.doorDelivery)" showArrow> | |
| 134 | + <template v-slot:body> | |
| 135 | + <view class="item-title"><text class="required">*</text><text>是否送货上门</text></view> | |
| 136 | + </template> | |
| 137 | + </uni-list-item> | |
| 138 | + | |
| 139 | + <!-- 装车费 --> | |
| 140 | + <uni-list-item title="装车费"> | |
| 141 | + <template v-slot:footer> | |
| 142 | + <uni-easyinput v-model="form.loadingFee" type="digit" :inputBorder="false" placeholder="请输入装车费" /> | |
| 143 | + </template> | |
| 144 | + </uni-list-item> | |
| 145 | + | |
| 146 | + <!-- 开票品种 --> | |
| 147 | + <uni-list-item> | |
| 148 | + <template v-slot:body> | |
| 149 | + <view class="item-title"><text class="required">*</text><text>开票品种</text></view> | |
| 150 | + </template> | |
| 151 | + <template v-slot:footer> | |
| 152 | + <uni-easyinput v-model="form.invoiceProductName" :inputBorder="false" placeholder="请输入开票品种" /> | |
| 153 | + </template> | |
| 154 | + </uni-list-item> | |
| 155 | + | |
| 156 | + <!-- 交货日期 --> | |
| 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-datetime-picker type="date" v-model="form.deliveryDate" /> | |
| 163 | + </template> | |
| 164 | + </uni-list-item> | |
| 165 | + | |
| 166 | + <!-- 结算方式 --> | |
| 167 | + <uni-list-item> | |
| 168 | + <template v-slot:body> | |
| 169 | + <view class="item-title"><text class="required">*</text><text>结算方式</text></view> | |
| 170 | + </template> | |
| 171 | + <template v-slot:footer> | |
| 172 | + <uni-easyinput v-model="form.settlementMethod" :inputBorder="false" placeholder="请输入结算方式" /> | |
| 173 | + </template> | |
| 174 | + </uni-list-item> | |
| 175 | + | |
| 176 | + <!-- 结算比例(%) --> | |
| 177 | + <uni-list-item> | |
| 178 | + <template v-slot:body> | |
| 179 | + <view class="item-title"><text class="required">*</text><text>结算比例(%)</text></view> | |
| 180 | + </template> | |
| 181 | + <template v-slot:footer> | |
| 182 | + <uni-easyinput v-model="form.settlementRatio" type="digit" :inputBorder="false" placeholder="请输入结算比例" @blur="onRatioBlur" /> | |
| 183 | + </template> | |
| 184 | + </uni-list-item> | |
| 185 | + | |
| 186 | + <!-- 协调事由 --> | |
| 187 | + <uni-list-item title="协调事由"> | |
| 188 | + <template v-slot:footer> | |
| 189 | + <uni-easyinput v-model="form.coordinationReason" :inputBorder="false" placeholder="请输入协调事由" disabled /> | |
| 190 | + </template> | |
| 191 | + </uni-list-item> | |
| 192 | + | |
| 193 | + <!-- 票类状况 --> | |
| 194 | + <uni-list-item title="票类状况"> | |
| 195 | + <template v-slot:footer> | |
| 196 | + <uni-easyinput v-model="form.invoiceTypeStatus" :inputBorder="false" placeholder="请输入票类状况" disabled /> | |
| 197 | + </template> | |
| 198 | + </uni-list-item> | |
| 199 | + | |
| 200 | + <!-- 明细信息 --> | |
| 201 | + <ProductRel ref="productRel" mode="add" :options="productList" :priced="form.priced" @change="onProductsChange" /> | |
| 202 | + </uni-list> | |
| 203 | + | |
| 204 | + <!-- 料质状况模块(明细下方) --> | |
| 205 | + <view class="material-condition-section"> | |
| 206 | + <view class="section-header"> | |
| 207 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 208 | + <text class="section-title">料质状况</text> | |
| 209 | + </view> | |
| 210 | + <view class="section-content"> | |
| 211 | + <view class="form-row"> | |
| 212 | + <text class="form-label">是否启用料质状况</text> | |
| 213 | + <view class="bool-btns"> | |
| 214 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === true }" @click="form.materialConditionEnabled = true">是</view> | |
| 215 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === false }" @click="form.materialConditionEnabled = false">否</view> | |
| 216 | + </view> | |
| 217 | + </view> | |
| 218 | + | |
| 219 | + <template v-if="form.materialConditionEnabled"> | |
| 220 | + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group"> | |
| 221 | + <text class="material-label">{{ mc.label }}</text> | |
| 222 | + <view class="tag-list"> | |
| 223 | + <view | |
| 224 | + v-for="opt in getDictOptions(mc.dictCode)" | |
| 225 | + :key="opt.code" | |
| 226 | + class="tag-btn" | |
| 227 | + :class="{ active: isTagSelected(mc.field, opt.code) }" | |
| 228 | + @click="toggleTag(mc.field, opt.code)" | |
| 229 | + > | |
| 230 | + {{ opt.name }} | |
| 231 | + </view> | |
| 232 | + </view> | |
| 233 | + </view> | |
| 234 | + </template> | |
| 235 | + </view> | |
| 236 | + </view> | |
| 237 | + | |
| 238 | + <!-- 备注模块 --> | |
| 239 | + <view class="remark-section"> | |
| 240 | + <view class="section-header"> | |
| 241 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 242 | + <text class="section-title">备注</text> | |
| 243 | + </view> | |
| 244 | + <view class="section-content"> | |
| 245 | + <uni-easyinput v-model="form.remark" type="textarea" :inputBorder="false" placeholder="请输入备注" maxlength="2000" /> | |
| 246 | + </view> | |
| 247 | + </view> | |
| 248 | + | |
| 249 | + <view class="footer"> | |
| 250 | + <button class="btn submit" type="primary" @click="onSubmit">提交</button> | |
| 251 | + </view> | |
| 252 | + </scroll-view> | |
| 253 | + | |
| 254 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" | |
| 255 | + v-model="sheet.value" @confirm="onSheetConfirm" /> | |
| 256 | + | |
| 257 | + <RelateSelectSheet :visible.sync="supplierSheet.visible" :title="supplierSheet.title" | |
| 258 | + :fetchFn="supplierSheet.fetchFn" :display-fields="supplierSheet.displayFields" | |
| 259 | + :multiple="false" row-key="id" :selectedKeys.sync="supplierSheet.selectedKeys" | |
| 260 | + @confirm="onSupplierConfirm" /> | |
| 261 | + </view> | |
| 262 | +</template> | |
| 263 | +<script> | |
| 264 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 265 | +import RelateSelectSheet from '@/components/relate-select/index.vue' | |
| 266 | +import ProductRel from './productRel.vue' | |
| 267 | +import { salesmanQuotationGenerateCodeApi, salesmanQuotationSaveApi, breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 268 | +import { domesticCustomerCreditQueryApi } from '@/api/procure-manage/domesticCustomerCredit.js' | |
| 269 | +import { getInfo } from '@/api/login.js' | |
| 270 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 271 | + | |
| 272 | +export default { | |
| 273 | + name: 'AddSalesPrice', | |
| 274 | + components: { SingleSelectSheet, RelateSelectSheet, ProductRel }, | |
| 275 | + data() { | |
| 276 | + return { | |
| 277 | + form: { | |
| 278 | + code: '', | |
| 279 | + supplierId: '', | |
| 280 | + supplierName: '', | |
| 281 | + purchaseDepartmentName: '', | |
| 282 | + regionName: '', | |
| 283 | + validityPeriod: '', | |
| 284 | + quoterName: '', | |
| 285 | + longTermOperation: false, | |
| 286 | + terminalCustomer: false, | |
| 287 | + priced: true, | |
| 288 | + pricingDeadline: '', | |
| 289 | + otherExpenseSettlement: false, | |
| 290 | + deliveryMode: '', | |
| 291 | + commodityBuyout: false, | |
| 292 | + qualityBuyout: false, | |
| 293 | + quantityBuyout: false, | |
| 294 | + doorDelivery: false, | |
| 295 | + loadingFee: '', | |
| 296 | + invoiceProductName: '', | |
| 297 | + deliveryDate: '', | |
| 298 | + settlementMethod: '', | |
| 299 | + settlementRatio: '', | |
| 300 | + coordinationReason: '受市场影响,申请协调协调事由', | |
| 301 | + invoiceTypeStatus: '增票', | |
| 302 | + materialConditionEnabled: false, | |
| 303 | + materialConditionCopperRice: [], | |
| 304 | + materialConditionBrightCopper: [], | |
| 305 | + materialConditionFirstClass: [], | |
| 306 | + materialConditionSecondClass: [], | |
| 307 | + materialConditionTinCoated: [], | |
| 308 | + materialConditionScrap: [], | |
| 309 | + materialConditionWhiteYellow: [], | |
| 310 | + materialConditionPhosphorCopper: [], | |
| 311 | + remark: '' | |
| 312 | + }, | |
| 313 | + productList: [], | |
| 314 | + productLineList: [], | |
| 315 | + sheet: { visible: false, title: '请选择', options: [], value: '', field: '' }, | |
| 316 | + supplierSheet: { | |
| 317 | + visible: false, | |
| 318 | + title: '选择供应商', | |
| 319 | + fetchFn: null, | |
| 320 | + displayFields: [ | |
| 321 | + { label: '单位名称', field: 'unitName' }, | |
| 322 | + { label: '采购处', field: 'purchaseDepartmentName' }, | |
| 323 | + { label: '区域', field: 'regionName' } | |
| 324 | + ], | |
| 325 | + selectedKeys: [] | |
| 326 | + }, | |
| 327 | + materialConditionConfigs: [ | |
| 328 | + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' }, | |
| 329 | + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' }, | |
| 330 | + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' }, | |
| 331 | + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' }, | |
| 332 | + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' }, | |
| 333 | + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' }, | |
| 334 | + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' }, | |
| 335 | + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' } | |
| 336 | + ], | |
| 337 | + dictData: {} | |
| 338 | + } | |
| 339 | + }, | |
| 340 | + created() { | |
| 341 | + this.supplierSheet.fetchFn = this.fetchSuppliers.bind(this) | |
| 342 | + this.initCode() | |
| 343 | + this.loadQuoter() | |
| 344 | + this.loadProducts() | |
| 345 | + this.loadDictData() | |
| 346 | + }, | |
| 347 | + methods: { | |
| 348 | + async loadDictData() { | |
| 349 | + try { | |
| 350 | + const codes = [...this.materialConditionConfigs.map(mc => mc.dictCode), 'DELIVERY_METHOD'] | |
| 351 | + const res = await getDicByCodes(codes) | |
| 352 | + this.dictData = res || {} | |
| 353 | + } catch (e) { | |
| 354 | + this.dictData = {} | |
| 355 | + } | |
| 356 | + }, | |
| 357 | + getDictOptions(dictCode) { | |
| 358 | + const data = this.dictData[dictCode] | |
| 359 | + return (data && data.data) ? data.data : [] | |
| 360 | + }, | |
| 361 | + isTagSelected(field, code) { | |
| 362 | + const selected = this.form[field] | |
| 363 | + return Array.isArray(selected) && selected.includes(code) | |
| 364 | + }, | |
| 365 | + toggleTag(field, code) { | |
| 366 | + let selected = this.form[field] | |
| 367 | + if (!Array.isArray(selected)) { | |
| 368 | + selected = [] | |
| 369 | + } | |
| 370 | + const index = selected.indexOf(code) | |
| 371 | + if (index > -1) { | |
| 372 | + selected.splice(index, 1) | |
| 373 | + } else { | |
| 374 | + selected.push(code) | |
| 375 | + } | |
| 376 | + this.$set(this.form, field, [...selected]) | |
| 377 | + }, | |
| 378 | + async initCode() { | |
| 379 | + try { | |
| 380 | + const res = await salesmanQuotationGenerateCodeApi() | |
| 381 | + this.form.code = (res && res.data) ? res.data : '' | |
| 382 | + } catch (e) { | |
| 383 | + this.form.code = '' | |
| 384 | + } | |
| 385 | + }, | |
| 386 | + async loadQuoter() { | |
| 387 | + try { | |
| 388 | + // 优先使用 store 中已缓存的用户信息 | |
| 389 | + const storeUser = this.$store && this.$store.state && this.$store.state.user ? this.$store.state.user : {} | |
| 390 | + if (storeUser.name) { | |
| 391 | + this.form.quoterName = storeUser.name | |
| 392 | + return | |
| 393 | + } | |
| 394 | + const res = await getInfo() | |
| 395 | + const _data = res && res.data ? res.data : {} | |
| 396 | + this.form.quoterName = _data.name || '' | |
| 397 | + } catch (e) { | |
| 398 | + this.form.quoterName = '' | |
| 399 | + } | |
| 400 | + }, | |
| 401 | + async loadProducts() { | |
| 402 | + try { | |
| 403 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 404 | + const _data = res && res.data ? res.data : {} | |
| 405 | + const list = _data.datas || _data.list || _data.records || [] | |
| 406 | + this.productList = list.map(it => ({ | |
| 407 | + label: it.productName || it.name || '', | |
| 408 | + value: it.productId || it.id || '', | |
| 409 | + lossRate: it.fixedLossRate != null ? it.fixedLossRate : (it.lossRate != null ? it.lossRate : (it.attritionRate != null ? it.attritionRate : '')) | |
| 410 | + })) | |
| 411 | + } catch (e) { | |
| 412 | + this.productList = [] | |
| 413 | + } | |
| 414 | + }, | |
| 415 | + // 供应商列表数据源(RelateSelectSheet fetchFn) | |
| 416 | + fetchSuppliers({ pageIndex, pageSize, extra }) { | |
| 417 | + const name = (extra && (extra.name || extra.keyword || extra.key)) || '' | |
| 418 | + const params = { | |
| 419 | + pageIndex, | |
| 420 | + pageSize, | |
| 421 | + available: true, | |
| 422 | + unitName: name, | |
| 423 | + status: 'PASS', | |
| 424 | + freeze: false | |
| 425 | + } | |
| 426 | + return domesticCustomerCreditQueryApi(params).then(res => { | |
| 427 | + const _data = res && res.data ? res.data : {} | |
| 428 | + const records = _data.datas || _data.list || _data.records || [] | |
| 429 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 430 | + const hasNext = _data.hasNext || false | |
| 431 | + return { records, totalCount, hasNext } | |
| 432 | + }).catch(() => ({ records: [], totalCount: 0, hasNext: false })) | |
| 433 | + }, | |
| 434 | + openSupplierSheet() { | |
| 435 | + this.supplierSheet.selectedKeys = this.form.supplierId ? [this.form.supplierId] : [] | |
| 436 | + this.supplierSheet.visible = true | |
| 437 | + }, | |
| 438 | + onSupplierConfirm({ items }) { | |
| 439 | + const first = (items && items.length > 0) ? items[0] : null | |
| 440 | + if (!first) return | |
| 441 | + this.form.supplierId = first.id || '' | |
| 442 | + this.form.supplierName = first.unitName || '' | |
| 443 | + this.form.purchaseDepartmentName = first.purchaseDepartmentName || '' | |
| 444 | + this.form.regionName = first.regionName || '' | |
| 445 | + }, | |
| 446 | + boolText(v) { | |
| 447 | + if (typeof v !== 'boolean') return '请选择' | |
| 448 | + return v ? '是' : '否' | |
| 449 | + }, | |
| 450 | + openSheet(field) { | |
| 451 | + const opts = [{ label: '是', value: true }, { label: '否', value: false }] | |
| 452 | + const cur = this.form[field] | |
| 453 | + const match = opts.find(o => o.value === cur) | |
| 454 | + const titleMap = { | |
| 455 | + longTermOperation: '是否长单操作', | |
| 456 | + terminalCustomer: '是否终端客户', | |
| 457 | + priced: '是否未点价', | |
| 458 | + otherExpenseSettlement: '其他费用凭票到厂另行结算', | |
| 459 | + commodityBuyout: '是否通货买断', | |
| 460 | + qualityBuyout: '是否质量买断', | |
| 461 | + quantityBuyout: '是否数量买断', | |
| 462 | + doorDelivery: '是否送货上门' | |
| 463 | + } | |
| 464 | + this.sheet = { visible: true, title: titleMap[field] || '请选择', options: opts, value: match ? match.value : '', field, isDict: false } | |
| 465 | + }, | |
| 466 | + openDictSheet(field, dictCode, title) { | |
| 467 | + const raw = this.getDictOptions(dictCode) || [] | |
| 468 | + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') })) | |
| 469 | + const cur = this.form[field] | |
| 470 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 471 | + this.sheet = { visible: true, title: title || '请选择', options: opts, value: match ? match.value : '', field, isDict: true } | |
| 472 | + }, | |
| 473 | + getDeliveryMethodLabel() { | |
| 474 | + const opts = this.getDictOptions('DELIVERY_METHOD') || [] | |
| 475 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.deliveryMode)) | |
| 476 | + return m ? (m.name || m.label || '') : '' | |
| 477 | + }, | |
| 478 | + onSheetConfirm({ value }) { | |
| 479 | + const field = this.sheet.field | |
| 480 | + if (!field) return | |
| 481 | + if (this.sheet.isDict) { | |
| 482 | + this.form[field] = value | |
| 483 | + } else { | |
| 484 | + this.form[field] = !!value | |
| 485 | + // 切换 是否未点价 时,若改为 是 则清空点价截止日期 | |
| 486 | + if (field === 'priced' && value === true) { | |
| 487 | + this.form.pricingDeadline = '' | |
| 488 | + } | |
| 489 | + } | |
| 490 | + }, | |
| 491 | + onRatioBlur() { | |
| 492 | + let v = parseFloat(this.form.settlementRatio) | |
| 493 | + if (isNaN(v)) v = '' | |
| 494 | + else v = Math.round(v * 100) / 100 | |
| 495 | + this.form.settlementRatio = v | |
| 496 | + }, | |
| 497 | + onProductsChange(products) { | |
| 498 | + this.productLineList = Array.isArray(products) ? products : [] | |
| 499 | + }, | |
| 500 | + validateRequired() { | |
| 501 | + const stringChecks = [ | |
| 502 | + { key: 'code', label: '编号', msg: '请输入编号' }, | |
| 503 | + { key: 'supplierId', label: '供应商名称', msg: '请选择供应商' }, | |
| 504 | + { key: 'validityPeriod', label: '报价时效', msg: '请输入报价时效' }, | |
| 505 | + { key: 'deliveryMode', label: '交提货方式', msg: '请选择交提货方式' }, | |
| 506 | + { key: 'invoiceProductName', label: '开票品种', msg: '请输入开票品种' }, | |
| 507 | + { key: 'deliveryDate', label: '交货日期', msg: '请选择交货日期' }, | |
| 508 | + { key: 'settlementMethod', label: '结算方式', msg: '请输入结算方式' }, | |
| 509 | + { key: 'settlementRatio', label: '结算比例', msg: '请输入结算比例' } | |
| 510 | + ] | |
| 511 | + for (const it of stringChecks) { | |
| 512 | + const val = this.form[it.key] | |
| 513 | + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) | |
| 514 | + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false } | |
| 515 | + } | |
| 516 | + const boolChecks = [ | |
| 517 | + { key: 'longTermOperation', msg: '请选择是否长单操作' }, | |
| 518 | + { key: 'terminalCustomer', msg: '请选择是否终端客户' }, | |
| 519 | + { key: 'priced', msg: '请选择是否未点价' }, | |
| 520 | + { key: 'otherExpenseSettlement', msg: '请选择其他费用凭票到厂另行结算' }, | |
| 521 | + { key: 'commodityBuyout', msg: '请选择是否通货买断' }, | |
| 522 | + { key: 'qualityBuyout', msg: '请选择是否质量买断' }, | |
| 523 | + { key: 'quantityBuyout', msg: '请选择是否数量买断' }, | |
| 524 | + { key: 'doorDelivery', msg: '请选择是否送货上门' } | |
| 525 | + ] | |
| 526 | + for (const it of boolChecks) { | |
| 527 | + if (typeof this.form[it.key] !== 'boolean') { | |
| 528 | + uni.showToast({ title: it.msg, icon: 'none' }) | |
| 529 | + return false | |
| 530 | + } | |
| 531 | + } | |
| 532 | + // 点价截止日期:是否未点价=否 时必填 | |
| 533 | + if (this.form.priced === false && !this.form.pricingDeadline) { | |
| 534 | + uni.showToast({ title: '请选择点价截止日期', icon: 'none' }) | |
| 535 | + return false | |
| 536 | + } | |
| 537 | + // 明细校验 | |
| 538 | + const list = Array.isArray(this.productLineList) ? this.productLineList : [] | |
| 539 | + if (list.length === 0) { | |
| 540 | + uni.showToast({ title: '请添加产品明细', icon: 'none' }) | |
| 541 | + return false | |
| 542 | + } | |
| 543 | + if (this.$refs.productRel && !this.$refs.productRel.validate(this.form.priced)) return false | |
| 544 | + return true | |
| 545 | + }, | |
| 546 | + async onSubmit() { | |
| 547 | + if (!this.validateRequired()) return | |
| 548 | + const confirmRes = await new Promise(resolve => { | |
| 549 | + uni.showModal({ title: '提示', content: '确定新增业务员报价单吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 550 | + }) | |
| 551 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 552 | + const clean = (obj) => { | |
| 553 | + const out = {} | |
| 554 | + Object.keys(obj || {}).forEach(k => { | |
| 555 | + const v = obj[k] | |
| 556 | + const isEmptyString = typeof v === 'string' && v.trim() === '' | |
| 557 | + const isUndef = v === undefined || v === null | |
| 558 | + const isNaNNumber = typeof v === 'number' && isNaN(v) | |
| 559 | + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v | |
| 560 | + }) | |
| 561 | + return out | |
| 562 | + } | |
| 563 | + const lines = (this.productLineList || []).map(it => clean(it)) | |
| 564 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 565 | + const formCopy = { ...this.form } | |
| 566 | + materialFields.forEach(f => { | |
| 567 | + const arr = formCopy[f] | |
| 568 | + formCopy[f] = Array.isArray(arr) ? arr.join(',') : (arr || '') | |
| 569 | + }) | |
| 570 | + const payload = clean({ | |
| 571 | + ...formCopy, | |
| 572 | + quotationLineList: lines | |
| 573 | + }) | |
| 574 | + try { | |
| 575 | + await salesmanQuotationSaveApi(payload) | |
| 576 | + uni.showToast({ title: '新增成功', icon: 'none' }) | |
| 577 | + try { uni.setStorageSync('SALES_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 578 | + setTimeout(() => { uni.redirectTo({ url: '/pages/sales-price/index' }) }, 400) | |
| 579 | + } catch (e) { | |
| 580 | + uni.showToast({ title: e.msg || '新增失败', icon: 'none' }) | |
| 581 | + } | |
| 582 | + } | |
| 583 | + } | |
| 584 | +} | |
| 585 | +</script> | |
| 586 | +<style lang="scss" scoped> | |
| 587 | +.page { | |
| 588 | + display: flex; | |
| 589 | + flex-direction: column; | |
| 590 | + height: 100%; | |
| 591 | +} | |
| 592 | + | |
| 593 | +.scroll { | |
| 594 | + flex: 1; | |
| 595 | + padding: 12rpx 0 392rpx !important; | |
| 596 | +} | |
| 597 | + | |
| 598 | +.footer { | |
| 599 | + z-index: 2; | |
| 600 | + position: fixed; | |
| 601 | + left: 0; | |
| 602 | + right: 0; | |
| 603 | + bottom: 0; | |
| 604 | + padding: 32rpx; | |
| 605 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 606 | + background: #fff; | |
| 607 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 608 | + | |
| 609 | + .btn { | |
| 610 | + height: 80rpx; | |
| 611 | + line-height: 80rpx; | |
| 612 | + border-radius: 12rpx; | |
| 613 | + font-size: 32rpx; | |
| 614 | + } | |
| 615 | + | |
| 616 | + .submit { | |
| 617 | + background: $theme-primary; | |
| 618 | + color: #fff; | |
| 619 | + } | |
| 620 | +} | |
| 621 | + | |
| 622 | +::v-deep .uni-list { | |
| 623 | + .uni-easyinput { | |
| 624 | + display: flex; | |
| 625 | + | |
| 626 | + .uni-input-input { | |
| 627 | + color: rgba(0, 0, 0, 0.9); | |
| 628 | + } | |
| 629 | + } | |
| 630 | + | |
| 631 | + .uni-input-placeholder { | |
| 632 | + z-index: 1; | |
| 633 | + } | |
| 634 | + | |
| 635 | + .uni-input-input { | |
| 636 | + background-color: #ffffff; | |
| 637 | + } | |
| 638 | + | |
| 639 | + background: transparent; | |
| 640 | + | |
| 641 | + &-item { | |
| 642 | + &__extra-text { | |
| 643 | + font-size: 32rpx; | |
| 644 | + } | |
| 645 | + | |
| 646 | + &__content-title { | |
| 647 | + font-size: 32rpx; | |
| 648 | + color: rgba(0, 0, 0, 0.9); | |
| 649 | + } | |
| 650 | + | |
| 651 | + &__container { | |
| 652 | + padding: 32rpx; | |
| 653 | + | |
| 654 | + .uni-easyinput { | |
| 655 | + &__placeholder-class { | |
| 656 | + font-size: 32rpx; | |
| 657 | + color: rgba(0, 0, 0, 0.4); | |
| 658 | + } | |
| 659 | + | |
| 660 | + &__content { | |
| 661 | + border: none; | |
| 662 | + background-color: #ffffff !important; | |
| 663 | + | |
| 664 | + &-input { | |
| 665 | + padding-left: 0 !important; | |
| 666 | + height: 48rpx; | |
| 667 | + line-height: 48rpx; | |
| 668 | + font-size: 32rpx; | |
| 669 | + } | |
| 670 | + | |
| 671 | + .content-clear-icon { | |
| 672 | + font-size: 44rpx !important; | |
| 673 | + } | |
| 674 | + } | |
| 675 | + } | |
| 676 | + | |
| 677 | + .item-title, | |
| 678 | + .uni-list-item__content { | |
| 679 | + flex: none; | |
| 680 | + min-height: 48rpx; | |
| 681 | + line-height: 48rpx; | |
| 682 | + font-size: 32rpx; | |
| 683 | + position: relative; | |
| 684 | + width: 240rpx; | |
| 685 | + margin-right: 32rpx; | |
| 686 | + color: rgba(0, 0, 0, 0.9); | |
| 687 | + | |
| 688 | + .required { | |
| 689 | + color: red; | |
| 690 | + position: absolute; | |
| 691 | + top: 50%; | |
| 692 | + transform: translateY(-50%); | |
| 693 | + left: -16rpx; | |
| 694 | + } | |
| 695 | + } | |
| 696 | + } | |
| 697 | + | |
| 698 | + &.select-item { | |
| 699 | + &.is-empty { | |
| 700 | + .uni-list-item__extra-text { | |
| 701 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 702 | + } | |
| 703 | + } | |
| 704 | + | |
| 705 | + &.is-filled { | |
| 706 | + .uni-list-item__extra-text { | |
| 707 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 708 | + } | |
| 709 | + } | |
| 710 | + | |
| 711 | + .serial-number-row { | |
| 712 | + display: flex; | |
| 713 | + align-items: center; | |
| 714 | + | |
| 715 | + .generate-btn { | |
| 716 | + margin-left: 24rpx; | |
| 717 | + height: 48rpx; | |
| 718 | + line-height: 48rpx; | |
| 719 | + padding: 0 24rpx; | |
| 720 | + font-size: 24rpx; | |
| 721 | + border-radius: 8rpx; | |
| 722 | + background: $theme-primary; | |
| 723 | + color: #fff; | |
| 724 | + } | |
| 725 | + } | |
| 726 | + } | |
| 727 | + } | |
| 728 | +} | |
| 729 | + | |
| 730 | +.readonly-text { | |
| 731 | + color: rgba(0, 0, 0, 0.9); | |
| 732 | + font-size: 32rpx; | |
| 733 | + line-height: 48rpx; | |
| 734 | + text-align: right; | |
| 735 | + white-space: pre-wrap; | |
| 736 | + word-break: break-all; | |
| 737 | +} | |
| 738 | + | |
| 739 | +.value-code { | |
| 740 | + width: 260rpx; | |
| 741 | + text-align: left; | |
| 742 | +} | |
| 743 | + | |
| 744 | +.material-condition-section, | |
| 745 | +.remark-section { | |
| 746 | + margin-top: 20rpx; | |
| 747 | + background: #fff; | |
| 748 | +} | |
| 749 | + | |
| 750 | +.section-header { | |
| 751 | + display: flex; | |
| 752 | + align-items: center; | |
| 753 | + padding: 24rpx 32rpx; | |
| 754 | + border-bottom: 1rpx solid #f0f0f0; | |
| 755 | +} | |
| 756 | + | |
| 757 | +.section-title { | |
| 758 | + font-size: 32rpx; | |
| 759 | + color: rgba(0, 0, 0, 0.9); | |
| 760 | + font-weight: 600; | |
| 761 | +} | |
| 762 | + | |
| 763 | +.opCollapse { | |
| 764 | + width: 32rpx; | |
| 765 | + height: 28rpx; | |
| 766 | + margin-right: 16rpx; | |
| 767 | + margin-top: 8rpx; | |
| 768 | +} | |
| 769 | + | |
| 770 | +.section-content { | |
| 771 | + padding: 32rpx; | |
| 772 | +} | |
| 773 | + | |
| 774 | +.form-row { | |
| 775 | + display: flex; | |
| 776 | + align-items: center; | |
| 777 | + justify-content: space-between; | |
| 778 | + margin-bottom: 24rpx; | |
| 779 | +} | |
| 780 | + | |
| 781 | +.form-row:last-child { | |
| 782 | + margin-bottom: 0; | |
| 783 | +} | |
| 784 | + | |
| 785 | +.form-label { | |
| 786 | + font-size: 32rpx; | |
| 787 | + color: rgba(0, 0, 0, 0.9); | |
| 788 | +} | |
| 789 | + | |
| 790 | +.bool-btns { | |
| 791 | + display: flex; | |
| 792 | + gap: 20rpx; | |
| 793 | +} | |
| 794 | + | |
| 795 | +.bool-btn { | |
| 796 | + padding: 12rpx 40rpx; | |
| 797 | + border: 1rpx solid #ddd; | |
| 798 | + border-radius: 8rpx; | |
| 799 | + font-size: 28rpx; | |
| 800 | + color: #666; | |
| 801 | + background: #fff; | |
| 802 | +} | |
| 803 | + | |
| 804 | +.bool-btn.active { | |
| 805 | + background: $theme-primary; | |
| 806 | + border-color: $theme-primary; | |
| 807 | + color: #fff; | |
| 808 | +} | |
| 809 | + | |
| 810 | +.material-group { | |
| 811 | + margin-top: 32rpx; | |
| 812 | +} | |
| 813 | + | |
| 814 | +.material-label { | |
| 815 | + display: block; | |
| 816 | + font-size: 30rpx; | |
| 817 | + color: rgba(0, 0, 0, 0.9); | |
| 818 | + margin-bottom: 20rpx; | |
| 819 | +} | |
| 820 | + | |
| 821 | +.tag-list { | |
| 822 | + display: flex; | |
| 823 | + flex-wrap: wrap; | |
| 824 | + gap: 16rpx; | |
| 825 | +} | |
| 826 | + | |
| 827 | +.tag-btn { | |
| 828 | + padding: 12rpx 24rpx; | |
| 829 | + border: 1rpx solid #ddd; | |
| 830 | + border-radius: 8rpx; | |
| 831 | + font-size: 28rpx; | |
| 832 | + color: #666; | |
| 833 | + background: #fff; | |
| 834 | +} | |
| 835 | + | |
| 836 | +.tag-btn.active { | |
| 837 | + background: $theme-primary; | |
| 838 | + border-color: $theme-primary; | |
| 839 | + color: #fff; | |
| 840 | +} | |
| 841 | + | |
| 842 | +::v-deep .uni-easyinput { | |
| 843 | + width: 100%; | |
| 844 | + | |
| 845 | + &__content { | |
| 846 | + background: #f8f8f8 !important; | |
| 847 | + border-radius: 8rpx; | |
| 848 | + | |
| 849 | + &-input { | |
| 850 | + font-size: 30rpx; | |
| 851 | + } | |
| 852 | + } | |
| 853 | +} | |
| 854 | +</style> | ... | ... |
pages/sales-price/copy.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <!-- 编号 --> | |
| 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 | + <view class="serial-number-row"> | |
| 12 | + <text class="readonly-text value-code">{{ form.code }}</text> | |
| 13 | + <button class="generate-btn" @click="initCode">点此生成</button> | |
| 14 | + </view> | |
| 15 | + </template> | |
| 16 | + </uni-list-item> | |
| 17 | + | |
| 18 | + <!-- 供应商名称 --> | |
| 19 | + <uni-list-item class="select-item" :class="form.supplierId ? 'is-filled' : 'is-empty'" clickable | |
| 20 | + @click="openSupplierSheet" :rightText="form.supplierName || '请选择供应商名称'" showArrow> | |
| 21 | + <template v-slot:body> | |
| 22 | + <view class="item-title"><text class="required">*</text><text>供应商名称</text></view> | |
| 23 | + </template> | |
| 24 | + </uni-list-item> | |
| 25 | + | |
| 26 | + <!-- 采购处(带出,禁用) --> | |
| 27 | + <uni-list-item title="采购处"> | |
| 28 | + <template v-slot:footer> | |
| 29 | + <uni-easyinput v-model="form.purchaseDepartmentName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 30 | + </template> | |
| 31 | + </uni-list-item> | |
| 32 | + | |
| 33 | + <!-- 区域(带出,禁用) --> | |
| 34 | + <uni-list-item title="区域"> | |
| 35 | + <template v-slot:footer> | |
| 36 | + <uni-easyinput v-model="form.regionName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 37 | + </template> | |
| 38 | + </uni-list-item> | |
| 39 | + | |
| 40 | + <!-- 报价时效 --> | |
| 41 | + <uni-list-item> | |
| 42 | + <template v-slot:body> | |
| 43 | + <view class="item-title"><text class="required">*</text><text>报价时效</text></view> | |
| 44 | + </template> | |
| 45 | + <template v-slot:footer> | |
| 46 | + <uni-easyinput v-model="form.validityPeriod" :inputBorder="false" placeholder="请输入报价时效" /> | |
| 47 | + </template> | |
| 48 | + </uni-list-item> | |
| 49 | + | |
| 50 | + <!-- 报价人(禁用) --> | |
| 51 | + <uni-list-item title="报价人"> | |
| 52 | + <template v-slot:footer> | |
| 53 | + <uni-easyinput v-model="form.quoterName" :inputBorder="false" disabled placeholder="自动获取" /> | |
| 54 | + </template> | |
| 55 | + </uni-list-item> | |
| 56 | + | |
| 57 | + <!-- 是否长单操作 --> | |
| 58 | + <uni-list-item class="select-item" :class="typeof form.longTermOperation === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 59 | + @click="openSheet('longTermOperation')" :rightText="boolText(form.longTermOperation)" showArrow> | |
| 60 | + <template v-slot:body> | |
| 61 | + <view class="item-title"><text class="required">*</text><text>是否长单操作</text></view> | |
| 62 | + </template> | |
| 63 | + </uni-list-item> | |
| 64 | + | |
| 65 | + <!-- 是否终端客户 --> | |
| 66 | + <uni-list-item class="select-item" :class="typeof form.terminalCustomer === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 67 | + @click="openSheet('terminalCustomer')" :rightText="boolText(form.terminalCustomer)" showArrow> | |
| 68 | + <template v-slot:body> | |
| 69 | + <view class="item-title"><text class="required">*</text><text>是否终端客户</text></view> | |
| 70 | + </template> | |
| 71 | + </uni-list-item> | |
| 72 | + | |
| 73 | + <!-- 是否未点价 --> | |
| 74 | + <uni-list-item class="select-item" :class="typeof form.priced === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 75 | + @click="openSheet('priced')" :rightText="boolText(form.priced)" showArrow> | |
| 76 | + <template v-slot:body> | |
| 77 | + <view class="item-title"><text class="required">*</text><text>是否未点价</text></view> | |
| 78 | + </template> | |
| 79 | + </uni-list-item> | |
| 80 | + | |
| 81 | + <!-- 点价截止日期 - 仅在 是否未点价 = 否 时显示且必填 --> | |
| 82 | + <uni-list-item v-if="form.priced === false" class="select-item" :class="form.pricingDeadline ? 'is-filled' : 'is-empty'"> | |
| 83 | + <template v-slot:body> | |
| 84 | + <view class="item-title"><text class="required">*</text><text>点价截止日期</text></view> | |
| 85 | + </template> | |
| 86 | + <template v-slot:footer> | |
| 87 | + <uni-datetime-picker type="date" v-model="form.pricingDeadline" /> | |
| 88 | + </template> | |
| 89 | + </uni-list-item> | |
| 90 | + | |
| 91 | + <!-- 其他费用凭票到厂另行结算 --> | |
| 92 | + <uni-list-item class="select-item" :class="typeof form.otherExpenseSettlement === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 93 | + @click="openSheet('otherExpenseSettlement')" :rightText="boolText(form.otherExpenseSettlement)" showArrow> | |
| 94 | + <template v-slot:body> | |
| 95 | + <view class="item-title"><text class="required">*</text><text>其他费用凭票到厂另行结算</text></view> | |
| 96 | + </template> | |
| 97 | + </uni-list-item> | |
| 98 | + | |
| 99 | + <!-- 交提货方式 --> | |
| 100 | + <uni-list-item class="select-item" :class="form.deliveryMode ? 'is-filled' : 'is-empty'" clickable | |
| 101 | + @click="openDictSheet('deliveryMode', 'DELIVERY_METHOD', '交提货方式')" :rightText="getDeliveryMethodLabel() || '请选择交提货方式'" showArrow> | |
| 102 | + <template v-slot:body> | |
| 103 | + <view class="item-title"><text class="required">*</text><text>交提货方式</text></view> | |
| 104 | + </template> | |
| 105 | + </uni-list-item> | |
| 106 | + | |
| 107 | + <!-- 是否通货买断 --> | |
| 108 | + <uni-list-item class="select-item" :class="typeof form.commodityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 109 | + @click="openSheet('commodityBuyout')" :rightText="boolText(form.commodityBuyout)" showArrow> | |
| 110 | + <template v-slot:body> | |
| 111 | + <view class="item-title"><text class="required">*</text><text>是否通货买断</text></view> | |
| 112 | + </template> | |
| 113 | + </uni-list-item> | |
| 114 | + | |
| 115 | + <!-- 是否质量买断 --> | |
| 116 | + <uni-list-item class="select-item" :class="typeof form.qualityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 117 | + @click="openSheet('qualityBuyout')" :rightText="boolText(form.qualityBuyout)" showArrow> | |
| 118 | + <template v-slot:body> | |
| 119 | + <view class="item-title"><text class="required">*</text><text>是否质量买断</text></view> | |
| 120 | + </template> | |
| 121 | + </uni-list-item> | |
| 122 | + | |
| 123 | + <!-- 是否数量买断 --> | |
| 124 | + <uni-list-item class="select-item" :class="typeof form.quantityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 125 | + @click="openSheet('quantityBuyout')" :rightText="boolText(form.quantityBuyout)" showArrow> | |
| 126 | + <template v-slot:body> | |
| 127 | + <view class="item-title"><text class="required">*</text><text>是否数量买断</text></view> | |
| 128 | + </template> | |
| 129 | + </uni-list-item> | |
| 130 | + | |
| 131 | + <!-- 是否送货上门 --> | |
| 132 | + <uni-list-item class="select-item" :class="typeof form.doorDelivery === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 133 | + @click="openSheet('doorDelivery')" :rightText="boolText(form.doorDelivery)" showArrow> | |
| 134 | + <template v-slot:body> | |
| 135 | + <view class="item-title"><text class="required">*</text><text>是否送货上门</text></view> | |
| 136 | + </template> | |
| 137 | + </uni-list-item> | |
| 138 | + | |
| 139 | + <!-- 装车费 --> | |
| 140 | + <uni-list-item title="装车费"> | |
| 141 | + <template v-slot:footer> | |
| 142 | + <uni-easyinput v-model="form.loadingFee" type="digit" :inputBorder="false" placeholder="请输入装车费" /> | |
| 143 | + </template> | |
| 144 | + </uni-list-item> | |
| 145 | + | |
| 146 | + <!-- 开票品种 --> | |
| 147 | + <uni-list-item> | |
| 148 | + <template v-slot:body> | |
| 149 | + <view class="item-title"><text class="required">*</text><text>开票品种</text></view> | |
| 150 | + </template> | |
| 151 | + <template v-slot:footer> | |
| 152 | + <uni-easyinput v-model="form.invoiceProductName" :inputBorder="false" placeholder="请输入开票品种" /> | |
| 153 | + </template> | |
| 154 | + </uni-list-item> | |
| 155 | + | |
| 156 | + <!-- 交货日期 --> | |
| 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-datetime-picker type="date" v-model="form.deliveryDate" /> | |
| 163 | + </template> | |
| 164 | + </uni-list-item> | |
| 165 | + | |
| 166 | + <!-- 结算方式 --> | |
| 167 | + <uni-list-item> | |
| 168 | + <template v-slot:body> | |
| 169 | + <view class="item-title"><text class="required">*</text><text>结算方式</text></view> | |
| 170 | + </template> | |
| 171 | + <template v-slot:footer> | |
| 172 | + <uni-easyinput v-model="form.settlementMethod" :inputBorder="false" placeholder="请输入结算方式" /> | |
| 173 | + </template> | |
| 174 | + </uni-list-item> | |
| 175 | + | |
| 176 | + <!-- 结算比例(%) --> | |
| 177 | + <uni-list-item> | |
| 178 | + <template v-slot:body> | |
| 179 | + <view class="item-title"><text class="required">*</text><text>结算比例(%)</text></view> | |
| 180 | + </template> | |
| 181 | + <template v-slot:footer> | |
| 182 | + <uni-easyinput v-model="form.settlementRatio" type="digit" :inputBorder="false" placeholder="请输入结算比例" @blur="onRatioBlur" /> | |
| 183 | + </template> | |
| 184 | + </uni-list-item> | |
| 185 | + | |
| 186 | + <!-- 协调事由 --> | |
| 187 | + <uni-list-item title="协调事由"> | |
| 188 | + <template v-slot:footer> | |
| 189 | + <uni-easyinput v-model="form.coordinationReason" :inputBorder="false" placeholder="请输入协调事由" disabled /> | |
| 190 | + </template> | |
| 191 | + </uni-list-item> | |
| 192 | + | |
| 193 | + <!-- 票类状况 --> | |
| 194 | + <uni-list-item title="票类状况"> | |
| 195 | + <template v-slot:footer> | |
| 196 | + <uni-easyinput v-model="form.invoiceTypeStatus" :inputBorder="false" placeholder="请输入票类状况" disabled /> | |
| 197 | + </template> | |
| 198 | + </uni-list-item> | |
| 199 | + | |
| 200 | + <!-- 明细信息 --> | |
| 201 | + <ProductRel ref="productRel" mode="add" :options="productList" :priced="form.priced" :list="productLineList" @change="onProductsChange" /> | |
| 202 | + </uni-list> | |
| 203 | + | |
| 204 | + <!-- 料质状况模块(明细下方) --> | |
| 205 | + <view class="material-condition-section"> | |
| 206 | + <view class="section-header"> | |
| 207 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 208 | + <text class="section-title">料质状况</text> | |
| 209 | + </view> | |
| 210 | + <view class="section-content"> | |
| 211 | + <view class="form-row"> | |
| 212 | + <text class="form-label">是否启用料质状况</text> | |
| 213 | + <view class="bool-btns"> | |
| 214 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === true }" @click="form.materialConditionEnabled = true">是</view> | |
| 215 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === false }" @click="form.materialConditionEnabled = false">否</view> | |
| 216 | + </view> | |
| 217 | + </view> | |
| 218 | + | |
| 219 | + <template v-if="form.materialConditionEnabled"> | |
| 220 | + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group"> | |
| 221 | + <text class="material-label">{{ mc.label }}</text> | |
| 222 | + <view class="tag-list"> | |
| 223 | + <view | |
| 224 | + v-for="opt in getDictOptions(mc.dictCode)" | |
| 225 | + :key="opt.code" | |
| 226 | + class="tag-btn" | |
| 227 | + :class="{ active: isTagSelected(mc.field, opt.code) }" | |
| 228 | + @click="toggleTag(mc.field, opt.code)" | |
| 229 | + > | |
| 230 | + {{ opt.name }} | |
| 231 | + </view> | |
| 232 | + </view> | |
| 233 | + </view> | |
| 234 | + </template> | |
| 235 | + </view> | |
| 236 | + </view> | |
| 237 | + | |
| 238 | + <!-- 备注模块 --> | |
| 239 | + <view class="remark-section"> | |
| 240 | + <view class="section-header"> | |
| 241 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 242 | + <text class="section-title">备注</text> | |
| 243 | + </view> | |
| 244 | + <view class="section-content"> | |
| 245 | + <uni-easyinput v-model="form.remark" type="textarea" :inputBorder="false" placeholder="请输入备注" maxlength="2000" /> | |
| 246 | + </view> | |
| 247 | + </view> | |
| 248 | + | |
| 249 | + <view class="footer"> | |
| 250 | + <button class="btn submit" type="primary" @click="onSubmit">提交</button> | |
| 251 | + </view> | |
| 252 | + </scroll-view> | |
| 253 | + | |
| 254 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" | |
| 255 | + v-model="sheet.value" @confirm="onSheetConfirm" /> | |
| 256 | + | |
| 257 | + <RelateSelectSheet :visible.sync="supplierSheet.visible" :title="supplierSheet.title" | |
| 258 | + :fetchFn="supplierSheet.fetchFn" :display-fields="supplierSheet.displayFields" | |
| 259 | + :multiple="false" row-key="id" :selectedKeys.sync="supplierSheet.selectedKeys" | |
| 260 | + @confirm="onSupplierConfirm" /> | |
| 261 | + </view> | |
| 262 | +</template> | |
| 263 | +<script> | |
| 264 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 265 | +import RelateSelectSheet from '@/components/relate-select/index.vue' | |
| 266 | +import ProductRel from './productRel.vue' | |
| 267 | +import { salesmanQuotationDetailApi, salesmanQuotationGenerateCodeApi, salesmanQuotationSaveApi, breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 268 | +import { domesticCustomerCreditQueryApi } from '@/api/procure-manage/domesticCustomerCredit.js' | |
| 269 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 270 | + | |
| 271 | +export default { | |
| 272 | + name: 'CopySalesPrice', | |
| 273 | + components: { SingleSelectSheet, RelateSelectSheet, ProductRel }, | |
| 274 | + data() { | |
| 275 | + return { | |
| 276 | + sourceId: '', | |
| 277 | + form: { | |
| 278 | + code: '', | |
| 279 | + supplierId: '', | |
| 280 | + supplierName: '', | |
| 281 | + purchaseDepartmentName: '', | |
| 282 | + regionName: '', | |
| 283 | + validityPeriod: '', | |
| 284 | + quoterName: '', | |
| 285 | + longTermOperation: false, | |
| 286 | + terminalCustomer: false, | |
| 287 | + priced: true, | |
| 288 | + pricingDeadline: '', | |
| 289 | + otherExpenseSettlement: false, | |
| 290 | + deliveryMode: '', | |
| 291 | + commodityBuyout: false, | |
| 292 | + qualityBuyout: false, | |
| 293 | + quantityBuyout: false, | |
| 294 | + doorDelivery: false, | |
| 295 | + loadingFee: '', | |
| 296 | + invoiceProductName: '', | |
| 297 | + deliveryDate: '', | |
| 298 | + settlementMethod: '', | |
| 299 | + settlementRatio: '', | |
| 300 | + coordinationReason: '', | |
| 301 | + invoiceTypeStatus: '', | |
| 302 | + materialConditionEnabled: false, | |
| 303 | + materialConditionCopperRice: [], | |
| 304 | + materialConditionBrightCopper: [], | |
| 305 | + materialConditionFirstClass: [], | |
| 306 | + materialConditionSecondClass: [], | |
| 307 | + materialConditionTinCoated: [], | |
| 308 | + materialConditionScrap: [], | |
| 309 | + materialConditionWhiteYellow: [], | |
| 310 | + materialConditionPhosphorCopper: [], | |
| 311 | + remark: '' | |
| 312 | + }, | |
| 313 | + productList: [], | |
| 314 | + productLineList: [], | |
| 315 | + sheet: { visible: false, title: '请选择', options: [], value: '', field: '' }, | |
| 316 | + supplierSheet: { | |
| 317 | + visible: false, | |
| 318 | + title: '选择供应商', | |
| 319 | + fetchFn: null, | |
| 320 | + displayFields: [ | |
| 321 | + { label: '单位名称', field: 'unitName' }, | |
| 322 | + { label: '采购处', field: 'purchaseDepartmentName' }, | |
| 323 | + { label: '区域', field: 'regionName' } | |
| 324 | + ], | |
| 325 | + selectedKeys: [] | |
| 326 | + }, | |
| 327 | + materialConditionConfigs: [ | |
| 328 | + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' }, | |
| 329 | + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' }, | |
| 330 | + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' }, | |
| 331 | + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' }, | |
| 332 | + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' }, | |
| 333 | + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' }, | |
| 334 | + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' }, | |
| 335 | + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' } | |
| 336 | + ], | |
| 337 | + dictData: {} | |
| 338 | + } | |
| 339 | + }, | |
| 340 | + onLoad(query) { | |
| 341 | + this.sourceId = (query && query.id) || '' | |
| 342 | + this.supplierSheet.fetchFn = this.fetchSuppliers.bind(this) | |
| 343 | + this.loadProducts() | |
| 344 | + this.loadDictData() | |
| 345 | + this.loadSourceDetail() | |
| 346 | + this.initCode() | |
| 347 | + }, | |
| 348 | + methods: { | |
| 349 | + async loadDictData() { | |
| 350 | + try { | |
| 351 | + const codes = [...this.materialConditionConfigs.map(mc => mc.dictCode), 'DELIVERY_METHOD'] | |
| 352 | + const res = await getDicByCodes(codes) | |
| 353 | + this.dictData = res || {} | |
| 354 | + } catch (e) { | |
| 355 | + this.dictData = {} | |
| 356 | + } | |
| 357 | + }, | |
| 358 | + getDictOptions(dictCode) { | |
| 359 | + const data = this.dictData[dictCode] | |
| 360 | + return (data && data.data) ? data.data : [] | |
| 361 | + }, | |
| 362 | + isTagSelected(field, code) { | |
| 363 | + const selected = this.form[field] | |
| 364 | + return Array.isArray(selected) && selected.includes(code) | |
| 365 | + }, | |
| 366 | + toggleTag(field, code) { | |
| 367 | + let selected = this.form[field] | |
| 368 | + if (!Array.isArray(selected)) { | |
| 369 | + selected = [] | |
| 370 | + } | |
| 371 | + const index = selected.indexOf(code) | |
| 372 | + if (index > -1) { | |
| 373 | + selected.splice(index, 1) | |
| 374 | + } else { | |
| 375 | + selected.push(code) | |
| 376 | + } | |
| 377 | + this.$set(this.form, field, [...selected]) | |
| 378 | + }, | |
| 379 | + async initCode() { | |
| 380 | + try { | |
| 381 | + const res = await salesmanQuotationGenerateCodeApi() | |
| 382 | + this.form.code = (res && res.data) ? res.data : '' | |
| 383 | + } catch (e) { | |
| 384 | + this.form.code = '' | |
| 385 | + } | |
| 386 | + }, | |
| 387 | + async loadProducts() { | |
| 388 | + try { | |
| 389 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 390 | + const _data = res && res.data ? res.data : {} | |
| 391 | + const list = _data.datas || _data.list || _data.records || [] | |
| 392 | + this.productList = list.map(it => ({ | |
| 393 | + label: it.productName || it.name || '', | |
| 394 | + value: it.productId || it.id || '', | |
| 395 | + lossRate: it.fixedLossRate != null ? it.fixedLossRate : (it.lossRate != null ? it.lossRate : (it.attritionRate != null ? it.attritionRate : '')) | |
| 396 | + })) | |
| 397 | + } catch (e) { | |
| 398 | + this.productList = [] | |
| 399 | + } | |
| 400 | + }, | |
| 401 | + async loadSourceDetail() { | |
| 402 | + if (!this.sourceId) return | |
| 403 | + try { | |
| 404 | + const res = await salesmanQuotationDetailApi({ id: this.sourceId }) | |
| 405 | + const d = (res && res.data) ? res.data : {} | |
| 406 | + // 清空 id 和 code,复制为新增 | |
| 407 | + delete d.id | |
| 408 | + delete d.code | |
| 409 | + // 清空明细行的 id 和 itemId | |
| 410 | + const lineList = Array.isArray(d.quotationLineList) ? d.quotationLineList : (Array.isArray(d.salesmanQuotationLineList) ? d.salesmanQuotationLineList : []) | |
| 411 | + lineList.forEach(it => { | |
| 412 | + delete it.id | |
| 413 | + delete it.itemId | |
| 414 | + }) | |
| 415 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 416 | + materialFields.forEach(f => { | |
| 417 | + const v = d[f] | |
| 418 | + d[f] = (typeof v === 'string' && v) ? v.split(',').filter(Boolean) : (Array.isArray(v) ? v : []) | |
| 419 | + }) | |
| 420 | + this.form = { ...this.form, ...d } | |
| 421 | + this.productLineList = lineList | |
| 422 | + } catch (e) { | |
| 423 | + uni.showToast({ title: '加载源数据失败', icon: 'none' }) | |
| 424 | + } | |
| 425 | + }, | |
| 426 | + fetchSuppliers({ pageIndex, pageSize, extra }) { | |
| 427 | + const name = (extra && (extra.name || extra.keyword || extra.key)) || '' | |
| 428 | + const params = { | |
| 429 | + pageIndex, | |
| 430 | + pageSize, | |
| 431 | + available: true, | |
| 432 | + unitName: name, | |
| 433 | + status: 'PASS', | |
| 434 | + freeze: false | |
| 435 | + } | |
| 436 | + return domesticCustomerCreditQueryApi(params).then(res => { | |
| 437 | + const _data = res && res.data ? res.data : {} | |
| 438 | + const records = _data.datas || _data.list || _data.records || [] | |
| 439 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 440 | + const hasNext = _data.hasNext || false | |
| 441 | + return { records, totalCount, hasNext } | |
| 442 | + }).catch(() => ({ records: [], totalCount: 0, hasNext: false })) | |
| 443 | + }, | |
| 444 | + openSupplierSheet() { | |
| 445 | + this.supplierSheet.selectedKeys = this.form.supplierId ? [this.form.supplierId] : [] | |
| 446 | + this.supplierSheet.visible = true | |
| 447 | + }, | |
| 448 | + onSupplierConfirm({ items }) { | |
| 449 | + const first = (items && items.length > 0) ? items[0] : null | |
| 450 | + if (!first) return | |
| 451 | + this.form.supplierId = first.id || '' | |
| 452 | + this.form.supplierName = first.unitName || '' | |
| 453 | + this.form.purchaseDepartmentName = first.purchaseDepartmentName || '' | |
| 454 | + this.form.regionName = first.regionName || '' | |
| 455 | + }, | |
| 456 | + boolText(v) { | |
| 457 | + if (typeof v !== 'boolean') return '请选择' | |
| 458 | + return v ? '是' : '否' | |
| 459 | + }, | |
| 460 | + openSheet(field) { | |
| 461 | + const opts = [{ label: '是', value: true }, { label: '否', value: false }] | |
| 462 | + const cur = this.form[field] | |
| 463 | + const match = opts.find(o => o.value === cur) | |
| 464 | + const titleMap = { | |
| 465 | + longTermOperation: '是否长单操作', | |
| 466 | + terminalCustomer: '是否终端客户', | |
| 467 | + priced: '是否未点价', | |
| 468 | + otherExpenseSettlement: '其他费用凭票到厂另行结算', | |
| 469 | + commodityBuyout: '是否通货买断', | |
| 470 | + qualityBuyout: '是否质量买断', | |
| 471 | + quantityBuyout: '是否数量买断', | |
| 472 | + doorDelivery: '是否送货上门' | |
| 473 | + } | |
| 474 | + this.sheet = { visible: true, title: titleMap[field] || '请选择', options: opts, value: match ? match.value : '', field, isDict: false } | |
| 475 | + }, | |
| 476 | + openDictSheet(field, dictCode, title) { | |
| 477 | + const raw = this.getDictOptions(dictCode) || [] | |
| 478 | + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') })) | |
| 479 | + const cur = this.form[field] | |
| 480 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 481 | + this.sheet = { visible: true, title: title || '请选择', options: opts, value: match ? match.value : '', field, isDict: true } | |
| 482 | + }, | |
| 483 | + getDeliveryMethodLabel() { | |
| 484 | + const opts = this.getDictOptions('DELIVERY_METHOD') || [] | |
| 485 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.deliveryMode)) | |
| 486 | + return m ? (m.name || m.label || '') : '' | |
| 487 | + }, | |
| 488 | + onSheetConfirm({ value }) { | |
| 489 | + const field = this.sheet.field | |
| 490 | + if (!field) return | |
| 491 | + if (this.sheet.isDict) { | |
| 492 | + this.form[field] = value | |
| 493 | + } else { | |
| 494 | + this.form[field] = !!value | |
| 495 | + if (field === 'priced' && value === true) { | |
| 496 | + this.form.pricingDeadline = '' | |
| 497 | + } | |
| 498 | + } | |
| 499 | + }, | |
| 500 | + onRatioBlur() { | |
| 501 | + let v = parseFloat(this.form.settlementRatio) | |
| 502 | + if (isNaN(v)) v = '' | |
| 503 | + else v = Math.round(v * 100) / 100 | |
| 504 | + this.form.settlementRatio = v | |
| 505 | + }, | |
| 506 | + onProductsChange(products) { | |
| 507 | + this.productLineList = Array.isArray(products) ? products : [] | |
| 508 | + }, | |
| 509 | + validateRequired() { | |
| 510 | + const stringChecks = [ | |
| 511 | + { key: 'code', msg: '请输入编号' }, | |
| 512 | + { key: 'supplierId', msg: '请选择供应商' }, | |
| 513 | + { key: 'validityPeriod', msg: '请输入报价时效' }, | |
| 514 | + { key: 'deliveryMode', msg: '请选择交提货方式' }, | |
| 515 | + { key: 'invoiceProductName', msg: '请输入开票品种' }, | |
| 516 | + { key: 'deliveryDate', msg: '请选择交货日期' }, | |
| 517 | + { key: 'settlementMethod', msg: '请输入结算方式' }, | |
| 518 | + { key: 'settlementRatio', msg: '请输入结算比例' } | |
| 519 | + ] | |
| 520 | + for (const it of stringChecks) { | |
| 521 | + const val = this.form[it.key] | |
| 522 | + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) | |
| 523 | + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false } | |
| 524 | + } | |
| 525 | + const boolChecks = [ | |
| 526 | + { key: 'longTermOperation', msg: '请选择是否长单操作' }, | |
| 527 | + { key: 'terminalCustomer', msg: '请选择是否终端客户' }, | |
| 528 | + { key: 'priced', msg: '请选择是否未点价' }, | |
| 529 | + { key: 'otherExpenseSettlement', msg: '请选择其他费用凭票到厂另行结算' }, | |
| 530 | + { key: 'commodityBuyout', msg: '请选择是否通货买断' }, | |
| 531 | + { key: 'qualityBuyout', msg: '请选择是否质量买断' }, | |
| 532 | + { key: 'quantityBuyout', msg: '请选择是否数量买断' }, | |
| 533 | + { key: 'doorDelivery', msg: '请选择是否送货上门' } | |
| 534 | + ] | |
| 535 | + for (const it of boolChecks) { | |
| 536 | + if (typeof this.form[it.key] !== 'boolean') { | |
| 537 | + uni.showToast({ title: it.msg, icon: 'none' }) | |
| 538 | + return false | |
| 539 | + } | |
| 540 | + } | |
| 541 | + if (this.form.priced === false && !this.form.pricingDeadline) { | |
| 542 | + uni.showToast({ title: '请选择点价截止日期', icon: 'none' }) | |
| 543 | + return false | |
| 544 | + } | |
| 545 | + const list = Array.isArray(this.productLineList) ? this.productLineList : [] | |
| 546 | + if (list.length === 0) { | |
| 547 | + uni.showToast({ title: '请添加产品明细', icon: 'none' }) | |
| 548 | + return false | |
| 549 | + } | |
| 550 | + if (this.$refs.productRel && !this.$refs.productRel.validate(this.form.priced)) return false | |
| 551 | + return true | |
| 552 | + }, | |
| 553 | + async onSubmit() { | |
| 554 | + if (!this.validateRequired()) return | |
| 555 | + const confirmRes = await new Promise(resolve => { | |
| 556 | + uni.showModal({ title: '提示', content: '确定复制提交吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 557 | + }) | |
| 558 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 559 | + const clean = (obj) => { | |
| 560 | + const out = {} | |
| 561 | + Object.keys(obj || {}).forEach(k => { | |
| 562 | + const v = obj[k] | |
| 563 | + const isEmptyString = typeof v === 'string' && v.trim() === '' | |
| 564 | + const isUndef = v === undefined || v === null | |
| 565 | + const isNaNNumber = typeof v === 'number' && isNaN(v) | |
| 566 | + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v | |
| 567 | + }) | |
| 568 | + return out | |
| 569 | + } | |
| 570 | + const lines = (this.productLineList || []).map(it => { | |
| 571 | + const c = clean(it) | |
| 572 | + delete c.id | |
| 573 | + delete c.itemId | |
| 574 | + return c | |
| 575 | + }) | |
| 576 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 577 | + const formCopy = { ...this.form } | |
| 578 | + delete formCopy.id | |
| 579 | + materialFields.forEach(f => { | |
| 580 | + const arr = formCopy[f] | |
| 581 | + formCopy[f] = Array.isArray(arr) ? arr.join(',') : (arr || '') | |
| 582 | + }) | |
| 583 | + const payload = clean({ | |
| 584 | + ...formCopy, | |
| 585 | + quotationLineList: lines | |
| 586 | + }) | |
| 587 | + try { | |
| 588 | + await salesmanQuotationSaveApi(payload) | |
| 589 | + uni.showToast({ title: '复制成功', icon: 'none' }) | |
| 590 | + try { uni.setStorageSync('SALES_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 591 | + setTimeout(() => { uni.redirectTo({ url: '/pages/sales-price/index' }) }, 400) | |
| 592 | + } catch (e) { | |
| 593 | + uni.showToast({ title: e.msg || '复制失败', icon: 'none' }) | |
| 594 | + } | |
| 595 | + } | |
| 596 | + } | |
| 597 | +} | |
| 598 | +</script> | |
| 599 | +<style lang="scss" scoped> | |
| 600 | +.page { | |
| 601 | + display: flex; | |
| 602 | + flex-direction: column; | |
| 603 | + height: 100%; | |
| 604 | +} | |
| 605 | + | |
| 606 | +.scroll { | |
| 607 | + flex: 1; | |
| 608 | + padding: 12rpx 0 392rpx !important; | |
| 609 | +} | |
| 610 | + | |
| 611 | +.footer { | |
| 612 | + z-index: 2; | |
| 613 | + position: fixed; | |
| 614 | + left: 0; | |
| 615 | + right: 0; | |
| 616 | + bottom: 0; | |
| 617 | + padding: 32rpx; | |
| 618 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 619 | + background: #fff; | |
| 620 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 621 | + | |
| 622 | + .btn { | |
| 623 | + height: 80rpx; | |
| 624 | + line-height: 80rpx; | |
| 625 | + border-radius: 12rpx; | |
| 626 | + font-size: 32rpx; | |
| 627 | + } | |
| 628 | + | |
| 629 | + .submit { | |
| 630 | + background: $theme-primary; | |
| 631 | + color: #fff; | |
| 632 | + } | |
| 633 | +} | |
| 634 | + | |
| 635 | +::v-deep .uni-list { | |
| 636 | + .uni-easyinput { | |
| 637 | + display: flex; | |
| 638 | + | |
| 639 | + .uni-input-input { | |
| 640 | + color: rgba(0, 0, 0, 0.9); | |
| 641 | + } | |
| 642 | + } | |
| 643 | + | |
| 644 | + .uni-input-placeholder { | |
| 645 | + z-index: 1; | |
| 646 | + } | |
| 647 | + | |
| 648 | + .uni-input-input { | |
| 649 | + background-color: #ffffff; | |
| 650 | + } | |
| 651 | + | |
| 652 | + background: transparent; | |
| 653 | + | |
| 654 | + &-item { | |
| 655 | + &__extra-text { | |
| 656 | + font-size: 32rpx; | |
| 657 | + } | |
| 658 | + | |
| 659 | + &__content-title { | |
| 660 | + font-size: 32rpx; | |
| 661 | + color: rgba(0, 0, 0, 0.9); | |
| 662 | + } | |
| 663 | + | |
| 664 | + &__container { | |
| 665 | + padding: 32rpx; | |
| 666 | + | |
| 667 | + .uni-easyinput { | |
| 668 | + &__placeholder-class { | |
| 669 | + font-size: 32rpx; | |
| 670 | + color: rgba(0, 0, 0, 0.4); | |
| 671 | + } | |
| 672 | + | |
| 673 | + &__content { | |
| 674 | + border: none; | |
| 675 | + background-color: #ffffff !important; | |
| 676 | + | |
| 677 | + &-input { | |
| 678 | + padding-left: 0 !important; | |
| 679 | + height: 48rpx; | |
| 680 | + line-height: 48rpx; | |
| 681 | + font-size: 32rpx; | |
| 682 | + } | |
| 683 | + | |
| 684 | + .content-clear-icon { | |
| 685 | + font-size: 44rpx !important; | |
| 686 | + } | |
| 687 | + } | |
| 688 | + } | |
| 689 | + | |
| 690 | + .item-title, | |
| 691 | + .uni-list-item__content { | |
| 692 | + flex: none; | |
| 693 | + min-height: 48rpx; | |
| 694 | + line-height: 48rpx; | |
| 695 | + font-size: 32rpx; | |
| 696 | + position: relative; | |
| 697 | + width: 240rpx; | |
| 698 | + margin-right: 32rpx; | |
| 699 | + color: rgba(0, 0, 0, 0.9); | |
| 700 | + | |
| 701 | + .required { | |
| 702 | + color: red; | |
| 703 | + position: absolute; | |
| 704 | + top: 50%; | |
| 705 | + transform: translateY(-50%); | |
| 706 | + left: -16rpx; | |
| 707 | + } | |
| 708 | + } | |
| 709 | + } | |
| 710 | + | |
| 711 | + &.select-item { | |
| 712 | + &.is-empty { | |
| 713 | + .uni-list-item__extra-text { | |
| 714 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 715 | + } | |
| 716 | + } | |
| 717 | + | |
| 718 | + &.is-filled { | |
| 719 | + .uni-list-item__extra-text { | |
| 720 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 721 | + } | |
| 722 | + } | |
| 723 | + | |
| 724 | + .serial-number-row { | |
| 725 | + display: flex; | |
| 726 | + align-items: center; | |
| 727 | + | |
| 728 | + .generate-btn { | |
| 729 | + margin-left: 24rpx; | |
| 730 | + height: 48rpx; | |
| 731 | + line-height: 48rpx; | |
| 732 | + padding: 0 24rpx; | |
| 733 | + font-size: 24rpx; | |
| 734 | + border-radius: 8rpx; | |
| 735 | + background: $theme-primary; | |
| 736 | + color: #fff; | |
| 737 | + } | |
| 738 | + } | |
| 739 | + } | |
| 740 | + } | |
| 741 | +} | |
| 742 | + | |
| 743 | +.readonly-text { | |
| 744 | + color: rgba(0, 0, 0, 0.9); | |
| 745 | + font-size: 32rpx; | |
| 746 | + line-height: 48rpx; | |
| 747 | + text-align: right; | |
| 748 | + white-space: pre-wrap; | |
| 749 | + word-break: break-all; | |
| 750 | +} | |
| 751 | + | |
| 752 | +.value-code { | |
| 753 | + width: 260rpx; | |
| 754 | + text-align: left; | |
| 755 | +} | |
| 756 | + | |
| 757 | +.material-condition-section, | |
| 758 | +.remark-section { | |
| 759 | + margin-top: 20rpx; | |
| 760 | + background: #fff; | |
| 761 | +} | |
| 762 | + | |
| 763 | +.section-header { | |
| 764 | + display: flex; | |
| 765 | + align-items: center; | |
| 766 | + padding: 24rpx 32rpx; | |
| 767 | + border-bottom: 1rpx solid #f0f0f0; | |
| 768 | +} | |
| 769 | + | |
| 770 | +.section-title { | |
| 771 | + font-size: 32rpx; | |
| 772 | + color: rgba(0, 0, 0, 0.9); | |
| 773 | + font-weight: 600; | |
| 774 | +} | |
| 775 | + | |
| 776 | +.opCollapse { | |
| 777 | + width: 32rpx; | |
| 778 | + height: 28rpx; | |
| 779 | + margin-right: 16rpx; | |
| 780 | + margin-top: 8rpx; | |
| 781 | +} | |
| 782 | + | |
| 783 | +.section-content { | |
| 784 | + padding: 32rpx; | |
| 785 | +} | |
| 786 | + | |
| 787 | +.form-row { | |
| 788 | + display: flex; | |
| 789 | + align-items: center; | |
| 790 | + justify-content: space-between; | |
| 791 | + margin-bottom: 24rpx; | |
| 792 | +} | |
| 793 | + | |
| 794 | +.form-row:last-child { | |
| 795 | + margin-bottom: 0; | |
| 796 | +} | |
| 797 | + | |
| 798 | +.form-label { | |
| 799 | + font-size: 32rpx; | |
| 800 | + color: rgba(0, 0, 0, 0.9); | |
| 801 | +} | |
| 802 | + | |
| 803 | +.bool-btns { | |
| 804 | + display: flex; | |
| 805 | + gap: 20rpx; | |
| 806 | +} | |
| 807 | + | |
| 808 | +.bool-btn { | |
| 809 | + padding: 12rpx 40rpx; | |
| 810 | + border: 1rpx solid #ddd; | |
| 811 | + border-radius: 8rpx; | |
| 812 | + font-size: 28rpx; | |
| 813 | + color: #666; | |
| 814 | + background: #fff; | |
| 815 | +} | |
| 816 | + | |
| 817 | +.bool-btn.active { | |
| 818 | + background: $theme-primary; | |
| 819 | + border-color: $theme-primary; | |
| 820 | + color: #fff; | |
| 821 | +} | |
| 822 | + | |
| 823 | +.material-group { | |
| 824 | + margin-top: 32rpx; | |
| 825 | +} | |
| 826 | + | |
| 827 | +.material-label { | |
| 828 | + display: block; | |
| 829 | + font-size: 30rpx; | |
| 830 | + color: rgba(0, 0, 0, 0.9); | |
| 831 | + margin-bottom: 20rpx; | |
| 832 | +} | |
| 833 | + | |
| 834 | +.tag-list { | |
| 835 | + display: flex; | |
| 836 | + flex-wrap: wrap; | |
| 837 | + gap: 16rpx; | |
| 838 | +} | |
| 839 | + | |
| 840 | +.tag-btn { | |
| 841 | + padding: 12rpx 24rpx; | |
| 842 | + border: 1rpx solid #ddd; | |
| 843 | + border-radius: 8rpx; | |
| 844 | + font-size: 28rpx; | |
| 845 | + color: #666; | |
| 846 | + background: #fff; | |
| 847 | +} | |
| 848 | + | |
| 849 | +.tag-btn.active { | |
| 850 | + background: $theme-primary; | |
| 851 | + border-color: $theme-primary; | |
| 852 | + color: #fff; | |
| 853 | +} | |
| 854 | + | |
| 855 | +::v-deep .uni-easyinput { | |
| 856 | + width: 100%; | |
| 857 | + | |
| 858 | + &__content { | |
| 859 | + background: #f8f8f8 !important; | |
| 860 | + border-radius: 8rpx; | |
| 861 | + | |
| 862 | + &-input { | |
| 863 | + font-size: 30rpx; | |
| 864 | + } | |
| 865 | + } | |
| 866 | +} | |
| 867 | +</style> | ... | ... |
pages/sales-price/detail.vue
0 → 100644
| 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">{{ safeText(form.code) }}</text> | |
| 8 | + <view class="row"><text class="label">供应商名称</text><text class="value">{{ safeText(form.supplierName) }}</text></view> | |
| 9 | + <view class="row"><text class="label">采购处</text><text class="value">{{ safeText(form.purchaseDepartmentName) }}</text></view> | |
| 10 | + <view class="row"><text class="label">区域</text><text class="value">{{ safeText(form.regionName) }}</text></view> | |
| 11 | + <view class="row"><text class="label">报价时效</text><text class="value">{{ safeText(form.validityPeriod) }}</text></view> | |
| 12 | + <view class="row"><text class="label">报价人</text><text class="value">{{ safeText(form.quoterName) }}</text></view> | |
| 13 | + <view class="row"><text class="label">是否长单操作</text><text class="value">{{ boolText(form.longTermOperation) }}</text></view> | |
| 14 | + <view class="row"><text class="label">是否终端客户</text><text class="value">{{ boolText(form.terminalCustomer) }}</text></view> | |
| 15 | + <view class="row"><text class="label">是否未点价</text><text class="value">{{ boolText(form.priced) }}</text></view> | |
| 16 | + <view v-if="form.priced === false" class="row"><text class="label">点价截止日期</text><text class="value">{{ formatDateOnly(form.pricingDeadline) }}</text></view> | |
| 17 | + <view class="row"><text class="label">其他费用凭票到厂另行结算</text><text class="value">{{ boolText(form.otherExpenseSettlement) }}</text></view> | |
| 18 | + <view class="row"><text class="label">交提货方式</text><text class="value">{{ getDeliveryMethodLabel() }}</text></view> | |
| 19 | + <view class="row"><text class="label">是否通货买断</text><text class="value">{{ boolText(form.commodityBuyout) }}</text></view> | |
| 20 | + <view class="row"><text class="label">是否质量买断</text><text class="value">{{ boolText(form.qualityBuyout) }}</text></view> | |
| 21 | + <view class="row"><text class="label">是否数量买断</text><text class="value">{{ boolText(form.quantityBuyout) }}</text></view> | |
| 22 | + <view class="row"><text class="label">是否送货上门</text><text class="value">{{ boolText(form.doorDelivery) }}</text></view> | |
| 23 | + <view class="row"><text class="label">装车费</text><text class="value">{{ safeText(form.loadingFee) }}</text></view> | |
| 24 | + <view class="row"><text class="label">开票品种</text><text class="value">{{ safeText(form.invoiceProductName) }}</text></view> | |
| 25 | + <view class="row"><text class="label">交货日期</text><text class="value">{{ formatDateOnly(form.deliveryDate) }}</text></view> | |
| 26 | + <view class="row"><text class="label">结算方式</text><text class="value">{{ safeText(form.settlementMethod) }}</text></view> | |
| 27 | + <view class="row"><text class="label">结算比例(%)</text><text class="value">{{ safeText(form.settlementRatio) }}</text></view> | |
| 28 | + <view class="row"><text class="label">协调事由</text><text class="value">{{ safeText(form.coordinationReason) }}</text></view> | |
| 29 | + <view class="row"><text class="label">票类状况</text><text class="value">{{ safeText(form.invoiceTypeStatus) }}</text></view> | |
| 30 | + </view> | |
| 31 | + | |
| 32 | + <!-- 明细信息 --> | |
| 33 | + <ProductRel mode="view" :list="productLineList" /> | |
| 34 | + | |
| 35 | + <!-- 料质状况 --> | |
| 36 | + <view class="title-header"> | |
| 37 | + <image class="title-header_icon" src="/static/images/title.png" /> | |
| 38 | + <span>料质状况</span> | |
| 39 | + </view> | |
| 40 | + <view class="section"> | |
| 41 | + <view class="row"><text class="label">是否启用料质状况</text><text class="value">{{ boolText(form.materialConditionEnabled) }}</text></view> | |
| 42 | + <template v-if="form.materialConditionEnabled"> | |
| 43 | + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group"> | |
| 44 | + <text class="material-label">{{ mc.label }}</text> | |
| 45 | + <view class="tag-list"> | |
| 46 | + <view | |
| 47 | + v-for="opt in getDictOptions(mc.dictCode)" | |
| 48 | + :key="opt.code" | |
| 49 | + class="tag-btn" | |
| 50 | + :class="{ active: isTagSelected(mc.field, opt.code) }" | |
| 51 | + >{{ opt.name }}</view> | |
| 52 | + </view> | |
| 53 | + </view> | |
| 54 | + </template> | |
| 55 | + </view> | |
| 56 | + | |
| 57 | + <!-- 备注 --> | |
| 58 | + <view class="title-header"> | |
| 59 | + <image class="title-header_icon" src="/static/images/title.png" /> | |
| 60 | + <span>备注</span> | |
| 61 | + </view> | |
| 62 | + <view class="section"> | |
| 63 | + <text class="remark-text">{{ safeText(form.remark) }}</text> | |
| 64 | + </view> | |
| 65 | + </view> | |
| 66 | + </scroll-view> | |
| 67 | + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" /> | |
| 68 | + </view> | |
| 69 | +</template> | |
| 70 | +<script> | |
| 71 | +import DetailButtons from '@/components/detail-buttons/index.vue' | |
| 72 | +import ProductRel from './productRel.vue' | |
| 73 | +import { salesmanQuotationDetailApi, salesmanQuotationCancelApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 74 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 75 | +import { getInfo } from '@/api/login.js' | |
| 76 | +export default { | |
| 77 | + name: 'DetailSalesPrice', | |
| 78 | + components: { DetailButtons, ProductRel }, | |
| 79 | + data() { | |
| 80 | + return { | |
| 81 | + id: '', | |
| 82 | + form: {}, | |
| 83 | + productLineList: [], | |
| 84 | + materialConditionConfigs: [ | |
| 85 | + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' }, | |
| 86 | + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' }, | |
| 87 | + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' }, | |
| 88 | + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' }, | |
| 89 | + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' }, | |
| 90 | + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' }, | |
| 91 | + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' }, | |
| 92 | + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' } | |
| 93 | + ], | |
| 94 | + dictData: {}, | |
| 95 | + buttons: [ | |
| 96 | + { text: '编辑', visible: true, variant: 'outline', event: 'edit' }, | |
| 97 | + { text: '复制', visible: true, variant: 'primary', event: 'copy' }, | |
| 98 | + { text: '取消', visible: true, variant: 'outline', event: 'cancel' }, | |
| 99 | + { text: '生成计划', visible: true, variant: 'outline', event: 'plan' }, | |
| 100 | + { text: '审核', visible: true, variant: 'primary', event: 'approve' }, | |
| 101 | + { text: '审核详情', visible: true, event: 'approveDetail' }, | |
| 102 | + ], | |
| 103 | + investigatorId: '', | |
| 104 | + } | |
| 105 | + }, | |
| 106 | + computed: { | |
| 107 | + displayButtons() { | |
| 108 | + const s = this.form.approvalStatus || '' | |
| 109 | + const c = this.form.createById || '' | |
| 110 | + const p = this.form.status || '' | |
| 111 | + return [ | |
| 112 | + { ...this.buttons[0], visible: (s === 'REFUSE' && c === this.investigatorId && this.$auth.hasPermi('procure-manage:sales-price:modify')) }, | |
| 113 | + { ...this.buttons[1], visible: this.$auth.hasPermi('procure-manage:sales-price:copy') }, | |
| 114 | + { ...this.buttons[2], visible: (s === 'REFUSE' && c === this.investigatorId && this.$auth.hasPermi('procure-manage:sales-price:cancel')) }, | |
| 115 | + { ...this.buttons[3], visible: (p === 'DEAL_CLOSED' && this.$auth.hasPermi('procure-manage:sales-price:create')) }, | |
| 116 | + { ...this.buttons[4], visible: (s === 'AUDIT' && this.form.showExamine && this.$auth.hasPermi('procure-manage:sales-price:approves')) }, | |
| 117 | + { ...this.buttons[5], visible: (this.form.showExamineDetail && this.$auth.hasPermi('procure-manage:sales-price:view')) }, | |
| 118 | + ] | |
| 119 | + } | |
| 120 | + }, | |
| 121 | + onLoad(query) { | |
| 122 | + this.id = (query && query.id) || '' | |
| 123 | + this.loadDictData() | |
| 124 | + this.loadDetail() | |
| 125 | + // 获取当前用户信息 | |
| 126 | + this.getUserInfo(); | |
| 127 | + }, | |
| 128 | + methods: { | |
| 129 | + // 获取当前用户信息 | |
| 130 | + async getUserInfo() { | |
| 131 | + getInfo() | |
| 132 | + .then((res) => { | |
| 133 | + this.investigatorId = res?.id || ''; | |
| 134 | + }) | |
| 135 | + .catch((err) => { | |
| 136 | + console.error('获取用户信息失败:', err); | |
| 137 | + }); | |
| 138 | + }, | |
| 139 | + async loadDictData() { | |
| 140 | + try { | |
| 141 | + const codes = [...this.materialConditionConfigs.map(mc => mc.dictCode), 'DELIVERY_METHOD'] | |
| 142 | + const res = await getDicByCodes(codes) | |
| 143 | + this.dictData = res || {} | |
| 144 | + } catch (e) { | |
| 145 | + this.dictData = {} | |
| 146 | + } | |
| 147 | + }, | |
| 148 | + getDictOptions(dictCode) { | |
| 149 | + const data = this.dictData[dictCode] | |
| 150 | + return (data && data.data) ? data.data : [] | |
| 151 | + }, | |
| 152 | + isTagSelected(field, code) { | |
| 153 | + const selected = this.form[field] | |
| 154 | + return Array.isArray(selected) && selected.includes(code) | |
| 155 | + }, | |
| 156 | + boolText(v) { | |
| 157 | + if (typeof v !== 'boolean') return '-' | |
| 158 | + return v ? '是' : '否' | |
| 159 | + }, | |
| 160 | + getDeliveryMethodLabel() { | |
| 161 | + const opts = this.getDictOptions('DELIVERY_METHOD') || [] | |
| 162 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.deliveryMode)) | |
| 163 | + return m ? (m.name || m.label || '-') : '-' | |
| 164 | + }, | |
| 165 | + formatDateOnly(v) { | |
| 166 | + const s = v == null ? '' : String(v).trim() | |
| 167 | + if (!s) return '-' | |
| 168 | + if (s.length >= 10) return s.slice(0, 10) | |
| 169 | + return s | |
| 170 | + }, | |
| 171 | + safeText(v) { | |
| 172 | + const s = v == null ? '' : String(v) | |
| 173 | + return s.trim() ? s : '-' | |
| 174 | + }, | |
| 175 | + async loadDetail() { | |
| 176 | + if (!this.id) return | |
| 177 | + try { | |
| 178 | + const res = await salesmanQuotationDetailApi({ id: this.id }) | |
| 179 | + const d = (res && res.data) ? res.data : {} | |
| 180 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 181 | + materialFields.forEach(f => { | |
| 182 | + const v = d[f] | |
| 183 | + d[f] = (typeof v === 'string' && v) ? v.split(',').filter(Boolean) : (Array.isArray(v) ? v : []) | |
| 184 | + }) | |
| 185 | + this.form = d | |
| 186 | + this.productLineList = Array.isArray(d.quotationLineList) ? d.quotationLineList : (Array.isArray(d.salesmanQuotationLineList) ? d.salesmanQuotationLineList : []) | |
| 187 | + } catch (e) { | |
| 188 | + uni.showToast({ title: '加载详情失败', icon: 'none' }) | |
| 189 | + } | |
| 190 | + }, | |
| 191 | + handleButtonClick(btn) { | |
| 192 | + if (!btn || btn.disabled) return | |
| 193 | + const e = btn.event || '' | |
| 194 | + if (e === 'edit') return this.onEdit() | |
| 195 | + if (e === 'copy') return this.onCopy() | |
| 196 | + if (e === 'approve') return this.onApprove() | |
| 197 | + if (e === 'approveDetail') return this.onApproveDetail() | |
| 198 | + if (e === 'cancel') return this.onCancel() | |
| 199 | + }, | |
| 200 | + onEdit() { | |
| 201 | + uni.navigateTo({ url: '/pages/sales-price/modify?id=' + this.id }) | |
| 202 | + }, | |
| 203 | + onCopy() { | |
| 204 | + uni.navigateTo({ url: '/pages/sales-price/copy?id=' + this.id }) | |
| 205 | + }, | |
| 206 | + onCancel() { | |
| 207 | + const id = this.id || (this.form && this.form.id) || '' | |
| 208 | + if (!id) return | |
| 209 | + uni.showModal({ | |
| 210 | + title: '系统提示', | |
| 211 | + content: '是否确定取消该流程?', | |
| 212 | + confirmText: '确定', | |
| 213 | + cancelText: '取消', | |
| 214 | + success: (res) => { | |
| 215 | + if (res && res.confirm) { | |
| 216 | + salesmanQuotationCancelApi({ id }).then(() => { | |
| 217 | + uni.showToast({ title: '已取消', icon: 'none' }) | |
| 218 | + try { uni.setStorageSync('SALES_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 219 | + setTimeout(() => { uni.redirectTo({ url: '/pages/supplier-price/index' }) }, 300) | |
| 220 | + }).catch((e) => { | |
| 221 | + uni.showToast({ title: (e && e.msg) || '取消失败', icon: 'none' }) | |
| 222 | + }) | |
| 223 | + } | |
| 224 | + } | |
| 225 | + }) | |
| 226 | + }, | |
| 227 | + onApprove() { | |
| 228 | + const id = this.id || (this.form && this.form.id) || '' | |
| 229 | + if (!id) return | |
| 230 | + const CACHE_KEY = 'sourceBusinessId' | |
| 231 | + const TYPE = 'contractType' | |
| 232 | + uni.setStorageSync(TYPE, 'SALESMAN_QUOTATION') | |
| 233 | + uni.setStorageSync(CACHE_KEY, id) | |
| 234 | + uni.navigateTo({ url: '/pages/flow/audit' }) | |
| 235 | + }, | |
| 236 | + onApproveDetail() { | |
| 237 | + const id = this.id || (this.form && this.form.id) || '' | |
| 238 | + if (!id) return | |
| 239 | + const CACHE_KEY = 'sourceBusinessId' | |
| 240 | + const TYPE = 'contractType' | |
| 241 | + uni.setStorageSync(TYPE, 'SALESMAN_QUOTATION') | |
| 242 | + uni.setStorageSync(CACHE_KEY, id) | |
| 243 | + uni.navigateTo({ url: '/pages/flow/audit_detail' }) | |
| 244 | + } | |
| 245 | + } | |
| 246 | +} | |
| 247 | +</script> | |
| 248 | +<style lang="scss" scoped> | |
| 249 | +.page { | |
| 250 | + display: flex; | |
| 251 | + flex-direction: column; | |
| 252 | + height: 100%; | |
| 253 | +} | |
| 254 | + | |
| 255 | +.scroll { | |
| 256 | + flex: 1; | |
| 257 | + padding: 8rpx 0 144rpx; | |
| 258 | +} | |
| 259 | + | |
| 260 | +.detail-page { | |
| 261 | + background: #f3f3f3; | |
| 262 | +} | |
| 263 | + | |
| 264 | +.title-header { | |
| 265 | + display: flex; | |
| 266 | + align-items: center; | |
| 267 | + padding: 24rpx 32rpx 12rpx; | |
| 268 | + background-color: #ffffff; | |
| 269 | + | |
| 270 | + .title-header_icon { | |
| 271 | + width: 36rpx; | |
| 272 | + height: 36rpx; | |
| 273 | + margin-right: 12rpx; | |
| 274 | + } | |
| 275 | + | |
| 276 | + span { | |
| 277 | + font-size: 34rpx; | |
| 278 | + font-weight: 600; | |
| 279 | + color: rgba(0, 0, 0, 0.9); | |
| 280 | + } | |
| 281 | +} | |
| 282 | + | |
| 283 | +.section { | |
| 284 | + padding: 32rpx; | |
| 285 | + background: #fff; | |
| 286 | + margin-bottom: 20rpx; | |
| 287 | +} | |
| 288 | + | |
| 289 | +.row { | |
| 290 | + display: flex; | |
| 291 | + margin-bottom: 28rpx; | |
| 292 | + | |
| 293 | + &:last-child { | |
| 294 | + margin-bottom: 0; | |
| 295 | + } | |
| 296 | + | |
| 297 | + &.company { | |
| 298 | + font-size: 36rpx; | |
| 299 | + font-weight: 600; | |
| 300 | + color: rgba(0, 0, 0, 0.9); | |
| 301 | + padding-top: 8rpx; | |
| 302 | + line-height: 50rpx; | |
| 303 | + } | |
| 304 | + | |
| 305 | + .label { | |
| 306 | + max-width: 420rpx; | |
| 307 | + margin-right: 20rpx; | |
| 308 | + line-height: 32rpx; | |
| 309 | + font-size: 28rpx; | |
| 310 | + color: rgba(0, 0, 0, 0.6); | |
| 311 | + } | |
| 312 | + | |
| 313 | + .value { | |
| 314 | + flex: 1; | |
| 315 | + line-height: 32rpx; | |
| 316 | + font-size: 28rpx; | |
| 317 | + color: rgba(0, 0, 0, 0.9); | |
| 318 | + text-align: right; | |
| 319 | + word-break: break-all; | |
| 320 | + } | |
| 321 | +} | |
| 322 | + | |
| 323 | +.line-block { | |
| 324 | + padding-bottom: 24rpx; | |
| 325 | + margin-bottom: 24rpx; | |
| 326 | + border-bottom: 1rpx solid #f0f0f0; | |
| 327 | + | |
| 328 | + &:last-child { | |
| 329 | + border-bottom: none; | |
| 330 | + margin-bottom: 0; | |
| 331 | + padding-bottom: 0; | |
| 332 | + } | |
| 333 | +} | |
| 334 | + | |
| 335 | +.material-group { | |
| 336 | + margin-top: 32rpx; | |
| 337 | + | |
| 338 | + &:first-child { | |
| 339 | + margin-top: 0; | |
| 340 | + } | |
| 341 | +} | |
| 342 | + | |
| 343 | +.material-label { | |
| 344 | + display: block; | |
| 345 | + font-size: 28rpx; | |
| 346 | + color: rgba(0, 0, 0, 0.6); | |
| 347 | + margin-bottom: 20rpx; | |
| 348 | +} | |
| 349 | + | |
| 350 | +.tag-list { | |
| 351 | + display: flex; | |
| 352 | + flex-wrap: wrap; | |
| 353 | + gap: 16rpx; | |
| 354 | +} | |
| 355 | + | |
| 356 | +.tag-btn { | |
| 357 | + padding: 12rpx 24rpx; | |
| 358 | + border: 1rpx solid #ddd; | |
| 359 | + border-radius: 8rpx; | |
| 360 | + font-size: 28rpx; | |
| 361 | + color: #999; | |
| 362 | + background: #f5f5f5; | |
| 363 | +} | |
| 364 | + | |
| 365 | +.tag-btn.active { | |
| 366 | + background: $theme-primary; | |
| 367 | + border-color: $theme-primary; | |
| 368 | + color: #fff; | |
| 369 | +} | |
| 370 | + | |
| 371 | +.remark-text { | |
| 372 | + font-size: 28rpx; | |
| 373 | + color: rgba(0, 0, 0, 0.9); | |
| 374 | + line-height: 1.6; | |
| 375 | + white-space: pre-wrap; | |
| 376 | +} | |
| 377 | + | |
| 378 | +.empty-tip { | |
| 379 | + text-align: center; | |
| 380 | + font-size: 28rpx; | |
| 381 | + color: rgba(0, 0, 0, 0.4); | |
| 382 | + padding: 24rpx 0; | |
| 383 | +} | |
| 384 | +</style> | ... | ... |
pages/sales-price/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <view class="dev-list-fixed"> | |
| 4 | + <view class="search-row"> | |
| 5 | + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入供应商名称" clearButton="auto" | |
| 6 | + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search" | |
| 7 | + @input="onSearchInput" /> | |
| 8 | + <view class="tool-icons"> | |
| 9 | + <image v-if="$auth.hasPermi('procure-manage:sales-price:add')" class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" /> | |
| 10 | + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" /> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + </view> | |
| 14 | + | |
| 15 | + <view class="list-box"> | |
| 16 | + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id" | |
| 17 | + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError"> | |
| 18 | + <template v-slot="{ item }"> | |
| 19 | + <view class="card" @click.stop="onCardClick(item)"> | |
| 20 | + <view class="card-header"> | |
| 21 | + <text class="title omit2">{{ item.supplierName || '-' }}</text> | |
| 22 | + <text :class="['status', `status_${item.approvalStatus}`]">{{ getAuditStatusText(item.approvalStatus) }}</text> | |
| 23 | + </view> | |
| 24 | + <view class="info-row"> | |
| 25 | + <text>编号</text><text>{{ item.code || '-' }}</text> | |
| 26 | + </view> | |
| 27 | + <view class="info-row"> | |
| 28 | + <text>采购处</text><text>{{ item.purchaseDepartmentName || '-' }}</text> | |
| 29 | + </view> | |
| 30 | + <view class="info-row"> | |
| 31 | + <text>区域</text><text>{{ item.regionName || '-' }}</text> | |
| 32 | + </view> | |
| 33 | + <view class="info-row"> | |
| 34 | + <text>日期</text><text>{{ formatDateOnly(item.quoteTime) }}</text> | |
| 35 | + </view> | |
| 36 | + </view> | |
| 37 | + </template> | |
| 38 | + </card-list> | |
| 39 | + </view> | |
| 40 | + | |
| 41 | + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset" | |
| 42 | + @confirm="onFilterConfirm"> | |
| 43 | + <template v-slot="{ model }"> | |
| 44 | + <view class="filter-form"> | |
| 45 | + <view class="form-item"> | |
| 46 | + <view class="label">编号</view> | |
| 47 | + <uni-easyinput v-model="model.code" placeholder="请输入编号" :inputBorder="false" | |
| 48 | + placeholderStyle="font-size:14px" @input="onCodeInput" /> | |
| 49 | + </view> | |
| 50 | + <view class="form-item"> | |
| 51 | + <view class="label">报价时间</view> | |
| 52 | + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" /> | |
| 53 | + </view> | |
| 54 | + </view> | |
| 55 | + </template> | |
| 56 | + </filter-modal> | |
| 57 | + </view> | |
| 58 | +</template> | |
| 59 | + | |
| 60 | +<script> | |
| 61 | +import CardList from '@/components/card/index.vue' | |
| 62 | +import FilterModal from '@/components/filter/index.vue' | |
| 63 | +import { getDicByCodes, getDicName } from '@/utils/dic.js' | |
| 64 | +import { salesmanQuotationQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 65 | + | |
| 66 | +export default { | |
| 67 | + name: 'SalesPriceList', | |
| 68 | + components: { CardList, FilterModal }, | |
| 69 | + data() { | |
| 70 | + return { | |
| 71 | + searchKeyword: '', | |
| 72 | + searchKeywordDebounced: '', | |
| 73 | + searchDebounceTimer: null, | |
| 74 | + query: { | |
| 75 | + supplierName: '', | |
| 76 | + code: '', | |
| 77 | + dateRange: [] | |
| 78 | + }, | |
| 79 | + extraParams: {}, | |
| 80 | + currentItems: [], | |
| 81 | + filterVisible: false, | |
| 82 | + filterForm: { | |
| 83 | + code: '', | |
| 84 | + dateRange: [] | |
| 85 | + }, | |
| 86 | + dicOptions: { | |
| 87 | + AUDIT_STATUS: [] | |
| 88 | + }, | |
| 89 | + } | |
| 90 | + }, | |
| 91 | + watch: { | |
| 92 | + searchKeywordDebounced(v) { | |
| 93 | + this.query = { ...this.query, supplierName: v || '' } | |
| 94 | + } | |
| 95 | + }, | |
| 96 | + created() { | |
| 97 | + this.loadDicData() | |
| 98 | + }, | |
| 99 | + onShow() { | |
| 100 | + let needRefresh = '' | |
| 101 | + try { needRefresh = uni.getStorageSync('SALES_PRICE_LIST_NEED_REFRESH') } catch (e) {} | |
| 102 | + if (!needRefresh) return | |
| 103 | + try { uni.removeStorageSync('SALES_PRICE_LIST_NEED_REFRESH') } catch (e) {} | |
| 104 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) { | |
| 105 | + this.$refs.cardRef.reload() | |
| 106 | + } | |
| 107 | + }, | |
| 108 | + onReachBottom() { | |
| 109 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) { | |
| 110 | + this.$refs.cardRef.onLoadMore() | |
| 111 | + } | |
| 112 | + }, | |
| 113 | + beforeDestroy() { | |
| 114 | + if (this.searchDebounceTimer) { | |
| 115 | + clearTimeout(this.searchDebounceTimer) | |
| 116 | + this.searchDebounceTimer = null | |
| 117 | + } | |
| 118 | + }, | |
| 119 | + methods: { | |
| 120 | + onAdd() { | |
| 121 | + uni.navigateTo({ url: '/pages/sales-price/add' }) | |
| 122 | + }, | |
| 123 | + async loadDicData() { | |
| 124 | + try { | |
| 125 | + const results = await getDicByCodes(['AUDIT_STATUS']) | |
| 126 | + this.dicOptions.AUDIT_STATUS = (results.AUDIT_STATUS && results.AUDIT_STATUS.data) || [] | |
| 127 | + } catch (e) { | |
| 128 | + this.dicOptions.AUDIT_STATUS = [] | |
| 129 | + } | |
| 130 | + }, | |
| 131 | + formatDateOnly(v) { | |
| 132 | + const s = v == null ? '' : String(v).trim() | |
| 133 | + if (!s) return '-' | |
| 134 | + if (s.length >= 10) return s.slice(0, 10) | |
| 135 | + return s | |
| 136 | + }, | |
| 137 | + onCardLoaded({ items }) { | |
| 138 | + this.currentItems = items | |
| 139 | + }, | |
| 140 | + onCardError() { | |
| 141 | + uni.showToast({ title: '列表加载失败', icon: 'none' }) | |
| 142 | + }, | |
| 143 | + onSearchInput() { | |
| 144 | + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer) | |
| 145 | + this.searchDebounceTimer = setTimeout(() => { | |
| 146 | + this.searchKeywordDebounced = this.searchKeyword | |
| 147 | + this.searchDebounceTimer = null | |
| 148 | + }, 1200) | |
| 149 | + }, | |
| 150 | + search(e) { | |
| 151 | + const val = e && e.value != null ? e.value : this.searchKeyword | |
| 152 | + this.searchKeyword = val | |
| 153 | + this.searchKeywordDebounced = val | |
| 154 | + }, | |
| 155 | + openFilter() { | |
| 156 | + this.filterForm = { | |
| 157 | + code: this.query.code || '', | |
| 158 | + dateRange: Array.isArray(this.query.dateRange) ? this.query.dateRange.slice(0) : [] | |
| 159 | + } | |
| 160 | + this.filterVisible = true | |
| 161 | + }, | |
| 162 | + onFilterReset(payload) { | |
| 163 | + this.filterForm = payload | |
| 164 | + }, | |
| 165 | + onFilterConfirm(payload) { | |
| 166 | + this.query = { | |
| 167 | + supplierName: this.query.supplierName || '', | |
| 168 | + code: payload.code || '', | |
| 169 | + dateRange: Array.isArray(payload.dateRange) ? payload.dateRange : [] | |
| 170 | + } | |
| 171 | + }, | |
| 172 | + onCodeInput(val) { | |
| 173 | + this.filterForm.code = val | |
| 174 | + }, | |
| 175 | + getAuditStatusText(status) { | |
| 176 | + const v = status == null ? '' : String(status) | |
| 177 | + if (!v) return '-' | |
| 178 | + return getDicName('AUDIT_STATUS', v, this.dicOptions.AUDIT_STATUS) || v | |
| 179 | + }, | |
| 180 | + fetchList({ pageIndex, pageSize, query }) { | |
| 181 | + const q = query || {} | |
| 182 | + const range = Array.isArray(q.dateRange) ? q.dateRange : [] | |
| 183 | + const params = { | |
| 184 | + pageIndex, | |
| 185 | + pageSize, | |
| 186 | + supplierName: q.supplierName || '', | |
| 187 | + code: q.code || '', | |
| 188 | + quoteDateStart: (range && range.length === 2) ? range[0] : '', | |
| 189 | + quoteDateEnd: (range && range.length === 2) ? range[1] : '' | |
| 190 | + } | |
| 191 | + return salesmanQuotationQueryApi(params).then(res => { | |
| 192 | + const _data = res && res.data ? res.data : {} | |
| 193 | + let records = _data.datas || _data.list || _data.records || [] | |
| 194 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 195 | + const hasNext = _data.hasNext || false | |
| 196 | + records = records.map(it => ({ ...it })) | |
| 197 | + return { records, totalCount, hasNext } | |
| 198 | + }).catch(() => { | |
| 199 | + this.onCardError() | |
| 200 | + return { records: [], totalCount: 0, hasNext: false } | |
| 201 | + }) | |
| 202 | + }, | |
| 203 | + onCardClick(item) { | |
| 204 | + uni.navigateTo({ url: '/pages/sales-price/detail?id=' + item.id }) | |
| 205 | + }, | |
| 206 | + } | |
| 207 | +} | |
| 208 | +</script> | |
| 209 | + | |
| 210 | +<style lang="scss" scoped> | |
| 211 | +.page { | |
| 212 | + display: flex; | |
| 213 | + flex-direction: column; | |
| 214 | + height: 100vh; | |
| 215 | +} | |
| 216 | + | |
| 217 | +.dev-list-fixed { | |
| 218 | + position: fixed; | |
| 219 | + top: 96rpx; | |
| 220 | + left: 0; | |
| 221 | + right: 0; | |
| 222 | + z-index: 2; | |
| 223 | + background: #fff; | |
| 224 | + | |
| 225 | + .search-row { | |
| 226 | + display: flex; | |
| 227 | + align-items: center; | |
| 228 | + padding: 16rpx 32rpx; | |
| 229 | + | |
| 230 | + .uni-searchbar { | |
| 231 | + padding: 0; | |
| 232 | + flex: 1; | |
| 233 | + } | |
| 234 | + | |
| 235 | + .tool-icons { | |
| 236 | + display: flex; | |
| 237 | + | |
| 238 | + .tool-icon { | |
| 239 | + width: 48rpx; | |
| 240 | + height: 48rpx; | |
| 241 | + display: block; | |
| 242 | + margin-left: 32rpx; | |
| 243 | + } | |
| 244 | + } | |
| 245 | + } | |
| 246 | +} | |
| 247 | + | |
| 248 | +::v-deep .uni-searchbar__box { | |
| 249 | + height: 80rpx !important; | |
| 250 | + justify-content: start; | |
| 251 | + | |
| 252 | + .uni-searchbar__box-search-input { | |
| 253 | + font-size: 32rpx !important; | |
| 254 | + } | |
| 255 | +} | |
| 256 | + | |
| 257 | +.list-box { | |
| 258 | + flex: 1; | |
| 259 | + padding-top: 140rpx; | |
| 260 | + | |
| 261 | + .card { | |
| 262 | + position: relative; | |
| 263 | + } | |
| 264 | + | |
| 265 | + .card-header { | |
| 266 | + margin-bottom: 28rpx; | |
| 267 | + position: relative; | |
| 268 | + | |
| 269 | + .title { | |
| 270 | + font-size: 36rpx; | |
| 271 | + font-weight: 600; | |
| 272 | + line-height: 50rpx; | |
| 273 | + color: rgba(0, 0, 0, 0.9); | |
| 274 | + width: 578rpx; | |
| 275 | + } | |
| 276 | + | |
| 277 | + .status { | |
| 278 | + position: absolute; | |
| 279 | + top: -32rpx; | |
| 280 | + right: -12rpx; | |
| 281 | + height: 48rpx; | |
| 282 | + line-height: 48rpx; | |
| 283 | + font-weight: 600; | |
| 284 | + color: #fff; | |
| 285 | + font-size: 24rpx; | |
| 286 | + padding: 0 14rpx; | |
| 287 | + border-radius: 6rpx; | |
| 288 | + | |
| 289 | + &.status_AUDIT { | |
| 290 | + background: $theme-primary; | |
| 291 | + } | |
| 292 | + | |
| 293 | + &.status_PASS { | |
| 294 | + background: #2BA471; | |
| 295 | + } | |
| 296 | + | |
| 297 | + &.status_REFUSE { | |
| 298 | + background: #D54941; | |
| 299 | + } | |
| 300 | + | |
| 301 | + &.status_CANCEL { | |
| 302 | + background: #E7E7E7; | |
| 303 | + color: rgba(0, 0, 0, 0.9); | |
| 304 | + } | |
| 305 | + } | |
| 306 | + } | |
| 307 | + | |
| 308 | + .info-row { | |
| 309 | + display: flex; | |
| 310 | + align-items: center; | |
| 311 | + color: rgba(0, 0, 0, 0.6); | |
| 312 | + font-size: 28rpx; | |
| 313 | + margin-bottom: 24rpx; | |
| 314 | + height: 32rpx; | |
| 315 | + | |
| 316 | + &:last-child { | |
| 317 | + margin-bottom: 0; | |
| 318 | + } | |
| 319 | + | |
| 320 | + text { | |
| 321 | + width: 50%; | |
| 322 | + | |
| 323 | + &:last-child { | |
| 324 | + color: rgba(0, 0, 0, 0.9); | |
| 325 | + width: 50%; | |
| 326 | + } | |
| 327 | + } | |
| 328 | + } | |
| 329 | +} | |
| 330 | + | |
| 331 | +.filter-form { | |
| 332 | + .form-item { | |
| 333 | + margin-bottom: 24rpx; | |
| 334 | + } | |
| 335 | + | |
| 336 | + .label { | |
| 337 | + margin-bottom: 20rpx; | |
| 338 | + color: rgba(0, 0, 0, 0.9); | |
| 339 | + height: 44rpx; | |
| 340 | + line-height: 44rpx; | |
| 341 | + font-size: 30rpx; | |
| 342 | + } | |
| 343 | + | |
| 344 | + .uni-easyinput { | |
| 345 | + border: 1rpx solid #f3f3f3; | |
| 346 | + } | |
| 347 | +} | |
| 348 | +</style> | ... | ... |
pages/sales-price/modify.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <!-- 编号 --> | |
| 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 | + <view class="serial-number-row"> | |
| 12 | + <text class="readonly-text value-code">{{ form.code }}</text> | |
| 13 | + </view> | |
| 14 | + </template> | |
| 15 | + </uni-list-item> | |
| 16 | + | |
| 17 | + <!-- 供应商名称 --> | |
| 18 | + <uni-list-item class="select-item" :class="form.supplierId ? 'is-filled' : 'is-empty'" clickable | |
| 19 | + @click="openSupplierSheet" :rightText="form.supplierName || '请选择供应商名称'" showArrow> | |
| 20 | + <template v-slot:body> | |
| 21 | + <view class="item-title"><text class="required">*</text><text>供应商名称</text></view> | |
| 22 | + </template> | |
| 23 | + </uni-list-item> | |
| 24 | + | |
| 25 | + <!-- 采购处(带出,禁用) --> | |
| 26 | + <uni-list-item title="采购处"> | |
| 27 | + <template v-slot:footer> | |
| 28 | + <uni-easyinput v-model="form.purchaseDepartmentName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 29 | + </template> | |
| 30 | + </uni-list-item> | |
| 31 | + | |
| 32 | + <!-- 区域(带出,禁用) --> | |
| 33 | + <uni-list-item title="区域"> | |
| 34 | + <template v-slot:footer> | |
| 35 | + <uni-easyinput v-model="form.regionName" :inputBorder="false" disabled placeholder="选择供应商后自动带出" /> | |
| 36 | + </template> | |
| 37 | + </uni-list-item> | |
| 38 | + | |
| 39 | + <!-- 报价时效 --> | |
| 40 | + <uni-list-item> | |
| 41 | + <template v-slot:body> | |
| 42 | + <view class="item-title"><text class="required">*</text><text>报价时效</text></view> | |
| 43 | + </template> | |
| 44 | + <template v-slot:footer> | |
| 45 | + <uni-easyinput v-model="form.validityPeriod" :inputBorder="false" placeholder="请输入报价时效" /> | |
| 46 | + </template> | |
| 47 | + </uni-list-item> | |
| 48 | + | |
| 49 | + <!-- 报价人(禁用) --> | |
| 50 | + <uni-list-item title="报价人"> | |
| 51 | + <template v-slot:footer> | |
| 52 | + <uni-easyinput v-model="form.quoterName" :inputBorder="false" disabled placeholder="自动获取" /> | |
| 53 | + </template> | |
| 54 | + </uni-list-item> | |
| 55 | + | |
| 56 | + <!-- 是否长单操作 --> | |
| 57 | + <uni-list-item class="select-item" :class="typeof form.longTermOperation === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 58 | + @click="openSheet('longTermOperation')" :rightText="boolText(form.longTermOperation)" showArrow> | |
| 59 | + <template v-slot:body> | |
| 60 | + <view class="item-title"><text class="required">*</text><text>是否长单操作</text></view> | |
| 61 | + </template> | |
| 62 | + </uni-list-item> | |
| 63 | + | |
| 64 | + <!-- 是否终端客户 --> | |
| 65 | + <uni-list-item class="select-item" :class="typeof form.terminalCustomer === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 66 | + @click="openSheet('terminalCustomer')" :rightText="boolText(form.terminalCustomer)" showArrow> | |
| 67 | + <template v-slot:body> | |
| 68 | + <view class="item-title"><text class="required">*</text><text>是否终端客户</text></view> | |
| 69 | + </template> | |
| 70 | + </uni-list-item> | |
| 71 | + | |
| 72 | + <!-- 是否未点价 --> | |
| 73 | + <uni-list-item class="select-item" :class="typeof form.priced === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 74 | + @click="openSheet('priced')" :rightText="boolText(form.priced)" showArrow> | |
| 75 | + <template v-slot:body> | |
| 76 | + <view class="item-title"><text class="required">*</text><text>是否未点价</text></view> | |
| 77 | + </template> | |
| 78 | + </uni-list-item> | |
| 79 | + | |
| 80 | + <!-- 点价截止日期 - 仅在 是否未点价 = 否 时显示且必填 --> | |
| 81 | + <uni-list-item v-if="form.priced === false" class="select-item" :class="form.pricingDeadline ? 'is-filled' : 'is-empty'"> | |
| 82 | + <template v-slot:body> | |
| 83 | + <view class="item-title"><text class="required">*</text><text>点价截止日期</text></view> | |
| 84 | + </template> | |
| 85 | + <template v-slot:footer> | |
| 86 | + <uni-datetime-picker type="date" v-model="form.pricingDeadline" /> | |
| 87 | + </template> | |
| 88 | + </uni-list-item> | |
| 89 | + | |
| 90 | + <!-- 其他费用凭票到厂另行结算 --> | |
| 91 | + <uni-list-item class="select-item" :class="typeof form.otherExpenseSettlement === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 92 | + @click="openSheet('otherExpenseSettlement')" :rightText="boolText(form.otherExpenseSettlement)" showArrow> | |
| 93 | + <template v-slot:body> | |
| 94 | + <view class="item-title"><text class="required">*</text><text>其他费用凭票到厂另行结算</text></view> | |
| 95 | + </template> | |
| 96 | + </uni-list-item> | |
| 97 | + | |
| 98 | + <!-- 交提货方式 --> | |
| 99 | + <uni-list-item class="select-item" :class="form.deliveryMode ? 'is-filled' : 'is-empty'" clickable | |
| 100 | + @click="openDictSheet('deliveryMode', 'DELIVERY_METHOD', '交提货方式')" :rightText="getDeliveryMethodLabel() || '请选择交提货方式'" showArrow> | |
| 101 | + <template v-slot:body> | |
| 102 | + <view class="item-title"><text class="required">*</text><text>交提货方式</text></view> | |
| 103 | + </template> | |
| 104 | + </uni-list-item> | |
| 105 | + | |
| 106 | + <!-- 是否通货买断 --> | |
| 107 | + <uni-list-item class="select-item" :class="typeof form.commodityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 108 | + @click="openSheet('commodityBuyout')" :rightText="boolText(form.commodityBuyout)" showArrow> | |
| 109 | + <template v-slot:body> | |
| 110 | + <view class="item-title"><text class="required">*</text><text>是否通货买断</text></view> | |
| 111 | + </template> | |
| 112 | + </uni-list-item> | |
| 113 | + | |
| 114 | + <!-- 是否质量买断 --> | |
| 115 | + <uni-list-item class="select-item" :class="typeof form.qualityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 116 | + @click="openSheet('qualityBuyout')" :rightText="boolText(form.qualityBuyout)" showArrow> | |
| 117 | + <template v-slot:body> | |
| 118 | + <view class="item-title"><text class="required">*</text><text>是否质量买断</text></view> | |
| 119 | + </template> | |
| 120 | + </uni-list-item> | |
| 121 | + | |
| 122 | + <!-- 是否数量买断 --> | |
| 123 | + <uni-list-item class="select-item" :class="typeof form.quantityBuyout === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 124 | + @click="openSheet('quantityBuyout')" :rightText="boolText(form.quantityBuyout)" showArrow> | |
| 125 | + <template v-slot:body> | |
| 126 | + <view class="item-title"><text class="required">*</text><text>是否数量买断</text></view> | |
| 127 | + </template> | |
| 128 | + </uni-list-item> | |
| 129 | + | |
| 130 | + <!-- 是否送货上门 --> | |
| 131 | + <uni-list-item class="select-item" :class="typeof form.doorDelivery === 'boolean' ? 'is-filled' : 'is-empty'" clickable | |
| 132 | + @click="openSheet('doorDelivery')" :rightText="boolText(form.doorDelivery)" showArrow> | |
| 133 | + <template v-slot:body> | |
| 134 | + <view class="item-title"><text class="required">*</text><text>是否送货上门</text></view> | |
| 135 | + </template> | |
| 136 | + </uni-list-item> | |
| 137 | + | |
| 138 | + <!-- 装车费 --> | |
| 139 | + <uni-list-item title="装车费"> | |
| 140 | + <template v-slot:footer> | |
| 141 | + <uni-easyinput v-model="form.loadingFee" type="digit" :inputBorder="false" placeholder="请输入装车费" /> | |
| 142 | + </template> | |
| 143 | + </uni-list-item> | |
| 144 | + | |
| 145 | + <!-- 开票品种 --> | |
| 146 | + <uni-list-item> | |
| 147 | + <template v-slot:body> | |
| 148 | + <view class="item-title"><text class="required">*</text><text>开票品种</text></view> | |
| 149 | + </template> | |
| 150 | + <template v-slot:footer> | |
| 151 | + <uni-easyinput v-model="form.invoiceProductName" :inputBorder="false" placeholder="请输入开票品种" /> | |
| 152 | + </template> | |
| 153 | + </uni-list-item> | |
| 154 | + | |
| 155 | + <!-- 交货日期 --> | |
| 156 | + <uni-list-item> | |
| 157 | + <template v-slot:body> | |
| 158 | + <view class="item-title"><text class="required">*</text><text>交货日期</text></view> | |
| 159 | + </template> | |
| 160 | + <template v-slot:footer> | |
| 161 | + <uni-datetime-picker type="date" v-model="form.deliveryDate" /> | |
| 162 | + </template> | |
| 163 | + </uni-list-item> | |
| 164 | + | |
| 165 | + <!-- 结算方式 --> | |
| 166 | + <uni-list-item> | |
| 167 | + <template v-slot:body> | |
| 168 | + <view class="item-title"><text class="required">*</text><text>结算方式</text></view> | |
| 169 | + </template> | |
| 170 | + <template v-slot:footer> | |
| 171 | + <uni-easyinput v-model="form.settlementMethod" :inputBorder="false" placeholder="请输入结算方式" /> | |
| 172 | + </template> | |
| 173 | + </uni-list-item> | |
| 174 | + | |
| 175 | + <!-- 结算比例(%) --> | |
| 176 | + <uni-list-item> | |
| 177 | + <template v-slot:body> | |
| 178 | + <view class="item-title"><text class="required">*</text><text>结算比例(%)</text></view> | |
| 179 | + </template> | |
| 180 | + <template v-slot:footer> | |
| 181 | + <uni-easyinput v-model="form.settlementRatio" type="digit" :inputBorder="false" placeholder="请输入结算比例" @blur="onRatioBlur" /> | |
| 182 | + </template> | |
| 183 | + </uni-list-item> | |
| 184 | + | |
| 185 | + <!-- 协调事由 --> | |
| 186 | + <uni-list-item title="协调事由"> | |
| 187 | + <template v-slot:footer> | |
| 188 | + <uni-easyinput v-model="form.coordinationReason" :inputBorder="false" placeholder="请输入协调事由" disabled /> | |
| 189 | + </template> | |
| 190 | + </uni-list-item> | |
| 191 | + | |
| 192 | + <!-- 票类状况 --> | |
| 193 | + <uni-list-item title="票类状况"> | |
| 194 | + <template v-slot:footer> | |
| 195 | + <uni-easyinput v-model="form.invoiceTypeStatus" :inputBorder="false" placeholder="请输入票类状况" disabled /> | |
| 196 | + </template> | |
| 197 | + </uni-list-item> | |
| 198 | + | |
| 199 | + <!-- 明细信息 --> | |
| 200 | + <ProductRel ref="productRel" mode="edit" :options="productList" :priced="form.priced" :list="productLineList" @change="onProductsChange" /> | |
| 201 | + </uni-list> | |
| 202 | + | |
| 203 | + <!-- 料质状况模块(明细下方) --> | |
| 204 | + <view class="material-condition-section"> | |
| 205 | + <view class="section-header"> | |
| 206 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 207 | + <text class="section-title">料质状况</text> | |
| 208 | + </view> | |
| 209 | + <view class="section-content"> | |
| 210 | + <view class="form-row"> | |
| 211 | + <text class="form-label">是否启用料质状况</text> | |
| 212 | + <view class="bool-btns"> | |
| 213 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === true }" @click="form.materialConditionEnabled = true">是</view> | |
| 214 | + <view class="bool-btn" :class="{ active: form.materialConditionEnabled === false }" @click="form.materialConditionEnabled = false">否</view> | |
| 215 | + </view> | |
| 216 | + </view> | |
| 217 | + | |
| 218 | + <template v-if="form.materialConditionEnabled"> | |
| 219 | + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group"> | |
| 220 | + <text class="material-label">{{ mc.label }}</text> | |
| 221 | + <view class="tag-list"> | |
| 222 | + <view | |
| 223 | + v-for="opt in getDictOptions(mc.dictCode)" | |
| 224 | + :key="opt.code" | |
| 225 | + class="tag-btn" | |
| 226 | + :class="{ active: isTagSelected(mc.field, opt.code) }" | |
| 227 | + @click="toggleTag(mc.field, opt.code)" | |
| 228 | + > | |
| 229 | + {{ opt.name }} | |
| 230 | + </view> | |
| 231 | + </view> | |
| 232 | + </view> | |
| 233 | + </template> | |
| 234 | + </view> | |
| 235 | + </view> | |
| 236 | + | |
| 237 | + <!-- 备注模块 --> | |
| 238 | + <view class="remark-section"> | |
| 239 | + <view class="section-header"> | |
| 240 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 241 | + <text class="section-title">备注</text> | |
| 242 | + </view> | |
| 243 | + <view class="section-content"> | |
| 244 | + <uni-easyinput v-model="form.remark" type="textarea" :inputBorder="false" placeholder="请输入备注" maxlength="2000" /> | |
| 245 | + </view> | |
| 246 | + </view> | |
| 247 | + | |
| 248 | + <view class="footer"> | |
| 249 | + <button class="btn submit" type="primary" @click="onSubmit">保存</button> | |
| 250 | + </view> | |
| 251 | + </scroll-view> | |
| 252 | + | |
| 253 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" | |
| 254 | + v-model="sheet.value" @confirm="onSheetConfirm" /> | |
| 255 | + | |
| 256 | + <RelateSelectSheet :visible.sync="supplierSheet.visible" :title="supplierSheet.title" | |
| 257 | + :fetchFn="supplierSheet.fetchFn" :display-fields="supplierSheet.displayFields" | |
| 258 | + :multiple="false" row-key="id" :selectedKeys.sync="supplierSheet.selectedKeys" | |
| 259 | + @confirm="onSupplierConfirm" /> | |
| 260 | + </view> | |
| 261 | +</template> | |
| 262 | +<script> | |
| 263 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 264 | +import RelateSelectSheet from '@/components/relate-select/index.vue' | |
| 265 | +import ProductRel from './productRel.vue' | |
| 266 | +import { salesmanQuotationDetailApi, salesmanQuotationUpdateApi, breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 267 | +import { domesticCustomerCreditQueryApi } from '@/api/procure-manage/domesticCustomerCredit.js' | |
| 268 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 269 | + | |
| 270 | +export default { | |
| 271 | + name: 'ModifySalesPrice', | |
| 272 | + components: { SingleSelectSheet, RelateSelectSheet, ProductRel }, | |
| 273 | + data() { | |
| 274 | + return { | |
| 275 | + id: '', | |
| 276 | + form: { | |
| 277 | + code: '', | |
| 278 | + supplierId: '', | |
| 279 | + supplierName: '', | |
| 280 | + purchaseDepartmentName: '', | |
| 281 | + regionName: '', | |
| 282 | + validityPeriod: '', | |
| 283 | + quoterName: '', | |
| 284 | + longTermOperation: false, | |
| 285 | + terminalCustomer: false, | |
| 286 | + priced: true, | |
| 287 | + pricingDeadline: '', | |
| 288 | + otherExpenseSettlement: false, | |
| 289 | + deliveryMode: '', | |
| 290 | + commodityBuyout: false, | |
| 291 | + qualityBuyout: false, | |
| 292 | + quantityBuyout: false, | |
| 293 | + doorDelivery: false, | |
| 294 | + loadingFee: '', | |
| 295 | + invoiceProductName: '', | |
| 296 | + deliveryDate: '', | |
| 297 | + settlementMethod: '', | |
| 298 | + settlementRatio: '', | |
| 299 | + coordinationReason: '', | |
| 300 | + invoiceTypeStatus: '', | |
| 301 | + materialConditionEnabled: false, | |
| 302 | + materialConditionCopperRice: [], | |
| 303 | + materialConditionBrightCopper: [], | |
| 304 | + materialConditionFirstClass: [], | |
| 305 | + materialConditionSecondClass: [], | |
| 306 | + materialConditionTinCoated: [], | |
| 307 | + materialConditionScrap: [], | |
| 308 | + materialConditionWhiteYellow: [], | |
| 309 | + materialConditionPhosphorCopper: [], | |
| 310 | + remark: '' | |
| 311 | + }, | |
| 312 | + productList: [], | |
| 313 | + productLineList: [], | |
| 314 | + sheet: { visible: false, title: '请选择', options: [], value: '', field: '' }, | |
| 315 | + supplierSheet: { | |
| 316 | + visible: false, | |
| 317 | + title: '选择供应商', | |
| 318 | + fetchFn: null, | |
| 319 | + displayFields: [ | |
| 320 | + { label: '单位名称', field: 'unitName' }, | |
| 321 | + { label: '采购处', field: 'purchaseDepartmentName' }, | |
| 322 | + { label: '区域', field: 'regionName' } | |
| 323 | + ], | |
| 324 | + selectedKeys: [] | |
| 325 | + }, | |
| 326 | + materialConditionConfigs: [ | |
| 327 | + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' }, | |
| 328 | + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' }, | |
| 329 | + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' }, | |
| 330 | + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' }, | |
| 331 | + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' }, | |
| 332 | + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' }, | |
| 333 | + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' }, | |
| 334 | + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' } | |
| 335 | + ], | |
| 336 | + dictData: {} | |
| 337 | + } | |
| 338 | + }, | |
| 339 | + onLoad(query) { | |
| 340 | + this.id = (query && query.id) || '' | |
| 341 | + this.supplierSheet.fetchFn = this.fetchSuppliers.bind(this) | |
| 342 | + this.loadProducts() | |
| 343 | + this.loadDictData() | |
| 344 | + this.loadDetail() | |
| 345 | + }, | |
| 346 | + methods: { | |
| 347 | + async loadDictData() { | |
| 348 | + try { | |
| 349 | + const codes = [...this.materialConditionConfigs.map(mc => mc.dictCode), 'DELIVERY_METHOD'] | |
| 350 | + const res = await getDicByCodes(codes) | |
| 351 | + this.dictData = res || {} | |
| 352 | + } catch (e) { | |
| 353 | + this.dictData = {} | |
| 354 | + } | |
| 355 | + }, | |
| 356 | + getDictOptions(dictCode) { | |
| 357 | + const data = this.dictData[dictCode] | |
| 358 | + return (data && data.data) ? data.data : [] | |
| 359 | + }, | |
| 360 | + isTagSelected(field, code) { | |
| 361 | + const selected = this.form[field] | |
| 362 | + return Array.isArray(selected) && selected.includes(code) | |
| 363 | + }, | |
| 364 | + toggleTag(field, code) { | |
| 365 | + let selected = this.form[field] | |
| 366 | + if (!Array.isArray(selected)) { | |
| 367 | + selected = [] | |
| 368 | + } | |
| 369 | + const index = selected.indexOf(code) | |
| 370 | + if (index > -1) { | |
| 371 | + selected.splice(index, 1) | |
| 372 | + } else { | |
| 373 | + selected.push(code) | |
| 374 | + } | |
| 375 | + this.$set(this.form, field, [...selected]) | |
| 376 | + }, | |
| 377 | + async loadProducts() { | |
| 378 | + try { | |
| 379 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 380 | + const _data = res && res.data ? res.data : {} | |
| 381 | + const list = _data.datas || _data.list || _data.records || [] | |
| 382 | + this.productList = list.map(it => ({ | |
| 383 | + label: it.productName || it.name || '', | |
| 384 | + value: it.productId || it.id || '', | |
| 385 | + lossRate: it.fixedLossRate != null ? it.fixedLossRate : (it.lossRate != null ? it.lossRate : (it.attritionRate != null ? it.attritionRate : '')) | |
| 386 | + })) | |
| 387 | + } catch (e) { | |
| 388 | + this.productList = [] | |
| 389 | + } | |
| 390 | + }, | |
| 391 | + async loadDetail() { | |
| 392 | + if (!this.id) return | |
| 393 | + try { | |
| 394 | + const res = await salesmanQuotationDetailApi({ id: this.id }) | |
| 395 | + const d = (res && res.data) ? res.data : {} | |
| 396 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 397 | + materialFields.forEach(f => { | |
| 398 | + const v = d[f] | |
| 399 | + d[f] = (typeof v === 'string' && v) ? v.split(',').filter(Boolean) : (Array.isArray(v) ? v : []) | |
| 400 | + }) | |
| 401 | + this.form = { ...this.form, ...d } | |
| 402 | + this.productLineList = Array.isArray(d.quotationLineList) ? d.quotationLineList : (Array.isArray(d.salesmanQuotationLineList) ? d.salesmanQuotationLineList : []) | |
| 403 | + } catch (e) { | |
| 404 | + uni.showToast({ title: '加载详情失败', icon: 'none' }) | |
| 405 | + } | |
| 406 | + }, | |
| 407 | + fetchSuppliers({ pageIndex, pageSize, extra }) { | |
| 408 | + const name = (extra && (extra.name || extra.keyword || extra.key)) || '' | |
| 409 | + const params = { | |
| 410 | + pageIndex, | |
| 411 | + pageSize, | |
| 412 | + available: true, | |
| 413 | + unitName: name, | |
| 414 | + status: 'PASS', | |
| 415 | + freeze: false | |
| 416 | + } | |
| 417 | + return domesticCustomerCreditQueryApi(params).then(res => { | |
| 418 | + const _data = res && res.data ? res.data : {} | |
| 419 | + const records = _data.datas || _data.list || _data.records || [] | |
| 420 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 421 | + const hasNext = _data.hasNext || false | |
| 422 | + return { records, totalCount, hasNext } | |
| 423 | + }).catch(() => ({ records: [], totalCount: 0, hasNext: false })) | |
| 424 | + }, | |
| 425 | + openSupplierSheet() { | |
| 426 | + this.supplierSheet.selectedKeys = this.form.supplierId ? [this.form.supplierId] : [] | |
| 427 | + this.supplierSheet.visible = true | |
| 428 | + }, | |
| 429 | + onSupplierConfirm({ items }) { | |
| 430 | + const first = (items && items.length > 0) ? items[0] : null | |
| 431 | + if (!first) return | |
| 432 | + this.form.supplierId = first.id || '' | |
| 433 | + this.form.supplierName = first.unitName || '' | |
| 434 | + this.form.purchaseDepartmentName = first.purchaseDepartmentName || '' | |
| 435 | + this.form.regionName = first.regionName || '' | |
| 436 | + }, | |
| 437 | + boolText(v) { | |
| 438 | + if (typeof v !== 'boolean') return '请选择' | |
| 439 | + return v ? '是' : '否' | |
| 440 | + }, | |
| 441 | + openSheet(field) { | |
| 442 | + const opts = [{ label: '是', value: true }, { label: '否', value: false }] | |
| 443 | + const cur = this.form[field] | |
| 444 | + const match = opts.find(o => o.value === cur) | |
| 445 | + const titleMap = { | |
| 446 | + longTermOperation: '是否长单操作', | |
| 447 | + terminalCustomer: '是否终端客户', | |
| 448 | + priced: '是否未点价', | |
| 449 | + otherExpenseSettlement: '其他费用凭票到厂另行结算', | |
| 450 | + commodityBuyout: '是否通货买断', | |
| 451 | + qualityBuyout: '是否质量买断', | |
| 452 | + quantityBuyout: '是否数量买断', | |
| 453 | + doorDelivery: '是否送货上门' | |
| 454 | + } | |
| 455 | + this.sheet = { visible: true, title: titleMap[field] || '请选择', options: opts, value: match ? match.value : '', field, isDict: false } | |
| 456 | + }, | |
| 457 | + openDictSheet(field, dictCode, title) { | |
| 458 | + const raw = this.getDictOptions(dictCode) || [] | |
| 459 | + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') })) | |
| 460 | + const cur = this.form[field] | |
| 461 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 462 | + this.sheet = { visible: true, title: title || '请选择', options: opts, value: match ? match.value : '', field, isDict: true } | |
| 463 | + }, | |
| 464 | + getDeliveryMethodLabel() { | |
| 465 | + const opts = this.getDictOptions('DELIVERY_METHOD') || [] | |
| 466 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.deliveryMode)) | |
| 467 | + return m ? (m.name || m.label || '') : '' | |
| 468 | + }, | |
| 469 | + onSheetConfirm({ value }) { | |
| 470 | + const field = this.sheet.field | |
| 471 | + if (!field) return | |
| 472 | + if (this.sheet.isDict) { | |
| 473 | + this.form[field] = value | |
| 474 | + } else { | |
| 475 | + this.form[field] = !!value | |
| 476 | + if (field === 'priced' && value === true) { | |
| 477 | + this.form.pricingDeadline = '' | |
| 478 | + } | |
| 479 | + } | |
| 480 | + }, | |
| 481 | + onRatioBlur() { | |
| 482 | + let v = parseFloat(this.form.settlementRatio) | |
| 483 | + if (isNaN(v)) v = '' | |
| 484 | + else v = Math.round(v * 100) / 100 | |
| 485 | + this.form.settlementRatio = v | |
| 486 | + }, | |
| 487 | + onProductsChange(products) { | |
| 488 | + this.productLineList = Array.isArray(products) ? products : [] | |
| 489 | + }, | |
| 490 | + validateRequired() { | |
| 491 | + const stringChecks = [ | |
| 492 | + { key: 'code', msg: '请输入编号' }, | |
| 493 | + { key: 'supplierId', msg: '请选择供应商' }, | |
| 494 | + { key: 'validityPeriod', msg: '请输入报价时效' }, | |
| 495 | + { key: 'deliveryMode', msg: '请选择交提货方式' }, | |
| 496 | + { key: 'invoiceProductName', msg: '请输入开票品种' }, | |
| 497 | + { key: 'deliveryDate', msg: '请选择交货日期' }, | |
| 498 | + { key: 'settlementMethod', msg: '请输入结算方式' }, | |
| 499 | + { key: 'settlementRatio', msg: '请输入结算比例' } | |
| 500 | + ] | |
| 501 | + for (const it of stringChecks) { | |
| 502 | + const val = this.form[it.key] | |
| 503 | + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) | |
| 504 | + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false } | |
| 505 | + } | |
| 506 | + const boolChecks = [ | |
| 507 | + { key: 'longTermOperation', msg: '请选择是否长单操作' }, | |
| 508 | + { key: 'terminalCustomer', msg: '请选择是否终端客户' }, | |
| 509 | + { key: 'priced', msg: '请选择是否未点价' }, | |
| 510 | + { key: 'otherExpenseSettlement', msg: '请选择其他费用凭票到厂另行结算' }, | |
| 511 | + { key: 'commodityBuyout', msg: '请选择是否通货买断' }, | |
| 512 | + { key: 'qualityBuyout', msg: '请选择是否质量买断' }, | |
| 513 | + { key: 'quantityBuyout', msg: '请选择是否数量买断' }, | |
| 514 | + { key: 'doorDelivery', msg: '请选择是否送货上门' } | |
| 515 | + ] | |
| 516 | + for (const it of boolChecks) { | |
| 517 | + if (typeof this.form[it.key] !== 'boolean') { | |
| 518 | + uni.showToast({ title: it.msg, icon: 'none' }) | |
| 519 | + return false | |
| 520 | + } | |
| 521 | + } | |
| 522 | + if (this.form.priced === false && !this.form.pricingDeadline) { | |
| 523 | + uni.showToast({ title: '请选择点价截止日期', icon: 'none' }) | |
| 524 | + return false | |
| 525 | + } | |
| 526 | + const list = Array.isArray(this.productLineList) ? this.productLineList : [] | |
| 527 | + if (list.length === 0) { | |
| 528 | + uni.showToast({ title: '请添加产品明细', icon: 'none' }) | |
| 529 | + return false | |
| 530 | + } | |
| 531 | + if (this.$refs.productRel && !this.$refs.productRel.validate(this.form.priced)) return false | |
| 532 | + return true | |
| 533 | + }, | |
| 534 | + async onSubmit() { | |
| 535 | + if (!this.validateRequired()) return | |
| 536 | + const confirmRes = await new Promise(resolve => { | |
| 537 | + uni.showModal({ title: '提示', content: '确定保存修改吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 538 | + }) | |
| 539 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 540 | + const clean = (obj) => { | |
| 541 | + const out = {} | |
| 542 | + Object.keys(obj || {}).forEach(k => { | |
| 543 | + const v = obj[k] | |
| 544 | + const isEmptyString = typeof v === 'string' && v.trim() === '' | |
| 545 | + const isUndef = v === undefined || v === null | |
| 546 | + const isNaNNumber = typeof v === 'number' && isNaN(v) | |
| 547 | + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v | |
| 548 | + }) | |
| 549 | + return out | |
| 550 | + } | |
| 551 | + const lines = (this.productLineList || []).map(it => clean(it)) | |
| 552 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 553 | + const formCopy = { ...this.form } | |
| 554 | + materialFields.forEach(f => { | |
| 555 | + const arr = formCopy[f] | |
| 556 | + formCopy[f] = Array.isArray(arr) ? arr.join(',') : (arr || '') | |
| 557 | + }) | |
| 558 | + const payload = clean({ | |
| 559 | + ...formCopy, | |
| 560 | + id: this.id, | |
| 561 | + quotationLineList: lines | |
| 562 | + }) | |
| 563 | + try { | |
| 564 | + await salesmanQuotationUpdateApi(payload) | |
| 565 | + uni.showToast({ title: '保存成功', icon: 'none' }) | |
| 566 | + try { uni.setStorageSync('SALES_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 567 | + setTimeout(() => { uni.redirectTo({ url: '/pages/sales-price/index' }) }, 400) | |
| 568 | + } catch (e) { | |
| 569 | + uni.showToast({ title: e.msg || '保存失败', icon: 'none' }) | |
| 570 | + } | |
| 571 | + } | |
| 572 | + } | |
| 573 | +} | |
| 574 | +</script> | |
| 575 | +<style lang="scss" scoped> | |
| 576 | +.page { | |
| 577 | + display: flex; | |
| 578 | + flex-direction: column; | |
| 579 | + height: 100%; | |
| 580 | +} | |
| 581 | + | |
| 582 | +.scroll { | |
| 583 | + flex: 1; | |
| 584 | + padding: 12rpx 0 392rpx !important; | |
| 585 | +} | |
| 586 | + | |
| 587 | +.footer { | |
| 588 | + z-index: 2; | |
| 589 | + position: fixed; | |
| 590 | + left: 0; | |
| 591 | + right: 0; | |
| 592 | + bottom: 0; | |
| 593 | + padding: 32rpx; | |
| 594 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 595 | + background: #fff; | |
| 596 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 597 | + | |
| 598 | + .btn { | |
| 599 | + height: 80rpx; | |
| 600 | + line-height: 80rpx; | |
| 601 | + border-radius: 12rpx; | |
| 602 | + font-size: 32rpx; | |
| 603 | + } | |
| 604 | + | |
| 605 | + .submit { | |
| 606 | + background: $theme-primary; | |
| 607 | + color: #fff; | |
| 608 | + } | |
| 609 | +} | |
| 610 | + | |
| 611 | +::v-deep .uni-list { | |
| 612 | + .uni-easyinput { | |
| 613 | + display: flex; | |
| 614 | + | |
| 615 | + .uni-input-input { | |
| 616 | + color: rgba(0, 0, 0, 0.9); | |
| 617 | + } | |
| 618 | + } | |
| 619 | + | |
| 620 | + .uni-input-placeholder { | |
| 621 | + z-index: 1; | |
| 622 | + } | |
| 623 | + | |
| 624 | + .uni-input-input { | |
| 625 | + background-color: #ffffff; | |
| 626 | + } | |
| 627 | + | |
| 628 | + background: transparent; | |
| 629 | + | |
| 630 | + &-item { | |
| 631 | + &__extra-text { | |
| 632 | + font-size: 32rpx; | |
| 633 | + } | |
| 634 | + | |
| 635 | + &__content-title { | |
| 636 | + font-size: 32rpx; | |
| 637 | + color: rgba(0, 0, 0, 0.9); | |
| 638 | + } | |
| 639 | + | |
| 640 | + &__container { | |
| 641 | + padding: 32rpx; | |
| 642 | + | |
| 643 | + .uni-easyinput { | |
| 644 | + &__placeholder-class { | |
| 645 | + font-size: 32rpx; | |
| 646 | + color: rgba(0, 0, 0, 0.4); | |
| 647 | + } | |
| 648 | + | |
| 649 | + &__content { | |
| 650 | + border: none; | |
| 651 | + background-color: #ffffff !important; | |
| 652 | + | |
| 653 | + &-input { | |
| 654 | + padding-left: 0 !important; | |
| 655 | + height: 48rpx; | |
| 656 | + line-height: 48rpx; | |
| 657 | + font-size: 32rpx; | |
| 658 | + } | |
| 659 | + | |
| 660 | + .content-clear-icon { | |
| 661 | + font-size: 44rpx !important; | |
| 662 | + } | |
| 663 | + } | |
| 664 | + } | |
| 665 | + | |
| 666 | + .item-title, | |
| 667 | + .uni-list-item__content { | |
| 668 | + flex: none; | |
| 669 | + min-height: 48rpx; | |
| 670 | + line-height: 48rpx; | |
| 671 | + font-size: 32rpx; | |
| 672 | + position: relative; | |
| 673 | + width: 240rpx; | |
| 674 | + margin-right: 32rpx; | |
| 675 | + color: rgba(0, 0, 0, 0.9); | |
| 676 | + | |
| 677 | + .required { | |
| 678 | + color: red; | |
| 679 | + position: absolute; | |
| 680 | + top: 50%; | |
| 681 | + transform: translateY(-50%); | |
| 682 | + left: -16rpx; | |
| 683 | + } | |
| 684 | + } | |
| 685 | + } | |
| 686 | + | |
| 687 | + &.select-item { | |
| 688 | + &.is-empty { | |
| 689 | + .uni-list-item__extra-text { | |
| 690 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 691 | + } | |
| 692 | + } | |
| 693 | + | |
| 694 | + &.is-filled { | |
| 695 | + .uni-list-item__extra-text { | |
| 696 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 697 | + } | |
| 698 | + } | |
| 699 | + | |
| 700 | + .serial-number-row { | |
| 701 | + display: flex; | |
| 702 | + align-items: center; | |
| 703 | + | |
| 704 | + .generate-btn { | |
| 705 | + margin-left: 24rpx; | |
| 706 | + height: 48rpx; | |
| 707 | + line-height: 48rpx; | |
| 708 | + padding: 0 24rpx; | |
| 709 | + font-size: 24rpx; | |
| 710 | + border-radius: 8rpx; | |
| 711 | + background: $theme-primary; | |
| 712 | + color: #fff; | |
| 713 | + } | |
| 714 | + } | |
| 715 | + } | |
| 716 | + } | |
| 717 | +} | |
| 718 | + | |
| 719 | +.readonly-text { | |
| 720 | + color: rgba(0, 0, 0, 0.9); | |
| 721 | + font-size: 32rpx; | |
| 722 | + line-height: 48rpx; | |
| 723 | + text-align: right; | |
| 724 | + white-space: pre-wrap; | |
| 725 | + word-break: break-all; | |
| 726 | +} | |
| 727 | + | |
| 728 | +.value-code { | |
| 729 | + width: 260rpx; | |
| 730 | + text-align: left; | |
| 731 | +} | |
| 732 | + | |
| 733 | +.material-condition-section, | |
| 734 | +.remark-section { | |
| 735 | + margin-top: 20rpx; | |
| 736 | + background: #fff; | |
| 737 | +} | |
| 738 | + | |
| 739 | +.section-header { | |
| 740 | + display: flex; | |
| 741 | + align-items: center; | |
| 742 | + padding: 24rpx 32rpx; | |
| 743 | + border-bottom: 1rpx solid #f0f0f0; | |
| 744 | +} | |
| 745 | + | |
| 746 | +.section-title { | |
| 747 | + font-size: 32rpx; | |
| 748 | + color: rgba(0, 0, 0, 0.9); | |
| 749 | + font-weight: 600; | |
| 750 | +} | |
| 751 | + | |
| 752 | +.opCollapse { | |
| 753 | + width: 32rpx; | |
| 754 | + height: 28rpx; | |
| 755 | + margin-right: 16rpx; | |
| 756 | + margin-top: 8rpx; | |
| 757 | +} | |
| 758 | + | |
| 759 | +.section-content { | |
| 760 | + padding: 32rpx; | |
| 761 | +} | |
| 762 | + | |
| 763 | +.form-row { | |
| 764 | + display: flex; | |
| 765 | + align-items: center; | |
| 766 | + justify-content: space-between; | |
| 767 | + margin-bottom: 24rpx; | |
| 768 | +} | |
| 769 | + | |
| 770 | +.form-row:last-child { | |
| 771 | + margin-bottom: 0; | |
| 772 | +} | |
| 773 | + | |
| 774 | +.form-label { | |
| 775 | + font-size: 32rpx; | |
| 776 | + color: rgba(0, 0, 0, 0.9); | |
| 777 | +} | |
| 778 | + | |
| 779 | +.bool-btns { | |
| 780 | + display: flex; | |
| 781 | + gap: 20rpx; | |
| 782 | +} | |
| 783 | + | |
| 784 | +.bool-btn { | |
| 785 | + padding: 12rpx 40rpx; | |
| 786 | + border: 1rpx solid #ddd; | |
| 787 | + border-radius: 8rpx; | |
| 788 | + font-size: 28rpx; | |
| 789 | + color: #666; | |
| 790 | + background: #fff; | |
| 791 | +} | |
| 792 | + | |
| 793 | +.bool-btn.active { | |
| 794 | + background: $theme-primary; | |
| 795 | + border-color: $theme-primary; | |
| 796 | + color: #fff; | |
| 797 | +} | |
| 798 | + | |
| 799 | +.material-group { | |
| 800 | + margin-top: 32rpx; | |
| 801 | +} | |
| 802 | + | |
| 803 | +.material-label { | |
| 804 | + display: block; | |
| 805 | + font-size: 30rpx; | |
| 806 | + color: rgba(0, 0, 0, 0.9); | |
| 807 | + margin-bottom: 20rpx; | |
| 808 | +} | |
| 809 | + | |
| 810 | +.tag-list { | |
| 811 | + display: flex; | |
| 812 | + flex-wrap: wrap; | |
| 813 | + gap: 16rpx; | |
| 814 | +} | |
| 815 | + | |
| 816 | +.tag-btn { | |
| 817 | + padding: 12rpx 24rpx; | |
| 818 | + border: 1rpx solid #ddd; | |
| 819 | + border-radius: 8rpx; | |
| 820 | + font-size: 28rpx; | |
| 821 | + color: #666; | |
| 822 | + background: #fff; | |
| 823 | +} | |
| 824 | + | |
| 825 | +.tag-btn.active { | |
| 826 | + background: $theme-primary; | |
| 827 | + border-color: $theme-primary; | |
| 828 | + color: #fff; | |
| 829 | +} | |
| 830 | + | |
| 831 | +::v-deep .uni-easyinput { | |
| 832 | + width: 100%; | |
| 833 | + | |
| 834 | + &__content { | |
| 835 | + background: #f8f8f8 !important; | |
| 836 | + border-radius: 8rpx; | |
| 837 | + | |
| 838 | + &-input { | |
| 839 | + font-size: 30rpx; | |
| 840 | + } | |
| 841 | + } | |
| 842 | +} | |
| 843 | +</style> | ... | ... |
pages/sales-price/productRel.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="product-rel"> | |
| 3 | + <view class="header"> | |
| 4 | + <image class="opCollapse" src="/static/images/title.png" /> | |
| 5 | + <text class="title">明细信息</text> | |
| 6 | + <view class="ops"> | |
| 7 | + <image v-if="mode === 'add' || mode === 'edit'" class="opAdd" @click="onAdd" src="/static/images/plus.png" /> | |
| 8 | + <view v-if="mode === 'view'" class="op1" @click="toggleViewCollapse"> | |
| 9 | + <image class="opAdd" :src="collapsedView ? '/static/images/down.png' : '/static/images/up.png'" /> | |
| 10 | + <text class="op">{{ collapsedView ? '展开' : '收起' }}</text> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + </view> | |
| 14 | + | |
| 15 | + <view v-if="mode === 'add' || mode === 'edit'" class="add-list"> | |
| 16 | + <view v-for="(item, idx) in items" :key="item._key || idx" class="block"> | |
| 17 | + <uni-list v-show="!item.collapsed"> | |
| 18 | + <uni-list-item class="select-item" :class="item.productName ? 'is-filled' : 'is-empty'" clickable @click="openProductSheet(idx)" :rightText="item.productName || '请选择品种'" showArrow> | |
| 19 | + <template v-slot:body> | |
| 20 | + <view class="item-title"><text class="required">*</text><text>品种</text></view> | |
| 21 | + </template> | |
| 22 | + </uni-list-item> | |
| 23 | + <uni-list-item title="料型"> | |
| 24 | + <template v-slot:footer> | |
| 25 | + <uni-easyinput v-model="item.materialType" :inputBorder="false" placeholder="请输入料型" /> | |
| 26 | + </template> | |
| 27 | + </uni-list-item> | |
| 28 | + <uni-list-item title="数量(吨)"> | |
| 29 | + <template v-slot:footer> | |
| 30 | + <uni-easyinput v-model="item.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @input="onNonNegativeInput(idx, 'quantity')" @blur="onNonNegativeBlur(idx, 'quantity', 1)" /> | |
| 31 | + </template> | |
| 32 | + </uni-list-item> | |
| 33 | + <uni-list-item title="采购价"> | |
| 34 | + <template v-slot:footer> | |
| 35 | + <uni-easyinput v-model="item.purchasePrice" type="digit" :inputBorder="false" placeholder="请输入采购价" @input="onNonNegativeInput(idx, 'purchasePrice')" @blur="onNonNegativeBlur(idx, 'purchasePrice', 2)" @change="recalcEstimatedLoss(idx)" /> | |
| 36 | + </template> | |
| 37 | + </uni-list-item> | |
| 38 | + <uni-list-item title="买断料预计损耗"> | |
| 39 | + <template v-slot:footer> | |
| 40 | + <uni-easyinput v-model="item.estimatedLoss" type="digit" :inputBorder="false" disabled placeholder="" /> | |
| 41 | + </template> | |
| 42 | + </uni-list-item> | |
| 43 | + <uni-list-item title="申请损耗(%)"> | |
| 44 | + <template v-slot:footer> | |
| 45 | + <uni-easyinput v-model="item.applicationAttrition" type="digit" :inputBorder="false" placeholder="请输入申请损耗" @input="onNonNegativeInput(idx, 'applicationAttrition')" @blur="onNonNegativeBlur(idx, 'applicationAttrition', 2)" @change="recalcEstimatedLoss(idx)" /> | |
| 46 | + </template> | |
| 47 | + </uni-list-item> | |
| 48 | + </uni-list> | |
| 49 | + <uni-list v-show="item.collapsed"> | |
| 50 | + <uni-list-item class="select-item" :class="item.productName ? 'is-filled' : 'is-empty'" clickable @click="openProductSheet(idx)" :rightText="item.productName || '请选择品种'" showArrow> | |
| 51 | + <template v-slot:body> | |
| 52 | + <view class="item-title"><text class="required">*</text><text>品种</text></view> | |
| 53 | + </template> | |
| 54 | + </uni-list-item> | |
| 55 | + </uni-list> | |
| 56 | + <view class="block-ops"> | |
| 57 | + <div class="del" @click="onRemove(idx)"> | |
| 58 | + <image src="/static/images/delete.png" class="icon" /> | |
| 59 | + 删除 | |
| 60 | + </div> | |
| 61 | + <div class="toggle" @click="toggleItem(idx)"> | |
| 62 | + <image :src="item.collapsed ? '/static/images/up.png' : '/static/images/down.png'" class="icon" /> | |
| 63 | + {{ item.collapsed ? '展开' : '收起' }}</div> | |
| 64 | + </view> | |
| 65 | + </view> | |
| 66 | + </view> | |
| 67 | + | |
| 68 | + <view v-else class="view-list" v-show="!collapsedView"> | |
| 69 | + <view v-for="(item, idx) in items" :key="'v-' + (item._key || idx)" class="card"> | |
| 70 | + <view class="row"><text class="label">品种</text><text class="value">{{ item.productName }}</text></view> | |
| 71 | + <view class="row"><text class="label">料型</text><text class="value">{{ item.materialType }}</text></view> | |
| 72 | + <view class="row"><text class="label">数量(吨)</text><text class="value">{{ item.quantity }}</text></view> | |
| 73 | + <view class="row"><text class="label">采购价</text><text class="value">{{ item.purchasePrice }}</text></view> | |
| 74 | + <view class="row"><text class="label">买断料预计损耗</text><text class="value">{{ item.estimatedLoss }}</text></view> | |
| 75 | + <view class="row"><text class="label">申请损耗(%)</text><text class="value">{{ item.applicationAttrition }}</text></view> | |
| 76 | + </view> | |
| 77 | + </view> | |
| 78 | + | |
| 79 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value" @confirm="onProductConfirm" /> | |
| 80 | + </view> | |
| 81 | +</template> | |
| 82 | +<script> | |
| 83 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 84 | + | |
| 85 | +export default { | |
| 86 | + name: 'SalesPriceProductRel', | |
| 87 | + props: { | |
| 88 | + mode: { type: String, default: 'add' }, | |
| 89 | + list: { type: Array, default: () => [] }, | |
| 90 | + max: { type: Number, default: 50 }, | |
| 91 | + options: { type: Array, default: () => [] }, | |
| 92 | + priced: { type: Boolean, default: true } | |
| 93 | + }, | |
| 94 | + components: { SingleSelectSheet }, | |
| 95 | + data() { | |
| 96 | + return { | |
| 97 | + items: [], | |
| 98 | + collapsedView: false, | |
| 99 | + sheet: { visible: false, title: '请选择', options: [], value: '', idx: -1 }, | |
| 100 | + _initialized: false | |
| 101 | + } | |
| 102 | + }, | |
| 103 | + computed: { | |
| 104 | + selectOptions() { | |
| 105 | + const list = Array.isArray(this.options) ? this.options : [] | |
| 106 | + return list.map(o => ({ | |
| 107 | + label: o.label != null ? o.label : (o.name != null ? o.name : ''), | |
| 108 | + value: o.value != null ? o.value : (o.id != null ? o.id : o.productId) | |
| 109 | + })) | |
| 110 | + } | |
| 111 | + }, | |
| 112 | + watch: { | |
| 113 | + items: { | |
| 114 | + handler() { this.emitChange() }, | |
| 115 | + deep: true | |
| 116 | + }, | |
| 117 | + list: { | |
| 118 | + handler(v) { | |
| 119 | + if (!v || !v.length) return | |
| 120 | + this._initialized = false | |
| 121 | + this.initItems() | |
| 122 | + this.$nextTick(() => { this._initialized = true }) | |
| 123 | + } | |
| 124 | + } | |
| 125 | + }, | |
| 126 | + created() { | |
| 127 | + this.initItems() | |
| 128 | + this.$nextTick(() => { this._initialized = true }) | |
| 129 | + }, | |
| 130 | + methods: { | |
| 131 | + initItems() { | |
| 132 | + const src = Array.isArray(this.list) && this.list.length > 0 ? this.list : [] | |
| 133 | + if (!src.length) { | |
| 134 | + this.items = [{ ...this.defaultItem(), _key: this.newKey(), collapsed: false }] | |
| 135 | + return | |
| 136 | + } | |
| 137 | + const existing = this.items || [] | |
| 138 | + const existingMap = {} | |
| 139 | + existing.forEach(it => { | |
| 140 | + if (it.itemId) existingMap[it.itemId] = it | |
| 141 | + }) | |
| 142 | + this.items = src.map((x, i) => { | |
| 143 | + const key = x && x.itemId ? x.itemId : (x && x._key ? x._key : this.newKey()) | |
| 144 | + const old = existingMap[key] | |
| 145 | + return { | |
| 146 | + ...this.defaultItem(), | |
| 147 | + ...x, | |
| 148 | + _key: key, | |
| 149 | + itemId: x && x.itemId ? x.itemId : key, | |
| 150 | + collapsed: old ? old.collapsed : true | |
| 151 | + } | |
| 152 | + }) | |
| 153 | + }, | |
| 154 | + newKey() { | |
| 155 | + return `${Date.now()}-${Math.random().toString(16).slice(2)}` | |
| 156 | + }, | |
| 157 | + defaultItem() { | |
| 158 | + return { | |
| 159 | + productId: '', | |
| 160 | + productName: '', | |
| 161 | + materialType: '', | |
| 162 | + quantity: '', | |
| 163 | + purchasePrice: '', | |
| 164 | + estimatedLoss: '', | |
| 165 | + applicationAttrition: '', | |
| 166 | + attritionRate: '' | |
| 167 | + } | |
| 168 | + }, | |
| 169 | + onNonNegativeInput(idx, field) { | |
| 170 | + const it = this.items[idx] | |
| 171 | + if (!it) return | |
| 172 | + let v = String(it[field] != null ? it[field] : '') | |
| 173 | + v = v.replace(/[^0-9.]/g, '') | |
| 174 | + v = v.replace(/(\..*)\./g, '$1') | |
| 175 | + it[field] = v | |
| 176 | + this.$set(this.items, idx, it) | |
| 177 | + if (field === 'applicationAttrition' || field === 'purchasePrice') { | |
| 178 | + this.recalcEstimatedLoss(idx) | |
| 179 | + } | |
| 180 | + }, | |
| 181 | + onNonNegativeBlur(idx, field, digits) { | |
| 182 | + const it = this.items[idx] | |
| 183 | + if (!it) return | |
| 184 | + let raw = it[field] | |
| 185 | + if (raw === '' || raw === null || raw === undefined) { | |
| 186 | + this.$set(this.items, idx, it) | |
| 187 | + return | |
| 188 | + } | |
| 189 | + let val = parseFloat(raw) | |
| 190 | + if (isNaN(val)) val = 0 | |
| 191 | + const m = Math.pow(10, digits) | |
| 192 | + const rounded = Math.round(val * m) / m | |
| 193 | + it[field] = rounded | |
| 194 | + this.$set(this.items, idx, it) | |
| 195 | + if (field === 'applicationAttrition' || field === 'purchasePrice') { | |
| 196 | + this.recalcEstimatedLoss(idx) | |
| 197 | + } | |
| 198 | + }, | |
| 199 | + recalcEstimatedLoss(idx) { | |
| 200 | + const it = this.items[idx] | |
| 201 | + if (!it) return | |
| 202 | + const price = parseFloat(it.purchasePrice) | |
| 203 | + if (isNaN(price) || price === 0) { | |
| 204 | + it.estimatedLoss = '' | |
| 205 | + this.$set(this.items, idx, it) | |
| 206 | + return | |
| 207 | + } | |
| 208 | + const app = it.applicationAttrition | |
| 209 | + const appNum = parseFloat(app) | |
| 210 | + if (app !== '' && app != null && !isNaN(appNum)) { | |
| 211 | + it.estimatedLoss = Math.round(price * appNum / 100 * 100) / 100 | |
| 212 | + } else { | |
| 213 | + const opt = (this.options || []).find(o => String(o.value) === String(it.productId)) | |
| 214 | + const rate = opt ? (opt.lossRate != null ? opt.lossRate : opt.fixedLossRate) : null | |
| 215 | + const rateNum = parseFloat(rate) | |
| 216 | + if (!isNaN(rateNum)) { | |
| 217 | + it.estimatedLoss = Math.round(price * rateNum / 100 * 100) / 100 | |
| 218 | + } else { | |
| 219 | + it.estimatedLoss = '' | |
| 220 | + } | |
| 221 | + } | |
| 222 | + this.$set(this.items, idx, it) | |
| 223 | + }, | |
| 224 | + onAdd() { | |
| 225 | + if (this.items.length >= this.max) { | |
| 226 | + uni.showToast({ title: `最多${this.max}条明细`, icon: 'none' }) | |
| 227 | + return | |
| 228 | + } | |
| 229 | + this.items.push({ ...this.defaultItem(), _key: this.newKey(), collapsed: false }) | |
| 230 | + }, | |
| 231 | + onRemove(idx) { | |
| 232 | + this.items.splice(idx, 1) | |
| 233 | + if (this.items.length === 0) { | |
| 234 | + this.items.push({ ...this.defaultItem(), _key: this.newKey(), collapsed: false }) | |
| 235 | + } | |
| 236 | + }, | |
| 237 | + toggleItem(idx) { | |
| 238 | + const it = this.items[idx] | |
| 239 | + if (!it) return | |
| 240 | + this.$set(it, 'collapsed', !it.collapsed) | |
| 241 | + }, | |
| 242 | + toggleViewCollapse() { | |
| 243 | + this.collapsedView = !this.collapsedView | |
| 244 | + }, | |
| 245 | + openProductSheet(idx) { | |
| 246 | + const opts = this.selectOptions | |
| 247 | + const cur = this.items[idx] && this.items[idx].productId | |
| 248 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 249 | + this.sheet = { visible: true, title: '请选择品种', options: opts, value: match ? match.value : '', idx } | |
| 250 | + }, | |
| 251 | + onProductConfirm({ value, label }) { | |
| 252 | + const idx = this.sheet.idx | |
| 253 | + if (idx < 0 || !this.items[idx]) return | |
| 254 | + const it = this.items[idx] | |
| 255 | + this.$set(it, 'productId', value) | |
| 256 | + this.$set(it, 'productName', label) | |
| 257 | + const opt = (this.options || []).find(o => String(o.value != null ? o.value : (o.id != null ? o.id : o.productId)) === String(value)) | |
| 258 | + const rate = opt ? (opt.lossRate != null ? opt.lossRate : opt.fixedLossRate) : '' | |
| 259 | + this.$set(it, 'attritionRate', rate != null ? rate : '') | |
| 260 | + this.recalcEstimatedLoss(idx) | |
| 261 | + }, | |
| 262 | + emitChange() { | |
| 263 | + if (!this._initialized) return | |
| 264 | + const list = (this.items || []).map(it => { | |
| 265 | + const c = { ...it } | |
| 266 | + const key = c._key | |
| 267 | + delete c._key | |
| 268 | + delete c.collapsed | |
| 269 | + delete c._selected | |
| 270 | + if (!c.itemId) c.itemId = key | |
| 271 | + return c | |
| 272 | + }) | |
| 273 | + this.$emit('change', list) | |
| 274 | + }, | |
| 275 | + validate(priced) { | |
| 276 | + const list = this.items || [] | |
| 277 | + for (let i = 0; i < list.length; i++) { | |
| 278 | + const it = list[i] | |
| 279 | + if (!it.productId) { | |
| 280 | + uni.showToast({ title: `第${i + 1}条明细:请选择品种`, icon: 'none' }) | |
| 281 | + return false | |
| 282 | + } | |
| 283 | + if (priced === false && (it.purchasePrice === '' || it.purchasePrice == null)) { | |
| 284 | + uni.showToast({ title: `第${i + 1}条明细:未点价为否时采购价必填`, icon: 'none' }) | |
| 285 | + return false | |
| 286 | + } | |
| 287 | + } | |
| 288 | + return true | |
| 289 | + } | |
| 290 | + } | |
| 291 | +} | |
| 292 | +</script> | |
| 293 | +<style lang="scss" scoped> | |
| 294 | +.product-rel { | |
| 295 | + margin-top: 20rpx; | |
| 296 | + background: #fff; | |
| 297 | +} | |
| 298 | + | |
| 299 | +.header { | |
| 300 | + display: flex; | |
| 301 | + align-items: center; | |
| 302 | + padding: 24rpx 32rpx; | |
| 303 | + border-bottom: 1rpx solid #f0f0f0; | |
| 304 | +} | |
| 305 | + | |
| 306 | +.dot { | |
| 307 | + width: 16rpx; | |
| 308 | + height: 16rpx; | |
| 309 | + background: #3D48A3; | |
| 310 | + border-radius: 50%; | |
| 311 | + margin-right: 12rpx; | |
| 312 | +} | |
| 313 | + | |
| 314 | +.title { | |
| 315 | + font-size: 32rpx; | |
| 316 | + color: rgba(0, 0, 0, 0.9); | |
| 317 | + font-weight: 600; | |
| 318 | +} | |
| 319 | + | |
| 320 | +.ops { | |
| 321 | + margin-left: auto; | |
| 322 | +} | |
| 323 | + | |
| 324 | +.op { | |
| 325 | + color: $theme-primary; | |
| 326 | + font-size: 28rpx; | |
| 327 | + margin-left: 8rpx; | |
| 328 | +} | |
| 329 | + | |
| 330 | +.op1 { | |
| 331 | + display: flex; | |
| 332 | + align-items: center; | |
| 333 | +} | |
| 334 | + | |
| 335 | +.opAdd { | |
| 336 | + color: rgba(0, 0, 0, 0.6); | |
| 337 | + width: 40rpx; | |
| 338 | + height: 40rpx; | |
| 339 | +} | |
| 340 | + | |
| 341 | +.opCollapse { | |
| 342 | + color: rgba(0, 0, 0, 0.6); | |
| 343 | + width: 32rpx; | |
| 344 | + height: 28rpx; | |
| 345 | + margin-right: 16rpx; | |
| 346 | + margin-top: 8rpx; | |
| 347 | +} | |
| 348 | + | |
| 349 | +.block { | |
| 350 | + background: #fff; | |
| 351 | + margin-bottom: 20rpx; | |
| 352 | +} | |
| 353 | + | |
| 354 | +::v-deep .uni-list-item__content { | |
| 355 | + display: flex; | |
| 356 | + justify-content: center; | |
| 357 | +} | |
| 358 | + | |
| 359 | +::v-deep .uni-list { | |
| 360 | + background: transparent; | |
| 361 | + | |
| 362 | + &-item { | |
| 363 | + &__container { | |
| 364 | + padding: 32rpx; | |
| 365 | + } | |
| 366 | + | |
| 367 | + &__content-title { | |
| 368 | + font-size: 28rpx; | |
| 369 | + color: rgba(0, 0, 0, 0.9); | |
| 370 | + } | |
| 371 | + | |
| 372 | + &__extra-text { | |
| 373 | + font-size: 32rpx; | |
| 374 | + } | |
| 375 | + | |
| 376 | + .uni-easyinput { | |
| 377 | + width: 100%; | |
| 378 | + | |
| 379 | + &__placeholder-class { | |
| 380 | + font-size: 32rpx; | |
| 381 | + color: rgba(0, 0, 0, 0.4); | |
| 382 | + } | |
| 383 | + | |
| 384 | + &__content { | |
| 385 | + border: none; | |
| 386 | + display: flex; | |
| 387 | + | |
| 388 | + &-input { | |
| 389 | + padding-left: 0 !important; | |
| 390 | + height: 48rpx; | |
| 391 | + line-height: 48rpx; | |
| 392 | + font-size: 32rpx; | |
| 393 | + } | |
| 394 | + } | |
| 395 | + } | |
| 396 | + } | |
| 397 | +} | |
| 398 | + | |
| 399 | +.block-ops { | |
| 400 | + display: flex; | |
| 401 | + padding: 20rpx 32rpx 20rpx; | |
| 402 | + justify-content: space-around; | |
| 403 | +} | |
| 404 | + | |
| 405 | +.del { | |
| 406 | + color: #D54941; | |
| 407 | + font-size: 28rpx; | |
| 408 | + display: flex; | |
| 409 | + align-items: center; | |
| 410 | + | |
| 411 | + image { | |
| 412 | + width: 40rpx; | |
| 413 | + height: 40rpx; | |
| 414 | + } | |
| 415 | +} | |
| 416 | + | |
| 417 | +.toggle { | |
| 418 | + color: $theme-primary; | |
| 419 | + font-size: 28rpx; | |
| 420 | + display: flex; | |
| 421 | + align-items: center; | |
| 422 | + | |
| 423 | + image { | |
| 424 | + width: 40rpx; | |
| 425 | + height: 40rpx; | |
| 426 | + } | |
| 427 | +} | |
| 428 | + | |
| 429 | +.view-list { | |
| 430 | + padding: 24rpx 32rpx; | |
| 431 | + background: #ffffff; | |
| 432 | +} | |
| 433 | + | |
| 434 | +.card { | |
| 435 | + background: #f3f3f3; | |
| 436 | + border-radius: 16rpx; | |
| 437 | + padding: 24rpx; | |
| 438 | + margin-bottom: 20rpx; | |
| 439 | +} | |
| 440 | + | |
| 441 | +.row { | |
| 442 | + display: flex; | |
| 443 | + margin-bottom: 20rpx; | |
| 444 | +} | |
| 445 | + | |
| 446 | +.row:last-child { | |
| 447 | + margin-bottom: 0; | |
| 448 | +} | |
| 449 | + | |
| 450 | +.label { | |
| 451 | + width: 240rpx; | |
| 452 | + color: rgba(0, 0, 0, 0.6); | |
| 453 | + font-size: 28rpx; | |
| 454 | +} | |
| 455 | + | |
| 456 | +.value { | |
| 457 | + flex: 1; | |
| 458 | + color: rgba(0, 0, 0, 0.9); | |
| 459 | + font-size: 28rpx; | |
| 460 | +} | |
| 461 | + | |
| 462 | +.item-title { | |
| 463 | + position: relative; | |
| 464 | + width: 210rpx; | |
| 465 | + margin-right: 32rpx; | |
| 466 | + color: rgba(0, 0, 0, 0.9); | |
| 467 | + font-size: 32rpx; | |
| 468 | + min-height: 48rpx; | |
| 469 | + line-height: 48rpx; | |
| 470 | + | |
| 471 | + .required { | |
| 472 | + color: red; | |
| 473 | + position: absolute; | |
| 474 | + top: 50%; | |
| 475 | + transform: translateY(-50%); | |
| 476 | + left: -16rpx; | |
| 477 | + } | |
| 478 | +} | |
| 479 | +</style> | ... | ... |
| 1 | +<template> | |
| 2 | + <view class="detail-page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <!-- 基础信息 --> | |
| 5 | + <view class="section"> | |
| 6 | + <text class="row company">{{ safeText(form.code) }}</text> | |
| 7 | + <view class="row"><text class="label">供应商名称</text><text class="value">{{ safeText(form.supplierName) }}</text></view> | |
| 8 | + <view class="row"><text class="label">采购处</text><text class="value">{{ safeText(form.purchaseDepartmentName) }}</text></view> | |
| 9 | + <view class="row"><text class="label">区域</text><text class="value">{{ safeText(form.regionName) }}</text></view> | |
| 10 | + <view class="row"><text class="label">报价时效</text><text class="value">{{ safeText(form.validityPeriod) }}</text></view> | |
| 11 | + <view class="row"><text class="label">报价人</text><text class="value">{{ safeText(form.quoterName) }}</text></view> | |
| 12 | + <view class="row"><text class="label">是否长单操作</text><text class="value">{{ boolText(form.longTermOperation) }}</text></view> | |
| 13 | + <view class="row"><text class="label">是否终端客户</text><text class="value">{{ boolText(form.terminalCustomer) }}</text></view> | |
| 14 | + <view class="row"><text class="label">是否未点价</text><text class="value">{{ boolText(form.priced) }}</text></view> | |
| 15 | + <view v-if="form.priced === false" class="row"><text class="label">点价截止日期</text><text class="value">{{ formatDateOnly(form.pricingDeadline) }}</text></view> | |
| 16 | + <view class="row"><text class="label">其他费用凭票到厂另行结算</text><text class="value">{{ boolText(form.otherExpenseSettlement) }}</text></view> | |
| 17 | + <view class="row"><text class="label">交提货方式</text><text class="value">{{ getDeliveryMethodLabel() }}</text></view> | |
| 18 | + <view class="row"><text class="label">是否通货买断</text><text class="value">{{ boolText(form.commodityBuyout) }}</text></view> | |
| 19 | + <view class="row"><text class="label">是否质量买断</text><text class="value">{{ boolText(form.qualityBuyout) }}</text></view> | |
| 20 | + <view class="row"><text class="label">是否数量买断</text><text class="value">{{ boolText(form.quantityBuyout) }}</text></view> | |
| 21 | + <view class="row"><text class="label">是否送货上门</text><text class="value">{{ boolText(form.doorDelivery) }}</text></view> | |
| 22 | + <view class="row"><text class="label">装车费</text><text class="value">{{ safeText(form.loadingFee) }}</text></view> | |
| 23 | + <view class="row"><text class="label">开票品种</text><text class="value">{{ safeText(form.invoiceProductName) }}</text></view> | |
| 24 | + <view class="row"><text class="label">交货日期</text><text class="value">{{ formatDateOnly(form.deliveryDate) }}</text></view> | |
| 25 | + <view class="row"><text class="label">结算方式</text><text class="value">{{ safeText(form.settlementMethod) }}</text></view> | |
| 26 | + <view class="row"><text class="label">结算比例(%)</text><text class="value">{{ safeText(form.settlementRatio) }}</text></view> | |
| 27 | + <view class="row"><text class="label">协调事由</text><text class="value">{{ safeText(form.coordinationReason) }}</text></view> | |
| 28 | + <view class="row"><text class="label">票类状况</text><text class="value">{{ safeText(form.invoiceTypeStatus) }}</text></view> | |
| 29 | + </view> | |
| 30 | + | |
| 31 | + <!-- 明细信息 --> | |
| 32 | + <ProductRel mode="view" :list="productLineList" /> | |
| 33 | + | |
| 34 | + <!-- 料质状况 --> | |
| 35 | + <view class="title-header"> | |
| 36 | + <image class="title-header_icon" src="/static/images/title.png" /> | |
| 37 | + <span>料质状况</span> | |
| 38 | + </view> | |
| 39 | + <view class="section"> | |
| 40 | + <view class="row"><text class="label">是否启用料质状况</text><text class="value">{{ boolText(form.materialConditionEnabled) }}</text></view> | |
| 41 | + <template v-if="form.materialConditionEnabled"> | |
| 42 | + <view v-for="mc in materialConditionConfigs" :key="mc.field" class="material-group"> | |
| 43 | + <text class="material-label">{{ mc.label }}</text> | |
| 44 | + <view class="tag-list"> | |
| 45 | + <view | |
| 46 | + v-for="opt in getDictOptions(mc.dictCode)" | |
| 47 | + :key="opt.code" | |
| 48 | + class="tag-btn" | |
| 49 | + :class="{ active: isTagSelected(mc.field, opt.code) }" | |
| 50 | + >{{ opt.name }}</view> | |
| 51 | + </view> | |
| 52 | + </view> | |
| 53 | + </template> | |
| 54 | + </view> | |
| 55 | + | |
| 56 | + <!-- 备注 --> | |
| 57 | + <view class="title-header"> | |
| 58 | + <image class="title-header_icon" src="/static/images/title.png" /> | |
| 59 | + <span>备注</span> | |
| 60 | + </view> | |
| 61 | + <view class="section"> | |
| 62 | + <text class="remark-text">{{ safeText(form.remark) }}</text> | |
| 63 | + </view> | |
| 64 | + </scroll-view> | |
| 65 | + </view> | |
| 66 | +</template> | |
| 67 | +<script> | |
| 68 | +import ProductRel from './productRel.vue' | |
| 69 | +import { salesmanQuotationDetailApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 70 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 71 | + | |
| 72 | +export default { | |
| 73 | + name: 'SalesmanQuotationViewer', | |
| 74 | + components: { ProductRel }, | |
| 75 | + props: { id: { type: [String, Number], default: '' } }, | |
| 76 | + data() { | |
| 77 | + return { | |
| 78 | + form: {}, | |
| 79 | + productLineList: [], | |
| 80 | + materialConditionConfigs: [ | |
| 81 | + { field: 'materialConditionCopperRice', label: '铜米', dictCode: 'COPPER_RICE' }, | |
| 82 | + { field: 'materialConditionBrightCopper', label: '光亮铜', dictCode: 'BRIGHT_COPPER' }, | |
| 83 | + { field: 'materialConditionFirstClass', label: '一特', dictCode: 'FIRST_CLASS' }, | |
| 84 | + { field: 'materialConditionSecondClass', label: '二特', dictCode: 'SECOND_CLASS' }, | |
| 85 | + { field: 'materialConditionTinCoated', label: '镀锡紫', dictCode: 'TIN_COATED' }, | |
| 86 | + { field: 'materialConditionScrap', label: '角料', dictCode: 'SCRAP' }, | |
| 87 | + { field: 'materialConditionWhiteYellow', label: '镀白黄', dictCode: 'WHITE_YELLOW' }, | |
| 88 | + { field: 'materialConditionPhosphorCopper', label: '磷铜', dictCode: 'PHOSPHOR_COPPER' } | |
| 89 | + ], | |
| 90 | + dictData: {} | |
| 91 | + } | |
| 92 | + }, | |
| 93 | + created() { | |
| 94 | + this.loadDictData() | |
| 95 | + }, | |
| 96 | + watch: { | |
| 97 | + id: { | |
| 98 | + immediate: true, | |
| 99 | + handler(val) { | |
| 100 | + const v = (val !== undefined && val !== null) ? String(val) : '' | |
| 101 | + if (v) this.loadDetail(v) | |
| 102 | + } | |
| 103 | + } | |
| 104 | + }, | |
| 105 | + methods: { | |
| 106 | + async loadDictData() { | |
| 107 | + try { | |
| 108 | + const codes = [...this.materialConditionConfigs.map(mc => mc.dictCode), 'DELIVERY_METHOD'] | |
| 109 | + const res = await getDicByCodes(codes) | |
| 110 | + this.dictData = res || {} | |
| 111 | + } catch (e) { | |
| 112 | + this.dictData = {} | |
| 113 | + } | |
| 114 | + }, | |
| 115 | + getDictOptions(dictCode) { | |
| 116 | + const data = this.dictData[dictCode] | |
| 117 | + return (data && data.data) ? data.data : [] | |
| 118 | + }, | |
| 119 | + isTagSelected(field, code) { | |
| 120 | + const selected = this.form[field] | |
| 121 | + return Array.isArray(selected) && selected.includes(code) | |
| 122 | + }, | |
| 123 | + boolText(v) { | |
| 124 | + if (typeof v !== 'boolean') return '-' | |
| 125 | + return v ? '是' : '否' | |
| 126 | + }, | |
| 127 | + getDeliveryMethodLabel() { | |
| 128 | + const opts = this.getDictOptions('DELIVERY_METHOD') || [] | |
| 129 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.deliveryMode)) | |
| 130 | + return m ? (m.name || m.label || '-') : '-' | |
| 131 | + }, | |
| 132 | + formatDateOnly(v) { | |
| 133 | + const s = v == null ? '' : String(v).trim() | |
| 134 | + if (!s) return '-' | |
| 135 | + if (s.length >= 10) return s.slice(0, 10) | |
| 136 | + return s | |
| 137 | + }, | |
| 138 | + safeText(v) { | |
| 139 | + const s = v == null ? '' : String(v) | |
| 140 | + return s.trim() ? s : '-' | |
| 141 | + }, | |
| 142 | + async loadDetail(id) { | |
| 143 | + // 审核详情接口不可用时回退到普通详情接口 | |
| 144 | + try { | |
| 145 | + const res = await salesmanQuotationDetailApi({ id }) | |
| 146 | + const d = (res && res.data) ? res.data : {} | |
| 147 | + const materialFields = this.materialConditionConfigs.map(mc => mc.field) | |
| 148 | + materialFields.forEach(f => { | |
| 149 | + const v = d[f] | |
| 150 | + d[f] = (typeof v === 'string' && v) ? v.split(',').filter(Boolean) : (Array.isArray(v) ? v : []) | |
| 151 | + }) | |
| 152 | + this.form = d | |
| 153 | + this.productLineList = Array.isArray(d.quotationLineList) ? d.quotationLineList : (Array.isArray(d.salesmanQuotationLineList) ? d.salesmanQuotationLineList : []) | |
| 154 | + } catch (err) { | |
| 155 | + this.form = {} | |
| 156 | + this.productLineList = [] | |
| 157 | + } | |
| 158 | + } | |
| 159 | + } | |
| 160 | +} | |
| 161 | +</script> | |
| 162 | +<style lang="scss" scoped> | |
| 163 | +.detail-page { | |
| 164 | + background: #f3f3f3; | |
| 165 | +} | |
| 166 | + | |
| 167 | +.scroll { | |
| 168 | + flex: 1; | |
| 169 | + padding: 8rpx 0 144rpx; | |
| 170 | +} | |
| 171 | + | |
| 172 | +.title-header { | |
| 173 | + display: flex; | |
| 174 | + align-items: center; | |
| 175 | + padding: 24rpx 32rpx 12rpx; | |
| 176 | + background-color: #ffffff; | |
| 177 | + | |
| 178 | + .title-header_icon { | |
| 179 | + width: 36rpx; | |
| 180 | + height: 36rpx; | |
| 181 | + margin-right: 12rpx; | |
| 182 | + } | |
| 183 | + | |
| 184 | + span { | |
| 185 | + font-size: 34rpx; | |
| 186 | + font-weight: 600; | |
| 187 | + color: rgba(0, 0, 0, 0.9); | |
| 188 | + } | |
| 189 | +} | |
| 190 | + | |
| 191 | +.section { | |
| 192 | + padding: 32rpx; | |
| 193 | + background: #fff; | |
| 194 | + margin-bottom: 20rpx; | |
| 195 | +} | |
| 196 | + | |
| 197 | +.row { | |
| 198 | + display: flex; | |
| 199 | + margin-bottom: 28rpx; | |
| 200 | + | |
| 201 | + &:last-child { | |
| 202 | + margin-bottom: 0; | |
| 203 | + } | |
| 204 | + | |
| 205 | + &.company { | |
| 206 | + font-size: 36rpx; | |
| 207 | + font-weight: 600; | |
| 208 | + color: rgba(0, 0, 0, 0.9); | |
| 209 | + padding-top: 8rpx; | |
| 210 | + line-height: 50rpx; | |
| 211 | + } | |
| 212 | + | |
| 213 | + .label { | |
| 214 | + max-width: 420rpx; | |
| 215 | + margin-right: 20rpx; | |
| 216 | + line-height: 32rpx; | |
| 217 | + font-size: 28rpx; | |
| 218 | + color: rgba(0, 0, 0, 0.6); | |
| 219 | + } | |
| 220 | + | |
| 221 | + .value { | |
| 222 | + flex: 1; | |
| 223 | + line-height: 32rpx; | |
| 224 | + font-size: 28rpx; | |
| 225 | + color: rgba(0, 0, 0, 0.9); | |
| 226 | + text-align: right; | |
| 227 | + word-break: break-all; | |
| 228 | + } | |
| 229 | +} | |
| 230 | + | |
| 231 | +.material-group { | |
| 232 | + margin-top: 32rpx; | |
| 233 | + | |
| 234 | + &:first-child { | |
| 235 | + margin-top: 0; | |
| 236 | + } | |
| 237 | +} | |
| 238 | + | |
| 239 | +.material-label { | |
| 240 | + display: block; | |
| 241 | + font-size: 28rpx; | |
| 242 | + color: rgba(0, 0, 0, 0.6); | |
| 243 | + margin-bottom: 20rpx; | |
| 244 | +} | |
| 245 | + | |
| 246 | +.tag-list { | |
| 247 | + display: flex; | |
| 248 | + flex-wrap: wrap; | |
| 249 | + gap: 16rpx; | |
| 250 | +} | |
| 251 | + | |
| 252 | +.tag-btn { | |
| 253 | + padding: 12rpx 24rpx; | |
| 254 | + border: 1rpx solid #ddd; | |
| 255 | + border-radius: 8rpx; | |
| 256 | + font-size: 28rpx; | |
| 257 | + color: #999; | |
| 258 | + background: #f5f5f5; | |
| 259 | +} | |
| 260 | + | |
| 261 | +.tag-btn.active { | |
| 262 | + background: $theme-primary; | |
| 263 | + border-color: $theme-primary; | |
| 264 | + color: #fff; | |
| 265 | +} | |
| 266 | + | |
| 267 | +.remark-text { | |
| 268 | + font-size: 28rpx; | |
| 269 | + color: rgba(0, 0, 0, 0.9); | |
| 270 | + line-height: 1.6; | |
| 271 | + white-space: pre-wrap; | |
| 272 | +} | |
| 273 | +</style> | ... | ... |
pages/supplier-price/add.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <!-- 项目名称 --> | |
| 6 | + <uni-list-item class="select-item" :class="form.name ? '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 | + <uni-easyinput v-model="form.name" :inputBorder="false" placeholder="请输入项目名称" /> | |
| 12 | + </template> | |
| 13 | + </uni-list-item> | |
| 14 | + | |
| 15 | + <!-- 品种 --> | |
| 16 | + <uni-list-item class="select-item" :class="form.productId ? 'is-filled' : 'is-empty'" clickable | |
| 17 | + @click="openProductSheet" :rightText="form.productName || '请选择品种'" showArrow> | |
| 18 | + <template v-slot:body> | |
| 19 | + <view class="item-title"><text class="required">*</text><text>品种</text></view> | |
| 20 | + </template> | |
| 21 | + </uni-list-item> | |
| 22 | + | |
| 23 | + <!-- 数量 --> | |
| 24 | + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'"> | |
| 25 | + <template v-slot:body> | |
| 26 | + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view> | |
| 27 | + </template> | |
| 28 | + <template v-slot:footer> | |
| 29 | + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" /> | |
| 30 | + </template> | |
| 31 | + </uni-list-item> | |
| 32 | + | |
| 33 | + <!-- 开始时间 --> | |
| 34 | + <uni-list-item class="select-item" :class="form.startTime ? 'is-filled' : 'is-empty'"> | |
| 35 | + <template v-slot:body> | |
| 36 | + <view class="item-title"><text class="required">*</text><text>开始时间</text></view> | |
| 37 | + </template> | |
| 38 | + <template v-slot:footer> | |
| 39 | + <uni-datetime-picker type="datetime" v-model="form.startTime" :hide-second="true" :clear-icon="false" /> | |
| 40 | + </template> | |
| 41 | + </uni-list-item> | |
| 42 | + | |
| 43 | + <!-- 截止时间 --> | |
| 44 | + <uni-list-item class="select-item" :class="form.endTime ? 'is-filled' : 'is-empty'"> | |
| 45 | + <template v-slot:body> | |
| 46 | + <view class="item-title"><text class="required">*</text><text>截止时间</text></view> | |
| 47 | + </template> | |
| 48 | + <template v-slot:footer> | |
| 49 | + <uni-datetime-picker type="datetime" v-model="form.endTime" :hide-second="true" :clear-icon="false" /> | |
| 50 | + </template> | |
| 51 | + </uni-list-item> | |
| 52 | + | |
| 53 | + <!-- 项目类型 --> | |
| 54 | + <uni-list-item class="select-item" :class="form.projectType ? 'is-filled' : 'is-empty'" clickable | |
| 55 | + @click="openDictSheet('projectType', 'PROJECT_TYPE', '项目类型')" :rightText="getProjectTypeLabel() || '请选择项目类型'" showArrow> | |
| 56 | + <template v-slot:body> | |
| 57 | + <view class="item-title"><text class="required">*</text><text>项目类型</text></view> | |
| 58 | + </template> | |
| 59 | + </uni-list-item> | |
| 60 | + | |
| 61 | + <!-- 开标时间 - 仅项目类型为集中开标时显示 --> | |
| 62 | + <uni-list-item v-if="isConcentratedBid" class="select-item" :class="form.bidTime ? 'is-filled' : 'is-empty'"> | |
| 63 | + <template v-slot:body> | |
| 64 | + <view class="item-title"><text class="required">*</text><text>开标时间</text></view> | |
| 65 | + </template> | |
| 66 | + <template v-slot:footer> | |
| 67 | + <uni-datetime-picker type="datetime" v-model="form.bidTime" :hide-second="true" :clear-icon="false" /> | |
| 68 | + </template> | |
| 69 | + </uni-list-item> | |
| 70 | + | |
| 71 | + <!-- 采购规则 --> | |
| 72 | + <uni-list-item class="select-item textarea-item" :class="form.purchaseRule ? 'is-filled' : 'is-empty'"> | |
| 73 | + <template v-slot:body> | |
| 74 | + <view class="item-title"><text class="required">*</text><text>采购规则</text></view> | |
| 75 | + </template> | |
| 76 | + <template v-slot:footer> | |
| 77 | + <uni-easyinput type="textarea" v-model="form.purchaseRule" :inputBorder="false" | |
| 78 | + placeholder="请输入采购规则" :autoHeight="true" /> | |
| 79 | + </template> | |
| 80 | + </uni-list-item> | |
| 81 | + </uni-list> | |
| 82 | + </scroll-view> | |
| 83 | + | |
| 84 | + <view class="footer"> | |
| 85 | + <button class="btn submit" @click="onSubmit">提交</button> | |
| 86 | + </view> | |
| 87 | + | |
| 88 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" | |
| 89 | + v-model="sheet.value" @confirm="onSheetConfirm" /> | |
| 90 | + </view> | |
| 91 | +</template> | |
| 92 | + | |
| 93 | +<script> | |
| 94 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 95 | +import { supplierQuotationProjectSaveApi } from '@/api/procure-manage/supplierQuotationProject.js' | |
| 96 | +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 97 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 98 | + | |
| 99 | +export default { | |
| 100 | + name: 'AddSupplierPrice', | |
| 101 | + components: { SingleSelectSheet }, | |
| 102 | + data() { | |
| 103 | + return { | |
| 104 | + form: { | |
| 105 | + name: '', | |
| 106 | + productId: '', | |
| 107 | + productName: '', | |
| 108 | + quantity: '', | |
| 109 | + startTime: '', | |
| 110 | + endTime: '', | |
| 111 | + projectType: '', | |
| 112 | + bidTime: '', | |
| 113 | + purchaseRule: '' | |
| 114 | + }, | |
| 115 | + productList: [], | |
| 116 | + dictData: {}, | |
| 117 | + sheet: { | |
| 118 | + visible: false, | |
| 119 | + title: '', | |
| 120 | + options: [], | |
| 121 | + value: '', | |
| 122 | + field: '', | |
| 123 | + isDict: false | |
| 124 | + } | |
| 125 | + } | |
| 126 | + }, | |
| 127 | + computed: { | |
| 128 | + isConcentratedBid() { | |
| 129 | + // 项目类型为"集中开标"时显示开标时间 | |
| 130 | + const opts = this.getDictOptions('PROJECT_TYPE') || [] | |
| 131 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 132 | + const label = m ? (m.name || m.label || '') : '' | |
| 133 | + return label.indexOf('集中开标') > -1 | |
| 134 | + } | |
| 135 | + }, | |
| 136 | + onLoad() { | |
| 137 | + this.loadDictData() | |
| 138 | + this.loadProducts() | |
| 139 | + }, | |
| 140 | + methods: { | |
| 141 | + async loadDictData() { | |
| 142 | + try { | |
| 143 | + const res = await getDicByCodes(['PROJECT_TYPE']) | |
| 144 | + this.dictData = res || {} | |
| 145 | + } catch (e) { | |
| 146 | + this.dictData = {} | |
| 147 | + } | |
| 148 | + }, | |
| 149 | + getDictOptions(dictCode) { | |
| 150 | + const data = this.dictData[dictCode] | |
| 151 | + return (data && data.data) ? data.data : (Array.isArray(data) ? data : []) | |
| 152 | + }, | |
| 153 | + async loadProducts() { | |
| 154 | + try { | |
| 155 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 156 | + const _data = res && res.data ? res.data : {} | |
| 157 | + const list = _data.datas || _data.list || _data.records || [] | |
| 158 | + this.productList = list.map(it => ({ | |
| 159 | + label: it.productName || it.name || '', | |
| 160 | + value: it.productId || it.id || '' | |
| 161 | + })) | |
| 162 | + } catch (e) { | |
| 163 | + this.productList = [] | |
| 164 | + } | |
| 165 | + }, | |
| 166 | + getProjectTypeLabel() { | |
| 167 | + const opts = this.getDictOptions('PROJECT_TYPE') || [] | |
| 168 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 169 | + return m ? (m.name || m.label || '') : '' | |
| 170 | + }, | |
| 171 | + openProductSheet() { | |
| 172 | + const opts = (this.productList || []).map(p => ({ label: p.label, value: p.value })) | |
| 173 | + const cur = this.form.productId | |
| 174 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 175 | + this.sheet = { | |
| 176 | + visible: true, | |
| 177 | + title: '请选择品种', | |
| 178 | + options: opts, | |
| 179 | + value: match ? match.value : '', | |
| 180 | + field: 'productId', | |
| 181 | + isDict: false | |
| 182 | + } | |
| 183 | + }, | |
| 184 | + openDictSheet(field, dictCode, title) { | |
| 185 | + const raw = this.getDictOptions(dictCode) || [] | |
| 186 | + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') })) | |
| 187 | + const cur = this.form[field] | |
| 188 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 189 | + this.sheet = { | |
| 190 | + visible: true, | |
| 191 | + title: title || '请选择', | |
| 192 | + options: opts, | |
| 193 | + value: match ? match.value : '', | |
| 194 | + field, | |
| 195 | + isDict: true | |
| 196 | + } | |
| 197 | + }, | |
| 198 | + onSheetConfirm({ value }) { | |
| 199 | + const field = this.sheet.field | |
| 200 | + if (!field) return | |
| 201 | + this.form[field] = value | |
| 202 | + if (field === 'productId') { | |
| 203 | + const m = (this.productList || []).find(p => String(p.value) === String(value)) | |
| 204 | + this.form.productName = m ? m.label : '' | |
| 205 | + } | |
| 206 | + // 切换项目类型时,若不再是集中开标则清空开标时间 | |
| 207 | + if (field === 'projectType' && !this.isConcentratedBid) { | |
| 208 | + this.form.bidTime = '' | |
| 209 | + } | |
| 210 | + }, | |
| 211 | + onQuantityBlur() { | |
| 212 | + let v = this.form.quantity | |
| 213 | + if (v === '' || v == null) return | |
| 214 | + v = parseFloat(v) | |
| 215 | + if (isNaN(v)) { | |
| 216 | + this.form.quantity = '' | |
| 217 | + return | |
| 218 | + } | |
| 219 | + this.form.quantity = String(v) | |
| 220 | + }, | |
| 221 | + formatDateTimeMinute(v) { | |
| 222 | + const s = v == null ? '' : String(v).trim() | |
| 223 | + if (!s) return '' | |
| 224 | + if (s.length >= 16) return s.slice(0, 16) | |
| 225 | + return s | |
| 226 | + }, | |
| 227 | + formatDateTimeSecond(v) { | |
| 228 | + const s = v == null ? '' : String(v).trim() | |
| 229 | + if (!s) return '' | |
| 230 | + // 截取到分钟,补齐秒 | |
| 231 | + const minute = s.length >= 16 ? s.slice(0, 16) : s | |
| 232 | + return minute + ':00' | |
| 233 | + }, | |
| 234 | + validateRequired() { | |
| 235 | + const checks = [ | |
| 236 | + { key: 'name', msg: '请输入项目名称' }, | |
| 237 | + { key: 'productId', msg: '请选择品种' }, | |
| 238 | + { key: 'quantity', msg: '请输入数量' }, | |
| 239 | + { key: 'startTime', msg: '请选择开始时间' }, | |
| 240 | + { key: 'endTime', msg: '请选择截止时间' }, | |
| 241 | + { key: 'projectType', msg: '请选择项目类型' }, | |
| 242 | + { key: 'purchaseRule', msg: '请输入采购规则' } | |
| 243 | + ] | |
| 244 | + for (const it of checks) { | |
| 245 | + const val = this.form[it.key] | |
| 246 | + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) | |
| 247 | + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false } | |
| 248 | + } | |
| 249 | + if (this.isConcentratedBid && !this.form.bidTime) { | |
| 250 | + uni.showToast({ title: '请选择开标时间', icon: 'none' }) | |
| 251 | + return false | |
| 252 | + } | |
| 253 | + // 数量必须为有效数字 | |
| 254 | + if (isNaN(parseFloat(this.form.quantity))) { | |
| 255 | + uni.showToast({ title: '数量格式不正确', icon: 'none' }) | |
| 256 | + return false | |
| 257 | + } | |
| 258 | + // 开始时间不能晚于截止时间 | |
| 259 | + if (this.form.startTime && this.form.endTime && this.formatDateTimeMinute(this.form.startTime) > this.formatDateTimeMinute(this.form.endTime)) { | |
| 260 | + uni.showToast({ title: '开始时间不能晚于截止时间', icon: 'none' }) | |
| 261 | + return false | |
| 262 | + } | |
| 263 | + return true | |
| 264 | + }, | |
| 265 | + async onSubmit() { | |
| 266 | + if (!this.validateRequired()) return | |
| 267 | + const confirmRes = await new Promise(resolve => { | |
| 268 | + uni.showModal({ title: '提示', content: '确定新增报价项目吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 269 | + }) | |
| 270 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 271 | + const clean = (obj) => { | |
| 272 | + const out = {} | |
| 273 | + Object.keys(obj || {}).forEach(k => { | |
| 274 | + const v = obj[k] | |
| 275 | + const isEmptyString = typeof v === 'string' && v.trim() === '' | |
| 276 | + const isUndef = v === undefined || v === null | |
| 277 | + const isNaNNumber = typeof v === 'number' && isNaN(v) | |
| 278 | + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v | |
| 279 | + }) | |
| 280 | + return out | |
| 281 | + } | |
| 282 | + const payload = clean({ | |
| 283 | + name: this.form.name, | |
| 284 | + productId: this.form.productId, | |
| 285 | + productName: this.form.productName, | |
| 286 | + quantity: this.form.quantity, | |
| 287 | + startTime: this.formatDateTimeSecond(this.form.startTime), | |
| 288 | + endTime: this.formatDateTimeSecond(this.form.endTime), | |
| 289 | + projectType: this.form.projectType, | |
| 290 | + bidTime: this.isConcentratedBid ? this.formatDateTimeSecond(this.form.bidTime) : '', | |
| 291 | + purchaseRule: this.form.purchaseRule | |
| 292 | + }) | |
| 293 | + try { | |
| 294 | + await supplierQuotationProjectSaveApi(payload) | |
| 295 | + uni.showToast({ title: '新增成功', icon: 'none' }) | |
| 296 | + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 297 | + setTimeout(() => { uni.redirectTo({ url: '/pages/supplier-price/index' }) }, 400) | |
| 298 | + } catch (e) { | |
| 299 | + uni.showToast({ title: e.msg || '新增失败', icon: 'none' }) | |
| 300 | + } | |
| 301 | + } | |
| 302 | + } | |
| 303 | +} | |
| 304 | +</script> | |
| 305 | + | |
| 306 | +<style lang="scss" scoped> | |
| 307 | +.page { | |
| 308 | + display: flex; | |
| 309 | + flex-direction: column; | |
| 310 | + height: 100%; | |
| 311 | +} | |
| 312 | + | |
| 313 | +.scroll { | |
| 314 | + flex: 1; | |
| 315 | + padding: 12rpx 0 200rpx !important; | |
| 316 | +} | |
| 317 | + | |
| 318 | +.footer { | |
| 319 | + z-index: 2; | |
| 320 | + position: fixed; | |
| 321 | + left: 0; | |
| 322 | + right: 0; | |
| 323 | + bottom: 0; | |
| 324 | + padding: 32rpx; | |
| 325 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 326 | + background: #fff; | |
| 327 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 328 | + | |
| 329 | + .btn { | |
| 330 | + height: 80rpx; | |
| 331 | + line-height: 80rpx; | |
| 332 | + border-radius: 12rpx; | |
| 333 | + font-size: 32rpx; | |
| 334 | + } | |
| 335 | + | |
| 336 | + .submit { | |
| 337 | + background: $theme-primary; | |
| 338 | + color: #fff; | |
| 339 | + } | |
| 340 | +} | |
| 341 | + | |
| 342 | +::v-deep .uni-list { | |
| 343 | + .uni-easyinput { | |
| 344 | + display: flex; | |
| 345 | + | |
| 346 | + .uni-input-input { | |
| 347 | + color: rgba(0, 0, 0, 0.9); | |
| 348 | + } | |
| 349 | + } | |
| 350 | + | |
| 351 | + .uni-input-placeholder { | |
| 352 | + z-index: 1; | |
| 353 | + } | |
| 354 | + | |
| 355 | + .uni-input-input { | |
| 356 | + background-color: #ffffff; | |
| 357 | + } | |
| 358 | + | |
| 359 | + background: transparent; | |
| 360 | + | |
| 361 | + &-item { | |
| 362 | + &__extra-text { | |
| 363 | + font-size: 32rpx; | |
| 364 | + } | |
| 365 | + | |
| 366 | + &__content-title { | |
| 367 | + font-size: 32rpx; | |
| 368 | + color: rgba(0, 0, 0, 0.9); | |
| 369 | + } | |
| 370 | + | |
| 371 | + &__container { | |
| 372 | + padding: 32rpx; | |
| 373 | + | |
| 374 | + .uni-easyinput { | |
| 375 | + &__placeholder-class { | |
| 376 | + font-size: 32rpx; | |
| 377 | + color: rgba(0, 0, 0, 0.4); | |
| 378 | + } | |
| 379 | + | |
| 380 | + &__content { | |
| 381 | + border: none; | |
| 382 | + background-color: #ffffff !important; | |
| 383 | + | |
| 384 | + &-input { | |
| 385 | + padding-left: 0 !important; | |
| 386 | + height: 48rpx; | |
| 387 | + line-height: 48rpx; | |
| 388 | + font-size: 32rpx; | |
| 389 | + } | |
| 390 | + | |
| 391 | + .content-clear-icon { | |
| 392 | + font-size: 44rpx !important; | |
| 393 | + } | |
| 394 | + } | |
| 395 | + } | |
| 396 | + | |
| 397 | + .item-title, | |
| 398 | + .uni-list-item__content { | |
| 399 | + flex: none; | |
| 400 | + min-height: 48rpx; | |
| 401 | + line-height: 48rpx; | |
| 402 | + font-size: 32rpx; | |
| 403 | + position: relative; | |
| 404 | + width: 240rpx; | |
| 405 | + margin-right: 32rpx; | |
| 406 | + color: rgba(0, 0, 0, 0.9); | |
| 407 | + | |
| 408 | + .required { | |
| 409 | + color: red; | |
| 410 | + position: absolute; | |
| 411 | + top: 50%; | |
| 412 | + transform: translateY(-50%); | |
| 413 | + left: -16rpx; | |
| 414 | + } | |
| 415 | + } | |
| 416 | + } | |
| 417 | + | |
| 418 | + &.select-item { | |
| 419 | + &.is-empty { | |
| 420 | + .uni-list-item__extra-text { | |
| 421 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 422 | + } | |
| 423 | + } | |
| 424 | + | |
| 425 | + &.is-filled { | |
| 426 | + .uni-list-item__extra-text { | |
| 427 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 428 | + } | |
| 429 | + } | |
| 430 | + } | |
| 431 | + } | |
| 432 | +} | |
| 433 | + | |
| 434 | +::v-deep .uni-datetime-picker { | |
| 435 | + flex: 1; | |
| 436 | + | |
| 437 | + .uni-input-wrapper { | |
| 438 | + justify-content: flex-end; | |
| 439 | + } | |
| 440 | + | |
| 441 | + .uni-input-input { | |
| 442 | + text-align: right; | |
| 443 | + font-size: 32rpx; | |
| 444 | + color: rgba(0, 0, 0, 0.9); | |
| 445 | + } | |
| 446 | +} | |
| 447 | + | |
| 448 | +.textarea-item { | |
| 449 | + ::v-deep .uni-list-item__container { | |
| 450 | + flex-direction: column; | |
| 451 | + align-items: stretch; | |
| 452 | + | |
| 453 | + .item-title { | |
| 454 | + width: auto; | |
| 455 | + margin-right: 0; | |
| 456 | + margin-bottom: 16rpx; | |
| 457 | + } | |
| 458 | + | |
| 459 | + .uni-easyinput { | |
| 460 | + width: 100%; | |
| 461 | + } | |
| 462 | + | |
| 463 | + .uni-textarea-textarea { | |
| 464 | + text-align: left; | |
| 465 | + min-height: 120rpx; | |
| 466 | + font-size: 32rpx; | |
| 467 | + color: rgba(0, 0, 0, 0.9); | |
| 468 | + } | |
| 469 | + } | |
| 470 | +} | |
| 471 | +</style> | ... | ... |
pages/supplier-price/detail.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <!-- 基础信息 --> | |
| 5 | + <view class="section"> | |
| 6 | + <text class="row company">{{ safeText(form.name) }}</text> | |
| 7 | + <view class="row"><text class="label">编号</text><text class="value">{{ safeText(form.code) }}</text></view> | |
| 8 | + <view class="row"><text class="label">品种</text><text class="value">{{ safeText(form.productName) }}</text></view> | |
| 9 | + <view class="row"><text class="label">数量(吨)</text><text class="value">{{ safeText(form.quantity) }}</text></view> | |
| 10 | + <view class="row"><text class="label">开始时间</text><text class="value">{{ formatDateTimeMinute(form.startTime) }}</text></view> | |
| 11 | + <view class="row"><text class="label">截止时间</text><text class="value">{{ formatDateTimeMinute(form.endTime) }}</text></view> | |
| 12 | + <view class="row"><text class="label">项目类型</text><text class="value">{{ getProjectTypeLabel() }}</text></view> | |
| 13 | + <view v-if="showBidTime" class="row"><text class="label">开标时间</text><text class="value">{{ formatDateTimeMinute(form.bidTime) }}</text></view> | |
| 14 | + <view class="row"><text class="label">采购规则</text><text class="value">{{ safeText(form.purchaseRule) }}</text></view> | |
| 15 | + <view class="row"><text class="label">状态</text><text class="value">{{ safeText(form.status) }}</text></view> | |
| 16 | + <view class="row"><text class="label">发布人</text><text class="value">{{ safeText(form.createBy) }}</text></view> | |
| 17 | + </view> | |
| 18 | + </scroll-view> | |
| 19 | + | |
| 20 | + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" /> | |
| 21 | + </view> | |
| 22 | +</template> | |
| 23 | + | |
| 24 | +<script> | |
| 25 | +import DetailButtons from '@/components/detail-buttons/index.vue' | |
| 26 | +import { supplierQuotationProjectDetailApi, supplierQuotationProjectUpdateStatusApi } from '@/api/procure-manage/supplierQuotationProject.js' | |
| 27 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 28 | + | |
| 29 | +export default { | |
| 30 | + name: 'DetailSupplierPrice', | |
| 31 | + components: { DetailButtons }, | |
| 32 | + data() { | |
| 33 | + return { | |
| 34 | + id: '', | |
| 35 | + form: {}, | |
| 36 | + dictData: {}, | |
| 37 | + buttons: [{ | |
| 38 | + text: '编辑', | |
| 39 | + visible: true, | |
| 40 | + variant: 'primary', | |
| 41 | + event: 'edit' | |
| 42 | + },{ | |
| 43 | + text: '报价', | |
| 44 | + visible: true, | |
| 45 | + event: 'bid' | |
| 46 | + },{ | |
| 47 | + text: '发布', | |
| 48 | + visible: true, | |
| 49 | + event: 'publish' | |
| 50 | + }] | |
| 51 | + } | |
| 52 | + }, | |
| 53 | + computed: { | |
| 54 | + showBidTime() { | |
| 55 | + const opts = (this.dictData.PROJECT_TYPE && this.dictData.PROJECT_TYPE.data) || [] | |
| 56 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 57 | + const label = m ? (m.name || m.label || '') : '' | |
| 58 | + return label.indexOf('集中开标') > -1 | |
| 59 | + }, | |
| 60 | + displayButtons() { | |
| 61 | + const s = this.form.status || '' | |
| 62 | + return [ | |
| 63 | + { ...this.buttons[0], visible: this.$auth.hasPermi('procure-manage:supplier-price:modify') && s === 'UNPUBLISHED' }, | |
| 64 | + { ...this.buttons[1], visible: this.$auth.hasPermi('procure-manage:supplier-price:quotation') && s === 'QUOTING' }, | |
| 65 | + { ...this.buttons[2], visible: this.$auth.hasPermi('procure-manage:supplier-price:publish') && s === 'UNPUBLISHED' } | |
| 66 | + ] | |
| 67 | + } | |
| 68 | + }, | |
| 69 | + onLoad(options) { | |
| 70 | + this.id = (options && options.id) ? options.id : '' | |
| 71 | + if (!this.id) return | |
| 72 | + this.loadDictData().then(() => { | |
| 73 | + this.loadDetail() | |
| 74 | + }) | |
| 75 | + }, | |
| 76 | + methods: { | |
| 77 | + async loadDictData() { | |
| 78 | + try { | |
| 79 | + const res = await getDicByCodes(['PROJECT_TYPE']) | |
| 80 | + this.dictData = res || {} | |
| 81 | + } catch (e) { | |
| 82 | + this.dictData = {} | |
| 83 | + } | |
| 84 | + }, | |
| 85 | + getProjectTypeLabel() { | |
| 86 | + const opts = (this.dictData.PROJECT_TYPE && this.dictData.PROJECT_TYPE.data) || [] | |
| 87 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 88 | + return m ? (m.name || m.label || '-') : '-' | |
| 89 | + }, | |
| 90 | + async loadDetail() { | |
| 91 | + try { | |
| 92 | + const res = await supplierQuotationProjectDetailApi({ id: this.id }) | |
| 93 | + this.form = (res && res.data) ? res.data : {} | |
| 94 | + } catch (e) { | |
| 95 | + uni.showToast({ title: '加载详情失败', icon: 'none' }) | |
| 96 | + } | |
| 97 | + }, | |
| 98 | + formatDateTimeMinute(v) { | |
| 99 | + const s = v == null ? '' : String(v).trim() | |
| 100 | + if (!s) return '-' | |
| 101 | + if (s.length >= 16) return s.slice(0, 16) | |
| 102 | + return s | |
| 103 | + }, | |
| 104 | + safeText(v) { | |
| 105 | + const s = v == null ? '' : String(v) | |
| 106 | + return s.trim() ? s : '-' | |
| 107 | + }, | |
| 108 | + handleButtonClick(btn) { | |
| 109 | + if (!btn || btn.disabled) return | |
| 110 | + const e = btn.event || '' | |
| 111 | + if (e === 'edit') return this.onEdit() | |
| 112 | + if (e === 'bid') return this.onQuote() | |
| 113 | + if (e === 'publish') return this.onPublish() | |
| 114 | + }, | |
| 115 | + onEdit() { | |
| 116 | + uni.navigateTo({ url: '/pages/supplier-price/modify?id=' + this.id }) | |
| 117 | + }, | |
| 118 | + onQuote() { | |
| 119 | + uni.navigateTo({ url: '/pages/supplier-price/quote?id=' + this.id }) | |
| 120 | + }, | |
| 121 | + async onPublish() { | |
| 122 | + const confirmRes = await new Promise(resolve => { | |
| 123 | + uni.showModal({ title: '提示', content: '确定发布该项目吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 124 | + }) | |
| 125 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 126 | + try { | |
| 127 | + await supplierQuotationProjectUpdateStatusApi({ id: this.id, status: 'PUBLISHED' }) | |
| 128 | + uni.showToast({ title: '发布成功', icon: 'none' }) | |
| 129 | + this.loadDetail() | |
| 130 | + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 131 | + } catch (e) { | |
| 132 | + uni.showToast({ title: e.msg || '发布失败', icon: 'none' }) | |
| 133 | + } | |
| 134 | + } | |
| 135 | + } | |
| 136 | +} | |
| 137 | +</script> | |
| 138 | + | |
| 139 | +<style lang="scss" scoped> | |
| 140 | +.page { | |
| 141 | + display: flex; | |
| 142 | + flex-direction: column; | |
| 143 | + height: 100vh; | |
| 144 | + background: #f3f3f3; | |
| 145 | +} | |
| 146 | + | |
| 147 | +.scroll { | |
| 148 | + flex: 1; | |
| 149 | + padding: 8rpx 0 144rpx; | |
| 150 | +} | |
| 151 | + | |
| 152 | +.section { | |
| 153 | + padding: 32rpx; | |
| 154 | + background: #fff; | |
| 155 | + margin-bottom: 20rpx; | |
| 156 | +} | |
| 157 | + | |
| 158 | +.row { | |
| 159 | + display: flex; | |
| 160 | + margin-bottom: 28rpx; | |
| 161 | + | |
| 162 | + &:last-child { | |
| 163 | + margin-bottom: 0; | |
| 164 | + } | |
| 165 | + | |
| 166 | + &.company { | |
| 167 | + font-size: 36rpx; | |
| 168 | + font-weight: 600; | |
| 169 | + color: rgba(0, 0, 0, 0.9); | |
| 170 | + padding-top: 8rpx; | |
| 171 | + line-height: 50rpx; | |
| 172 | + } | |
| 173 | + | |
| 174 | + .label { | |
| 175 | + max-width: 420rpx; | |
| 176 | + margin-right: 20rpx; | |
| 177 | + line-height: 32rpx; | |
| 178 | + font-size: 28rpx; | |
| 179 | + color: rgba(0, 0, 0, 0.6); | |
| 180 | + } | |
| 181 | + | |
| 182 | + .value { | |
| 183 | + flex: 1; | |
| 184 | + line-height: 32rpx; | |
| 185 | + font-size: 28rpx; | |
| 186 | + color: rgba(0, 0, 0, 0.9); | |
| 187 | + text-align: right; | |
| 188 | + word-break: break-all; | |
| 189 | + } | |
| 190 | +} | |
| 191 | +</style> | ... | ... |
pages/supplier-price/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <view class="dev-list-fixed"> | |
| 4 | + <view class="search-row"> | |
| 5 | + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入项目名称" clearButton="auto" | |
| 6 | + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search" | |
| 7 | + @input="onSearchInput" /> | |
| 8 | + <view class="tool-icons"> | |
| 9 | + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" /> | |
| 10 | + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" /> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | + </view> | |
| 14 | + | |
| 15 | + <view class="list-box"> | |
| 16 | + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id" | |
| 17 | + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError"> | |
| 18 | + <template v-slot="{ item }"> | |
| 19 | + <view class="card" @click.stop="onCardClick(item)"> | |
| 20 | + <view class="card-header"> | |
| 21 | + <text class="title omit2">{{ item.name || '-' }}</text> | |
| 22 | + </view> | |
| 23 | + <view class="info-row"> | |
| 24 | + <text>项目编号</text><text>{{ item.code || '-' }}</text> | |
| 25 | + </view> | |
| 26 | + <view class="info-row"> | |
| 27 | + <text>品种</text><text>{{ item.productName || '-' }}</text> | |
| 28 | + </view> | |
| 29 | + <view class="info-row"> | |
| 30 | + <text>开始时间</text><text>{{ formatDateTimeMinute(item.startTime) }}</text> | |
| 31 | + </view> | |
| 32 | + <view class="info-row"> | |
| 33 | + <text>截止时间</text><text>{{ formatDateTimeMinute(item.endTime) }}</text> | |
| 34 | + </view> | |
| 35 | + </view> | |
| 36 | + </template> | |
| 37 | + </card-list> | |
| 38 | + </view> | |
| 39 | + | |
| 40 | + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset" | |
| 41 | + @confirm="onFilterConfirm"> | |
| 42 | + <template v-slot="{ model }"> | |
| 43 | + <view class="filter-form"> | |
| 44 | + <view class="form-item"> | |
| 45 | + <view class="label">开始时间</view> | |
| 46 | + <uni-datetime-picker type="datetimerange" v-model="model.startTimeRange" start="2023-01-01" /> | |
| 47 | + </view> | |
| 48 | + <view class="form-item"> | |
| 49 | + <view class="label">截止时间</view> | |
| 50 | + <uni-datetime-picker type="datetimerange" v-model="model.endTimeRange" start="2023-01-01" /> | |
| 51 | + </view> | |
| 52 | + <view class="form-item"> | |
| 53 | + <view class="label">开标时间</view> | |
| 54 | + <uni-datetime-picker type="datetimerange" v-model="model.bidTimeRange" start="2023-01-01" /> | |
| 55 | + </view> | |
| 56 | + </view> | |
| 57 | + </template> | |
| 58 | + </filter-modal> | |
| 59 | + </view> | |
| 60 | +</template> | |
| 61 | + | |
| 62 | +<script> | |
| 63 | +import CardList from '@/components/card/index.vue' | |
| 64 | +import FilterModal from '@/components/filter/index.vue' | |
| 65 | +import { supplierQuotationProjectQueryApi } from '@/api/procure-manage/supplierQuotationProject.js' | |
| 66 | + | |
| 67 | +export default { | |
| 68 | + name: 'SupplierPriceList', | |
| 69 | + components: { CardList, FilterModal }, | |
| 70 | + data() { | |
| 71 | + return { | |
| 72 | + searchKeyword: '', | |
| 73 | + searchKeywordDebounced: '', | |
| 74 | + searchDebounceTimer: null, | |
| 75 | + query: { | |
| 76 | + name: '', | |
| 77 | + startTimeRange: [], | |
| 78 | + endTimeRange: [], | |
| 79 | + bidTimeRange: [] | |
| 80 | + }, | |
| 81 | + extraParams: {}, | |
| 82 | + currentItems: [], | |
| 83 | + filterVisible: false, | |
| 84 | + filterForm: { | |
| 85 | + startTimeRange: [], | |
| 86 | + endTimeRange: [], | |
| 87 | + bidTimeRange: [] | |
| 88 | + } | |
| 89 | + } | |
| 90 | + }, | |
| 91 | + watch: { | |
| 92 | + searchKeywordDebounced(v) { | |
| 93 | + this.query = { ...this.query, name: v || '' } | |
| 94 | + } | |
| 95 | + }, | |
| 96 | + onShow() { | |
| 97 | + let needRefresh = '' | |
| 98 | + try { needRefresh = uni.getStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH') } catch (e) {} | |
| 99 | + if (!needRefresh) return | |
| 100 | + try { uni.removeStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH') } catch (e) {} | |
| 101 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) { | |
| 102 | + this.$refs.cardRef.reload() | |
| 103 | + } | |
| 104 | + }, | |
| 105 | + onReachBottom() { | |
| 106 | + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) { | |
| 107 | + this.$refs.cardRef.onLoadMore() | |
| 108 | + } | |
| 109 | + }, | |
| 110 | + beforeDestroy() { | |
| 111 | + if (this.searchDebounceTimer) { | |
| 112 | + clearTimeout(this.searchDebounceTimer) | |
| 113 | + this.searchDebounceTimer = null | |
| 114 | + } | |
| 115 | + }, | |
| 116 | + methods: { | |
| 117 | + formatDateTimeMinute(v) { | |
| 118 | + const s = v == null ? '' : String(v).trim() | |
| 119 | + if (!s) return '-' | |
| 120 | + // 截取到分钟 (yyyy-MM-dd HH:mm) | |
| 121 | + if (s.length >= 16) return s.slice(0, 16) | |
| 122 | + if (s.length >= 10) return s.slice(0, 10) | |
| 123 | + return s | |
| 124 | + }, | |
| 125 | + onCardLoaded({ items }) { | |
| 126 | + this.currentItems = items | |
| 127 | + }, | |
| 128 | + onCardError() { | |
| 129 | + uni.showToast({ title: '列表加载失败', icon: 'none' }) | |
| 130 | + }, | |
| 131 | + onSearchInput() { | |
| 132 | + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer) | |
| 133 | + this.searchDebounceTimer = setTimeout(() => { | |
| 134 | + this.searchKeywordDebounced = this.searchKeyword | |
| 135 | + this.searchDebounceTimer = null | |
| 136 | + }, 1200) | |
| 137 | + }, | |
| 138 | + search(e) { | |
| 139 | + const val = e && e.value != null ? e.value : this.searchKeyword | |
| 140 | + this.searchKeyword = val | |
| 141 | + this.searchKeywordDebounced = val | |
| 142 | + }, | |
| 143 | + openFilter() { | |
| 144 | + this.filterForm = { | |
| 145 | + startTimeRange: Array.isArray(this.query.startTimeRange) ? this.query.startTimeRange.slice(0) : [], | |
| 146 | + endTimeRange: Array.isArray(this.query.endTimeRange) ? this.query.endTimeRange.slice(0) : [], | |
| 147 | + bidTimeRange: Array.isArray(this.query.bidTimeRange) ? this.query.bidTimeRange.slice(0) : [] | |
| 148 | + } | |
| 149 | + this.filterVisible = true | |
| 150 | + }, | |
| 151 | + onFilterReset(payload) { | |
| 152 | + this.filterForm = payload | |
| 153 | + }, | |
| 154 | + onFilterConfirm(payload) { | |
| 155 | + this.query = { | |
| 156 | + name: this.query.name || '', | |
| 157 | + startTimeRange: Array.isArray(payload.startTimeRange) ? payload.startTimeRange : [], | |
| 158 | + endTimeRange: Array.isArray(payload.endTimeRange) ? payload.endTimeRange : [], | |
| 159 | + bidTimeRange: Array.isArray(payload.bidTimeRange) ? payload.bidTimeRange : [] | |
| 160 | + } | |
| 161 | + }, | |
| 162 | + fetchList({ pageIndex, pageSize, query }) { | |
| 163 | + const q = query || {} | |
| 164 | + const sRange = Array.isArray(q.startTimeRange) ? q.startTimeRange : [] | |
| 165 | + const eRange = Array.isArray(q.endTimeRange) ? q.endTimeRange : [] | |
| 166 | + const bRange = Array.isArray(q.bidTimeRange) ? q.bidTimeRange : [] | |
| 167 | + const params = { | |
| 168 | + pageIndex, | |
| 169 | + pageSize, | |
| 170 | + name: q.name || '', | |
| 171 | + startTimeStart: (sRange && sRange.length === 2) ? sRange[0] : '', | |
| 172 | + startTimeEnd: (sRange && sRange.length === 2) ? sRange[1] : '', | |
| 173 | + endTimeStart: (eRange && eRange.length === 2) ? eRange[0] : '', | |
| 174 | + endTimeEnd: (eRange && eRange.length === 2) ? eRange[1] : '', | |
| 175 | + bidTimeStart: (bRange && bRange.length === 2) ? bRange[0] : '', | |
| 176 | + bidTimeEnd: (bRange && bRange.length === 2) ? bRange[1] : '' | |
| 177 | + } | |
| 178 | + return supplierQuotationProjectQueryApi(params).then(res => { | |
| 179 | + const _data = res && res.data ? res.data : {} | |
| 180 | + let records = _data.datas || _data.list || _data.records || [] | |
| 181 | + const totalCount = _data.totalCount || _data.count || 0 | |
| 182 | + const hasNext = _data.hasNext || false | |
| 183 | + records = records.map(it => ({ ...it })) | |
| 184 | + return { records, totalCount, hasNext } | |
| 185 | + }).catch(() => { | |
| 186 | + this.onCardError() | |
| 187 | + return { records: [], totalCount: 0, hasNext: false } | |
| 188 | + }) | |
| 189 | + }, | |
| 190 | + onCardClick(item) { | |
| 191 | + uni.navigateTo({ url: '/pages/supplier-price/detail?id=' + item.id }) | |
| 192 | + }, | |
| 193 | + onAdd() { | |
| 194 | + uni.navigateTo({ url: '/pages/supplier-price/add' }) | |
| 195 | + } | |
| 196 | + } | |
| 197 | +} | |
| 198 | +</script> | |
| 199 | + | |
| 200 | +<style lang="scss" scoped> | |
| 201 | +.page { | |
| 202 | + display: flex; | |
| 203 | + flex-direction: column; | |
| 204 | + height: 100vh; | |
| 205 | +} | |
| 206 | + | |
| 207 | +.dev-list-fixed { | |
| 208 | + position: fixed; | |
| 209 | + top: 96rpx; | |
| 210 | + left: 0; | |
| 211 | + right: 0; | |
| 212 | + z-index: 2; | |
| 213 | + background: #fff; | |
| 214 | + | |
| 215 | + .search-row { | |
| 216 | + display: flex; | |
| 217 | + align-items: center; | |
| 218 | + padding: 16rpx 32rpx; | |
| 219 | + | |
| 220 | + .uni-searchbar { | |
| 221 | + padding: 0; | |
| 222 | + flex: 1; | |
| 223 | + } | |
| 224 | + | |
| 225 | + .tool-icons { | |
| 226 | + display: flex; | |
| 227 | + | |
| 228 | + .tool-icon { | |
| 229 | + width: 48rpx; | |
| 230 | + height: 48rpx; | |
| 231 | + display: block; | |
| 232 | + margin-left: 32rpx; | |
| 233 | + } | |
| 234 | + } | |
| 235 | + } | |
| 236 | +} | |
| 237 | + | |
| 238 | +::v-deep .uni-searchbar__box { | |
| 239 | + height: 80rpx !important; | |
| 240 | + justify-content: start; | |
| 241 | + | |
| 242 | + .uni-searchbar__box-search-input { | |
| 243 | + font-size: 32rpx !important; | |
| 244 | + } | |
| 245 | +} | |
| 246 | + | |
| 247 | +.list-box { | |
| 248 | + flex: 1; | |
| 249 | + padding-top: 140rpx; | |
| 250 | + | |
| 251 | + .card { | |
| 252 | + position: relative; | |
| 253 | + } | |
| 254 | + | |
| 255 | + .card-header { | |
| 256 | + margin-bottom: 28rpx; | |
| 257 | + position: relative; | |
| 258 | + | |
| 259 | + .title { | |
| 260 | + font-size: 36rpx; | |
| 261 | + font-weight: 600; | |
| 262 | + line-height: 50rpx; | |
| 263 | + color: rgba(0, 0, 0, 0.9); | |
| 264 | + width: 578rpx; | |
| 265 | + } | |
| 266 | + } | |
| 267 | + | |
| 268 | + .info-row { | |
| 269 | + display: flex; | |
| 270 | + align-items: center; | |
| 271 | + color: rgba(0, 0, 0, 0.6); | |
| 272 | + font-size: 28rpx; | |
| 273 | + margin-bottom: 24rpx; | |
| 274 | + height: 32rpx; | |
| 275 | + | |
| 276 | + &:last-child { | |
| 277 | + margin-bottom: 0; | |
| 278 | + } | |
| 279 | + | |
| 280 | + text { | |
| 281 | + width: 50%; | |
| 282 | + | |
| 283 | + &:last-child { | |
| 284 | + color: rgba(0, 0, 0, 0.9); | |
| 285 | + width: 50%; | |
| 286 | + } | |
| 287 | + } | |
| 288 | + } | |
| 289 | +} | |
| 290 | + | |
| 291 | +.filter-form { | |
| 292 | + .form-item { | |
| 293 | + margin-bottom: 24rpx; | |
| 294 | + } | |
| 295 | + | |
| 296 | + .label { | |
| 297 | + margin-bottom: 20rpx; | |
| 298 | + color: rgba(0, 0, 0, 0.9); | |
| 299 | + height: 44rpx; | |
| 300 | + line-height: 44rpx; | |
| 301 | + font-size: 30rpx; | |
| 302 | + } | |
| 303 | +} | |
| 304 | +</style> | ... | ... |
pages/supplier-price/modify.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <!-- 项目名称 --> | |
| 6 | + <uni-list-item class="select-item" :class="form.name ? '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 | + <uni-easyinput v-model="form.name" :inputBorder="false" placeholder="请输入项目名称" /> | |
| 12 | + </template> | |
| 13 | + </uni-list-item> | |
| 14 | + | |
| 15 | + <!-- 品种 --> | |
| 16 | + <uni-list-item class="select-item" :class="form.productId ? 'is-filled' : 'is-empty'" clickable | |
| 17 | + @click="openProductSheet" :rightText="form.productName || '请选择品种'" showArrow> | |
| 18 | + <template v-slot:body> | |
| 19 | + <view class="item-title"><text class="required">*</text><text>品种</text></view> | |
| 20 | + </template> | |
| 21 | + </uni-list-item> | |
| 22 | + | |
| 23 | + <!-- 数量 --> | |
| 24 | + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'"> | |
| 25 | + <template v-slot:body> | |
| 26 | + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view> | |
| 27 | + </template> | |
| 28 | + <template v-slot:footer> | |
| 29 | + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" /> | |
| 30 | + </template> | |
| 31 | + </uni-list-item> | |
| 32 | + | |
| 33 | + <!-- 开始时间 --> | |
| 34 | + <uni-list-item class="select-item" :class="form.startTime ? 'is-filled' : 'is-empty'"> | |
| 35 | + <template v-slot:body> | |
| 36 | + <view class="item-title"><text class="required">*</text><text>开始时间</text></view> | |
| 37 | + </template> | |
| 38 | + <template v-slot:footer> | |
| 39 | + <uni-datetime-picker type="datetime" v-model="form.startTime" :hide-second="true" :clear-icon="false" /> | |
| 40 | + </template> | |
| 41 | + </uni-list-item> | |
| 42 | + | |
| 43 | + <!-- 截止时间 --> | |
| 44 | + <uni-list-item class="select-item" :class="form.endTime ? 'is-filled' : 'is-empty'"> | |
| 45 | + <template v-slot:body> | |
| 46 | + <view class="item-title"><text class="required">*</text><text>截止时间</text></view> | |
| 47 | + </template> | |
| 48 | + <template v-slot:footer> | |
| 49 | + <uni-datetime-picker type="datetime" v-model="form.endTime" :hide-second="true" :clear-icon="false" /> | |
| 50 | + </template> | |
| 51 | + </uni-list-item> | |
| 52 | + | |
| 53 | + <!-- 项目类型 --> | |
| 54 | + <uni-list-item class="select-item" :class="form.projectType ? 'is-filled' : 'is-empty'" clickable | |
| 55 | + @click="openDictSheet('projectType', 'PROJECT_TYPE', '项目类型')" :rightText="getProjectTypeLabel() || '请选择项目类型'" showArrow> | |
| 56 | + <template v-slot:body> | |
| 57 | + <view class="item-title"><text class="required">*</text><text>项目类型</text></view> | |
| 58 | + </template> | |
| 59 | + </uni-list-item> | |
| 60 | + | |
| 61 | + <!-- 开标时间 - 仅项目类型为集中开标时显示 --> | |
| 62 | + <uni-list-item v-if="isConcentratedBid" class="select-item" :class="form.bidTime ? 'is-filled' : 'is-empty'"> | |
| 63 | + <template v-slot:body> | |
| 64 | + <view class="item-title"><text class="required">*</text><text>开标时间</text></view> | |
| 65 | + </template> | |
| 66 | + <template v-slot:footer> | |
| 67 | + <uni-datetime-picker type="datetime" v-model="form.bidTime" :hide-second="true" :clear-icon="false" /> | |
| 68 | + </template> | |
| 69 | + </uni-list-item> | |
| 70 | + | |
| 71 | + <!-- 采购规则 --> | |
| 72 | + <uni-list-item class="select-item textarea-item" :class="form.purchaseRule ? 'is-filled' : 'is-empty'"> | |
| 73 | + <template v-slot:body> | |
| 74 | + <view class="item-title"><text class="required">*</text><text>采购规则</text></view> | |
| 75 | + </template> | |
| 76 | + <template v-slot:footer> | |
| 77 | + <uni-easyinput type="textarea" v-model="form.purchaseRule" :inputBorder="false" | |
| 78 | + placeholder="请输入采购规则" :autoHeight="true" /> | |
| 79 | + </template> | |
| 80 | + </uni-list-item> | |
| 81 | + </uni-list> | |
| 82 | + </scroll-view> | |
| 83 | + | |
| 84 | + <view class="footer"> | |
| 85 | + <button class="btn submit" @click="onSubmit">保存</button> | |
| 86 | + </view> | |
| 87 | + | |
| 88 | + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" | |
| 89 | + v-model="sheet.value" @confirm="onSheetConfirm" /> | |
| 90 | + </view> | |
| 91 | +</template> | |
| 92 | + | |
| 93 | +<script> | |
| 94 | +import SingleSelectSheet from '@/components/single-select/index.vue' | |
| 95 | +import { supplierQuotationProjectDetailApi, supplierQuotationProjectUpdateApi } from '@/api/procure-manage/supplierQuotationProject.js' | |
| 96 | +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js' | |
| 97 | +import { getDicByCodes } from '@/utils/dic.js' | |
| 98 | + | |
| 99 | +export default { | |
| 100 | + name: 'ModifySupplierPrice', | |
| 101 | + components: { SingleSelectSheet }, | |
| 102 | + data() { | |
| 103 | + return { | |
| 104 | + id: '', | |
| 105 | + form: { | |
| 106 | + id: '', | |
| 107 | + name: '', | |
| 108 | + code: '', | |
| 109 | + productId: '', | |
| 110 | + productName: '', | |
| 111 | + quantity: '', | |
| 112 | + startTime: '', | |
| 113 | + endTime: '', | |
| 114 | + projectType: '', | |
| 115 | + bidTime: '', | |
| 116 | + purchaseRule: '' | |
| 117 | + }, | |
| 118 | + productList: [], | |
| 119 | + dictData: {}, | |
| 120 | + sheet: { | |
| 121 | + visible: false, | |
| 122 | + title: '', | |
| 123 | + options: [], | |
| 124 | + value: '', | |
| 125 | + field: '', | |
| 126 | + isDict: false | |
| 127 | + } | |
| 128 | + } | |
| 129 | + }, | |
| 130 | + computed: { | |
| 131 | + isConcentratedBid() { | |
| 132 | + const opts = this.getDictOptions('PROJECT_TYPE') || [] | |
| 133 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 134 | + const label = m ? (m.name || m.label || '') : '' | |
| 135 | + return label.indexOf('集中开标') > -1 | |
| 136 | + } | |
| 137 | + }, | |
| 138 | + onLoad(options) { | |
| 139 | + this.id = (options && options.id) ? options.id : '' | |
| 140 | + this.loadDictData().then(() => { | |
| 141 | + this.loadProducts().then(() => { | |
| 142 | + if (this.id) this.loadDetail() | |
| 143 | + }) | |
| 144 | + }) | |
| 145 | + }, | |
| 146 | + methods: { | |
| 147 | + async loadDictData() { | |
| 148 | + try { | |
| 149 | + const res = await getDicByCodes(['PROJECT_TYPE']) | |
| 150 | + this.dictData = res || {} | |
| 151 | + } catch (e) { | |
| 152 | + this.dictData = {} | |
| 153 | + } | |
| 154 | + }, | |
| 155 | + getDictOptions(dictCode) { | |
| 156 | + const data = this.dictData[dictCode] | |
| 157 | + return (data && data.data) ? data.data : (Array.isArray(data) ? data : []) | |
| 158 | + }, | |
| 159 | + async loadProducts() { | |
| 160 | + try { | |
| 161 | + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 }) | |
| 162 | + const _data = res && res.data ? res.data : {} | |
| 163 | + const list = _data.datas || _data.list || _data.records || [] | |
| 164 | + this.productList = list.map(it => ({ | |
| 165 | + label: it.productName || it.name || '', | |
| 166 | + value: it.productId || it.id || '' | |
| 167 | + })) | |
| 168 | + } catch (e) { | |
| 169 | + this.productList = [] | |
| 170 | + } | |
| 171 | + }, | |
| 172 | + getProjectTypeLabel() { | |
| 173 | + const opts = this.getDictOptions('PROJECT_TYPE') || [] | |
| 174 | + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType)) | |
| 175 | + return m ? (m.name || m.label || '') : '' | |
| 176 | + }, | |
| 177 | + openProductSheet() { | |
| 178 | + const opts = (this.productList || []).map(p => ({ label: p.label, value: p.value })) | |
| 179 | + const cur = this.form.productId | |
| 180 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 181 | + this.sheet = { | |
| 182 | + visible: true, | |
| 183 | + title: '请选择品种', | |
| 184 | + options: opts, | |
| 185 | + value: match ? match.value : '', | |
| 186 | + field: 'productId', | |
| 187 | + isDict: false | |
| 188 | + } | |
| 189 | + }, | |
| 190 | + openDictSheet(field, dictCode, title) { | |
| 191 | + const raw = this.getDictOptions(dictCode) || [] | |
| 192 | + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') })) | |
| 193 | + const cur = this.form[field] | |
| 194 | + const match = opts.find(o => String(o.value) === String(cur)) | |
| 195 | + this.sheet = { | |
| 196 | + visible: true, | |
| 197 | + title: title || '请选择', | |
| 198 | + options: opts, | |
| 199 | + value: match ? match.value : '', | |
| 200 | + field, | |
| 201 | + isDict: true | |
| 202 | + } | |
| 203 | + }, | |
| 204 | + onSheetConfirm({ value }) { | |
| 205 | + const field = this.sheet.field | |
| 206 | + if (!field) return | |
| 207 | + this.form[field] = value | |
| 208 | + if (field === 'productId') { | |
| 209 | + const m = (this.productList || []).find(p => String(p.value) === String(value)) | |
| 210 | + this.form.productName = m ? m.label : '' | |
| 211 | + } | |
| 212 | + if (field === 'projectType' && !this.isConcentratedBid) { | |
| 213 | + this.form.bidTime = '' | |
| 214 | + } | |
| 215 | + }, | |
| 216 | + onQuantityBlur() { | |
| 217 | + let v = this.form.quantity | |
| 218 | + if (v === '' || v == null) return | |
| 219 | + v = parseFloat(v) | |
| 220 | + if (isNaN(v)) { | |
| 221 | + this.form.quantity = '' | |
| 222 | + return | |
| 223 | + } | |
| 224 | + this.form.quantity = String(v) | |
| 225 | + }, | |
| 226 | + formatDateTimeMinute(v) { | |
| 227 | + const s = v == null ? '' : String(v).trim() | |
| 228 | + if (!s) return '' | |
| 229 | + if (s.length >= 16) return s.slice(0, 16) | |
| 230 | + return s | |
| 231 | + }, | |
| 232 | + formatDateTimeSecond(v) { | |
| 233 | + const s = v == null ? '' : String(v).trim() | |
| 234 | + if (!s) return '' | |
| 235 | + const minute = s.length >= 16 ? s.slice(0, 16) : s | |
| 236 | + return minute + ':00' | |
| 237 | + }, | |
| 238 | + async loadDetail() { | |
| 239 | + try { | |
| 240 | + const res = await supplierQuotationProjectDetailApi({ id: this.id }) | |
| 241 | + const d = (res && res.data) ? res.data : {} | |
| 242 | + // 时间格式截取到分钟用于选择器回显 | |
| 243 | + if (d.startTime && d.startTime.length >= 16) d.startTime = d.startTime.slice(0, 16) | |
| 244 | + if (d.endTime && d.endTime.length >= 16) d.endTime = d.endTime.slice(0, 16) | |
| 245 | + if (d.bidTime && d.bidTime.length >= 16) d.bidTime = d.bidTime.slice(0, 16) | |
| 246 | + this.form = { ...this.form, ...d } | |
| 247 | + } catch (e) { | |
| 248 | + uni.showToast({ title: '加载详情失败', icon: 'none' }) | |
| 249 | + } | |
| 250 | + }, | |
| 251 | + validateRequired() { | |
| 252 | + const checks = [ | |
| 253 | + { key: 'name', msg: '请输入项目名称' }, | |
| 254 | + { key: 'productId', msg: '请选择品种' }, | |
| 255 | + { key: 'quantity', msg: '请输入数量' }, | |
| 256 | + { key: 'startTime', msg: '请选择开始时间' }, | |
| 257 | + { key: 'endTime', msg: '请选择截止时间' }, | |
| 258 | + { key: 'projectType', msg: '请选择项目类型' }, | |
| 259 | + { key: 'purchaseRule', msg: '请输入采购规则' } | |
| 260 | + ] | |
| 261 | + for (const it of checks) { | |
| 262 | + const val = this.form[it.key] | |
| 263 | + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) | |
| 264 | + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false } | |
| 265 | + } | |
| 266 | + if (this.isConcentratedBid && !this.form.bidTime) { | |
| 267 | + uni.showToast({ title: '请选择开标时间', icon: 'none' }) | |
| 268 | + return false | |
| 269 | + } | |
| 270 | + if (isNaN(parseFloat(this.form.quantity))) { | |
| 271 | + uni.showToast({ title: '数量格式不正确', icon: 'none' }) | |
| 272 | + return false | |
| 273 | + } | |
| 274 | + if (this.form.startTime && this.form.endTime && this.formatDateTimeMinute(this.form.startTime) > this.formatDateTimeMinute(this.form.endTime)) { | |
| 275 | + uni.showToast({ title: '开始时间不能晚于截止时间', icon: 'none' }) | |
| 276 | + return false | |
| 277 | + } | |
| 278 | + return true | |
| 279 | + }, | |
| 280 | + async onSubmit() { | |
| 281 | + if (!this.validateRequired()) return | |
| 282 | + const confirmRes = await new Promise(resolve => { | |
| 283 | + uni.showModal({ title: '提示', content: '确定保存修改吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 284 | + }) | |
| 285 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 286 | + const clean = (obj) => { | |
| 287 | + const out = {} | |
| 288 | + Object.keys(obj || {}).forEach(k => { | |
| 289 | + const v = obj[k] | |
| 290 | + const isEmptyString = typeof v === 'string' && v.trim() === '' | |
| 291 | + const isUndef = v === undefined || v === null | |
| 292 | + const isNaNNumber = typeof v === 'number' && isNaN(v) | |
| 293 | + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v | |
| 294 | + }) | |
| 295 | + return out | |
| 296 | + } | |
| 297 | + const payload = clean({ | |
| 298 | + id: this.form.id, | |
| 299 | + name: this.form.name, | |
| 300 | + productId: this.form.productId, | |
| 301 | + productName: this.form.productName, | |
| 302 | + quantity: this.form.quantity, | |
| 303 | + startTime: this.formatDateTimeSecond(this.form.startTime), | |
| 304 | + endTime: this.formatDateTimeSecond(this.form.endTime), | |
| 305 | + projectType: this.form.projectType, | |
| 306 | + bidTime: this.isConcentratedBid ? this.formatDateTimeSecond(this.form.bidTime) : '', | |
| 307 | + purchaseRule: this.form.purchaseRule | |
| 308 | + }) | |
| 309 | + try { | |
| 310 | + await supplierQuotationProjectUpdateApi(payload) | |
| 311 | + uni.showToast({ title: '保存成功', icon: 'none' }) | |
| 312 | + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 313 | + setTimeout(() => { uni.navigateBack() }, 400) | |
| 314 | + } catch (e) { | |
| 315 | + uni.showToast({ title: e.msg || '保存失败', icon: 'none' }) | |
| 316 | + } | |
| 317 | + } | |
| 318 | + } | |
| 319 | +} | |
| 320 | +</script> | |
| 321 | + | |
| 322 | +<style lang="scss" scoped> | |
| 323 | +.page { | |
| 324 | + display: flex; | |
| 325 | + flex-direction: column; | |
| 326 | + height: 100%; | |
| 327 | +} | |
| 328 | + | |
| 329 | +.scroll { | |
| 330 | + flex: 1; | |
| 331 | + padding: 12rpx 0 200rpx !important; | |
| 332 | +} | |
| 333 | + | |
| 334 | +.footer { | |
| 335 | + z-index: 2; | |
| 336 | + position: fixed; | |
| 337 | + left: 0; | |
| 338 | + right: 0; | |
| 339 | + bottom: 0; | |
| 340 | + padding: 32rpx; | |
| 341 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 342 | + background: #fff; | |
| 343 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 344 | + | |
| 345 | + .btn { | |
| 346 | + height: 80rpx; | |
| 347 | + line-height: 80rpx; | |
| 348 | + border-radius: 12rpx; | |
| 349 | + font-size: 32rpx; | |
| 350 | + } | |
| 351 | + | |
| 352 | + .submit { | |
| 353 | + background: $theme-primary; | |
| 354 | + color: #fff; | |
| 355 | + } | |
| 356 | +} | |
| 357 | + | |
| 358 | +::v-deep .uni-list { | |
| 359 | + .uni-easyinput { | |
| 360 | + display: flex; | |
| 361 | + | |
| 362 | + .uni-input-input { | |
| 363 | + color: rgba(0, 0, 0, 0.9); | |
| 364 | + } | |
| 365 | + } | |
| 366 | + | |
| 367 | + .uni-input-placeholder { | |
| 368 | + z-index: 1; | |
| 369 | + } | |
| 370 | + | |
| 371 | + .uni-input-input { | |
| 372 | + background-color: #ffffff; | |
| 373 | + } | |
| 374 | + | |
| 375 | + background: transparent; | |
| 376 | + | |
| 377 | + &-item { | |
| 378 | + &__extra-text { | |
| 379 | + font-size: 32rpx; | |
| 380 | + } | |
| 381 | + | |
| 382 | + &__content-title { | |
| 383 | + font-size: 32rpx; | |
| 384 | + color: rgba(0, 0, 0, 0.9); | |
| 385 | + } | |
| 386 | + | |
| 387 | + &__container { | |
| 388 | + padding: 32rpx; | |
| 389 | + | |
| 390 | + .uni-easyinput { | |
| 391 | + &__placeholder-class { | |
| 392 | + font-size: 32rpx; | |
| 393 | + color: rgba(0, 0, 0, 0.4); | |
| 394 | + } | |
| 395 | + | |
| 396 | + &__content { | |
| 397 | + border: none; | |
| 398 | + background-color: #ffffff !important; | |
| 399 | + | |
| 400 | + &-input { | |
| 401 | + padding-left: 0 !important; | |
| 402 | + height: 48rpx; | |
| 403 | + line-height: 48rpx; | |
| 404 | + font-size: 32rpx; | |
| 405 | + } | |
| 406 | + | |
| 407 | + .content-clear-icon { | |
| 408 | + font-size: 44rpx !important; | |
| 409 | + } | |
| 410 | + } | |
| 411 | + } | |
| 412 | + | |
| 413 | + .item-title, | |
| 414 | + .uni-list-item__content { | |
| 415 | + flex: none; | |
| 416 | + min-height: 48rpx; | |
| 417 | + line-height: 48rpx; | |
| 418 | + font-size: 32rpx; | |
| 419 | + position: relative; | |
| 420 | + width: 240rpx; | |
| 421 | + margin-right: 32rpx; | |
| 422 | + color: rgba(0, 0, 0, 0.9); | |
| 423 | + | |
| 424 | + .required { | |
| 425 | + color: red; | |
| 426 | + position: absolute; | |
| 427 | + top: 50%; | |
| 428 | + transform: translateY(-50%); | |
| 429 | + left: -16rpx; | |
| 430 | + } | |
| 431 | + } | |
| 432 | + } | |
| 433 | + | |
| 434 | + &.select-item { | |
| 435 | + &.is-empty { | |
| 436 | + .uni-list-item__extra-text { | |
| 437 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 438 | + } | |
| 439 | + } | |
| 440 | + | |
| 441 | + &.is-filled { | |
| 442 | + .uni-list-item__extra-text { | |
| 443 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 444 | + } | |
| 445 | + } | |
| 446 | + } | |
| 447 | + } | |
| 448 | +} | |
| 449 | + | |
| 450 | +::v-deep .uni-datetime-picker { | |
| 451 | + flex: 1; | |
| 452 | + | |
| 453 | + .uni-input-wrapper { | |
| 454 | + justify-content: flex-end; | |
| 455 | + } | |
| 456 | + | |
| 457 | + .uni-input-input { | |
| 458 | + text-align: right; | |
| 459 | + font-size: 32rpx; | |
| 460 | + color: rgba(0, 0, 0, 0.9); | |
| 461 | + } | |
| 462 | +} | |
| 463 | + | |
| 464 | +.textarea-item { | |
| 465 | + ::v-deep .uni-list-item__container { | |
| 466 | + flex-direction: column; | |
| 467 | + align-items: stretch; | |
| 468 | + | |
| 469 | + .item-title { | |
| 470 | + width: auto; | |
| 471 | + margin-right: 0; | |
| 472 | + margin-bottom: 16rpx; | |
| 473 | + } | |
| 474 | + | |
| 475 | + .uni-easyinput { | |
| 476 | + width: 100%; | |
| 477 | + } | |
| 478 | + | |
| 479 | + .uni-textarea-textarea { | |
| 480 | + text-align: left; | |
| 481 | + min-height: 120rpx; | |
| 482 | + font-size: 32rpx; | |
| 483 | + color: rgba(0, 0, 0, 0.9); | |
| 484 | + } | |
| 485 | + } | |
| 486 | +} | |
| 487 | +</style> | ... | ... |
pages/supplier-price/quote.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="page"> | |
| 3 | + <scroll-view class="scroll" scroll-y> | |
| 4 | + <uni-list> | |
| 5 | + <uni-list-item class="select-item is-filled"> | |
| 6 | + <template v-slot:body> | |
| 7 | + <view class="item-title">项目名称</view> | |
| 8 | + </template> | |
| 9 | + <template v-slot:footer> | |
| 10 | + <text class="readonly-text">{{ form.name || '-' }}</text> | |
| 11 | + </template> | |
| 12 | + </uni-list-item> | |
| 13 | + <uni-list-item class="select-item is-filled"> | |
| 14 | + <template v-slot:body> | |
| 15 | + <view class="item-title">开始时间</view> | |
| 16 | + </template> | |
| 17 | + <template v-slot:footer> | |
| 18 | + <text class="readonly-text">{{ form.startTime || '-' }}</text> | |
| 19 | + </template> | |
| 20 | + </uni-list-item> | |
| 21 | + <uni-list-item class="select-item is-filled"> | |
| 22 | + <template v-slot:body> | |
| 23 | + <view class="item-title">截止时间</view> | |
| 24 | + </template> | |
| 25 | + <template v-slot:footer> | |
| 26 | + <text class="readonly-text">{{ form.endTime || '-' }}</text> | |
| 27 | + </template> | |
| 28 | + </uni-list-item> | |
| 29 | + <!-- 品种 --> | |
| 30 | + <uni-list-item class="select-item is-filled"> | |
| 31 | + <template v-slot:body> | |
| 32 | + <view class="item-title"><text class="required">*</text><text>品种</text></view> | |
| 33 | + </template> | |
| 34 | + <template v-slot:footer> | |
| 35 | + <text class="readonly-text">{{ form.productName || '-' }}</text> | |
| 36 | + </template> | |
| 37 | + </uni-list-item> | |
| 38 | + | |
| 39 | + <!-- 数量 --> | |
| 40 | + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'"> | |
| 41 | + <template v-slot:body> | |
| 42 | + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view> | |
| 43 | + </template> | |
| 44 | + <template v-slot:footer> | |
| 45 | + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" /> | |
| 46 | + </template> | |
| 47 | + </uni-list-item> | |
| 48 | + | |
| 49 | + <!-- 价格 --> | |
| 50 | + <uni-list-item class="select-item" :class="form.price !== '' && form.price != null ? 'is-filled' : 'is-empty'"> | |
| 51 | + <template v-slot:body> | |
| 52 | + <view class="item-title"><text class="required">*</text><text>价格</text></view> | |
| 53 | + </template> | |
| 54 | + <template v-slot:footer> | |
| 55 | + <uni-easyinput v-model="form.price" type="digit" :inputBorder="false" placeholder="请输入价格" @blur="onPriceBlur" /> | |
| 56 | + </template> | |
| 57 | + </uni-list-item> | |
| 58 | + </uni-list> | |
| 59 | + </scroll-view> | |
| 60 | + | |
| 61 | + <view class="footer"> | |
| 62 | + <button class="btn submit" @click="onSubmit">提交报价</button> | |
| 63 | + </view> | |
| 64 | + </view> | |
| 65 | +</template> | |
| 66 | + | |
| 67 | +<script> | |
| 68 | +import { supplierQuotationProjectDetailApi, supplierQuotationProjectQuoteApi } from '@/api/procure-manage/supplierQuotationProject.js' | |
| 69 | + | |
| 70 | +export default { | |
| 71 | + name: 'QuoteSupplierPrice', | |
| 72 | + data() { | |
| 73 | + return { | |
| 74 | + projectId: '', | |
| 75 | + form: { | |
| 76 | + projectId: '', | |
| 77 | + productId: '', | |
| 78 | + productName: '', | |
| 79 | + quantity: '', | |
| 80 | + price: '', | |
| 81 | + startTime: '', | |
| 82 | + endTime: '', | |
| 83 | + name: '' | |
| 84 | + } | |
| 85 | + } | |
| 86 | + }, | |
| 87 | + onLoad(options) { | |
| 88 | + this.projectId = (options && options.id) ? options.id : '' | |
| 89 | + this.form.projectId = this.projectId | |
| 90 | + if (!this.projectId) return | |
| 91 | + this.loadDetail() | |
| 92 | + }, | |
| 93 | + methods: { | |
| 94 | + async loadDetail() { | |
| 95 | + try { | |
| 96 | + const res = await supplierQuotationProjectDetailApi({ id: this.projectId }) | |
| 97 | + const d = (res && res.data) ? res.data : {} | |
| 98 | + this.form.productId = d.productId || '' | |
| 99 | + this.form.productName = d.productName || '' | |
| 100 | + this.form.name = d.name || '' | |
| 101 | + this.form.startTime = d.startTime?.substring(0, 16) || '' | |
| 102 | + this.form.endTime = d.endTime?.substring(0, 16) || '' | |
| 103 | + // 数量默认带出项目数量,可修改 | |
| 104 | + if (d.quantity != null && d.quantity !== '') this.form.quantity = String(d.quantity) | |
| 105 | + } catch (e) { | |
| 106 | + uni.showToast({ title: '加载项目信息失败', icon: 'none' }) | |
| 107 | + } | |
| 108 | + }, | |
| 109 | + onQuantityBlur() { | |
| 110 | + let v = this.form.quantity | |
| 111 | + if (v === '' || v == null) return | |
| 112 | + v = parseFloat(v) | |
| 113 | + if (isNaN(v)) { this.form.quantity = ''; return } | |
| 114 | + this.form.quantity = String(v) | |
| 115 | + }, | |
| 116 | + onPriceBlur() { | |
| 117 | + let v = this.form.price | |
| 118 | + if (v === '' || v == null) return | |
| 119 | + v = parseFloat(v) | |
| 120 | + if (isNaN(v)) { this.form.price = ''; return } | |
| 121 | + this.form.price = String(v) | |
| 122 | + }, | |
| 123 | + validateRequired() { | |
| 124 | + if (!this.form.projectId) { uni.showToast({ title: '缺少项目ID', icon: 'none' }); return false } | |
| 125 | + if (!this.form.productId) { uni.showToast({ title: '缺少品种', icon: 'none' }); return false } | |
| 126 | + if (this.form.quantity === '' || this.form.quantity == null) { uni.showToast({ title: '请输入数量', icon: 'none' }); return false } | |
| 127 | + if (isNaN(parseFloat(this.form.quantity))) { uni.showToast({ title: '数量格式不正确', icon: 'none' }); return false } | |
| 128 | + if (this.form.price === '' || this.form.price == null) { uni.showToast({ title: '请输入价格', icon: 'none' }); return false } | |
| 129 | + if (isNaN(parseFloat(this.form.price))) { uni.showToast({ title: '价格格式不正确', icon: 'none' }); return false } | |
| 130 | + return true | |
| 131 | + }, | |
| 132 | + async onSubmit() { | |
| 133 | + if (!this.validateRequired()) return | |
| 134 | + const confirmRes = await new Promise(resolve => { | |
| 135 | + uni.showModal({ title: '提示', content: '确定提交报价吗?', confirmText: '确定', cancelText: '取消', success: resolve }) | |
| 136 | + }) | |
| 137 | + if (!(confirmRes && confirmRes.confirm)) return | |
| 138 | + const payload = { | |
| 139 | + projectId: this.form.projectId, | |
| 140 | + productId: this.form.productId, | |
| 141 | + quantity: parseFloat(this.form.quantity), | |
| 142 | + price: parseFloat(this.form.price) | |
| 143 | + } | |
| 144 | + try { | |
| 145 | + await supplierQuotationProjectQuoteApi(payload) | |
| 146 | + uni.showToast({ title: '报价成功', icon: 'none' }) | |
| 147 | + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {} | |
| 148 | + setTimeout(() => { uni.navigateBack() }, 400) | |
| 149 | + } catch (e) { | |
| 150 | + uni.showToast({ title: e.msg || '报价失败', icon: 'none' }) | |
| 151 | + } | |
| 152 | + } | |
| 153 | + } | |
| 154 | +} | |
| 155 | +</script> | |
| 156 | + | |
| 157 | +<style lang="scss" scoped> | |
| 158 | +.page { | |
| 159 | + display: flex; | |
| 160 | + flex-direction: column; | |
| 161 | + height: 100%; | |
| 162 | +} | |
| 163 | + | |
| 164 | +.scroll { | |
| 165 | + flex: 1; | |
| 166 | + padding: 12rpx 0 200rpx !important; | |
| 167 | +} | |
| 168 | + | |
| 169 | +.footer { | |
| 170 | + z-index: 2; | |
| 171 | + position: fixed; | |
| 172 | + left: 0; | |
| 173 | + right: 0; | |
| 174 | + bottom: 0; | |
| 175 | + padding: 32rpx; | |
| 176 | + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); | |
| 177 | + background: #fff; | |
| 178 | + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06); | |
| 179 | + | |
| 180 | + .btn { | |
| 181 | + height: 80rpx; | |
| 182 | + line-height: 80rpx; | |
| 183 | + border-radius: 12rpx; | |
| 184 | + font-size: 32rpx; | |
| 185 | + } | |
| 186 | + | |
| 187 | + .submit { | |
| 188 | + background: $theme-primary; | |
| 189 | + color: #fff; | |
| 190 | + } | |
| 191 | +} | |
| 192 | + | |
| 193 | +::v-deep .uni-list { | |
| 194 | + .uni-easyinput { | |
| 195 | + display: flex; | |
| 196 | + | |
| 197 | + .uni-input-input { | |
| 198 | + color: rgba(0, 0, 0, 0.9); | |
| 199 | + } | |
| 200 | + } | |
| 201 | + | |
| 202 | + .uni-input-placeholder { | |
| 203 | + z-index: 1; | |
| 204 | + } | |
| 205 | + | |
| 206 | + .uni-input-input { | |
| 207 | + background-color: #ffffff; | |
| 208 | + } | |
| 209 | + | |
| 210 | + background: transparent; | |
| 211 | + | |
| 212 | + &-item { | |
| 213 | + &__extra-text { | |
| 214 | + font-size: 32rpx; | |
| 215 | + } | |
| 216 | + | |
| 217 | + &__content-title { | |
| 218 | + font-size: 32rpx; | |
| 219 | + color: rgba(0, 0, 0, 0.9); | |
| 220 | + } | |
| 221 | + | |
| 222 | + &__container { | |
| 223 | + padding: 32rpx; | |
| 224 | + | |
| 225 | + .uni-easyinput { | |
| 226 | + &__placeholder-class { | |
| 227 | + font-size: 32rpx; | |
| 228 | + color: rgba(0, 0, 0, 0.4); | |
| 229 | + } | |
| 230 | + | |
| 231 | + &__content { | |
| 232 | + border: none; | |
| 233 | + background-color: #ffffff !important; | |
| 234 | + | |
| 235 | + &-input { | |
| 236 | + padding-left: 0 !important; | |
| 237 | + height: 48rpx; | |
| 238 | + line-height: 48rpx; | |
| 239 | + font-size: 32rpx; | |
| 240 | + } | |
| 241 | + | |
| 242 | + .content-clear-icon { | |
| 243 | + font-size: 44rpx !important; | |
| 244 | + } | |
| 245 | + } | |
| 246 | + } | |
| 247 | + | |
| 248 | + .item-title, | |
| 249 | + .uni-list-item__content { | |
| 250 | + flex: none; | |
| 251 | + min-height: 48rpx; | |
| 252 | + line-height: 48rpx; | |
| 253 | + font-size: 32rpx; | |
| 254 | + position: relative; | |
| 255 | + width: 240rpx; | |
| 256 | + margin-right: 32rpx; | |
| 257 | + color: rgba(0, 0, 0, 0.9); | |
| 258 | + | |
| 259 | + .required { | |
| 260 | + color: red; | |
| 261 | + position: absolute; | |
| 262 | + top: 50%; | |
| 263 | + transform: translateY(-50%); | |
| 264 | + left: -16rpx; | |
| 265 | + } | |
| 266 | + } | |
| 267 | + } | |
| 268 | + | |
| 269 | + &.select-item { | |
| 270 | + .readonly-text { | |
| 271 | + font-size: 32rpx; | |
| 272 | + color: rgba(0, 0, 0, 0.9); | |
| 273 | + text-align: right; | |
| 274 | + } | |
| 275 | + | |
| 276 | + &.is-empty { | |
| 277 | + .uni-list-item__extra-text { | |
| 278 | + color: rgba(0, 0, 0, 0.4) !important; | |
| 279 | + } | |
| 280 | + } | |
| 281 | + | |
| 282 | + &.is-filled { | |
| 283 | + .uni-list-item__extra-text { | |
| 284 | + color: rgba(0, 0, 0, 0.9) !important; | |
| 285 | + } | |
| 286 | + } | |
| 287 | + } | |
| 288 | + } | |
| 289 | +} | |
| 290 | +</style> | ... | ... |
| 1 | 1 | import CustomerDevelopViewer from '@/pages/dev_manage/viewer.vue' |
| 2 | 2 | import CustomerCreditViewer from '@/pages/credit_manage/viewer.vue' |
| 3 | 3 | import DomesticCustomerCreditViewer from '@/pages/domestic_trade/domesticCreditViewer.vue' |
| 4 | +import SalesmanQuotationViewer from '@/pages/sales-price/salesmanQuotationViewer.vue' | |
| 4 | 5 | import ForeignTradeCreditViewer from '@/pages/foreign_trade/foreignTradeCreditViewer.vue' |
| 5 | 6 | import RetailViewer from '@/pages/contract_retail/retailViewer.vue' |
| 6 | 7 | import ForeignStdViewer from '@/pages/contract_foreign_std/foreignStdViewer.vue' |
| ... | ... | @@ -29,6 +30,7 @@ export default function registerComponents(Vue) { |
| 29 | 30 | Vue.component('CustomerDevelopViewer', CustomerDevelopViewer) |
| 30 | 31 | Vue.component('CustomerCreditViewer', CustomerCreditViewer) |
| 31 | 32 | Vue.component('DomesticCustomerCreditViewer', DomesticCustomerCreditViewer) |
| 33 | + Vue.component('SalesmanQuotationViewer', SalesmanQuotationViewer) | |
| 32 | 34 | Vue.component('ForeignTradeCreditViewer', ForeignTradeCreditViewer) |
| 33 | 35 | Vue.component('RetailViewer', RetailViewer) |
| 34 | 36 | Vue.component('StandardContractViewer', StandardContractViewer) | ... | ... |
| ... | ... | @@ -20,6 +20,12 @@ export const getSysFlowComponentPath = (bizFlag) => { |
| 20 | 20 | case 'CUSTOMER_CREDIT_IN_EDIT': // 客户资信(内贸) |
| 21 | 21 | componentPath = 'DomesticCustomerCreditViewer'; // 客户资信(内贸)-审批 |
| 22 | 22 | break; |
| 23 | + case 'SALESMAN_QUOTATION': // 业务员报价单 | |
| 24 | + componentPath = 'SalesmanQuotationViewer'; // 业务员报价单-审批详情 | |
| 25 | + break; | |
| 26 | + case 'SALESMAN_QUOTATION_EDIT': // 业务员报价单 | |
| 27 | + componentPath = 'SalesmanQuotationViewer'; // 业务员报价单-审批 | |
| 28 | + break; | |
| 23 | 29 | case 'CUSTOMER_CREDIT_OUT': // 客户资信(外贸) |
| 24 | 30 | componentPath = 'ForeignTradeCreditViewer'; // 客户资信(外贸)-审批详情 |
| 25 | 31 | break; | ... | ... |