Commit 7b58cc8d7d484be051938c922ea36813a1d2337b

Authored by gesilong
2 parents a0aba82d ec6b490d

Merge branch 'cjerp-2.0-price' into test_cjerp

Too many changes to show.

To preserve performance only 19 of 29 files are displayed.

  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 +}
... ...
  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 +}
... ...
  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: '订货单管理',
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...