Commit c619206ecb9b2156ff4a59d0ba79883b5b8ede50

Authored by gesilong
1 parent 8f02816d

commit:供应商报价接口联调

... ... @@ -8,3 +8,11 @@ export function supplierQuotationDealQueryApi(params) {
8 8 params
9 9 })
10 10 }
  11 +
  12 +export function supplierQuotationDealGetApi(id) {
  13 + return request({
  14 + url: '/procurement/supplierQuotationDeal/get',
  15 + method: 'get',
  16 + params: { id }
  17 + })
  18 +}
... ...
... ... @@ -8,3 +8,51 @@ export function supplierQuotationProjectQueryApi(params) {
8 8 params
9 9 })
10 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 +}
... ...
... ... @@ -325,6 +325,38 @@
325 325 }
326 326 },
327 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 + {
328 360 "path": "pages/quote-manage/index",
329 361 "style": {
330 362 "navigationBarTitleText": "报价管理",
... ... @@ -333,6 +365,14 @@
333 365 }
334 366 },
335 367 {
  368 + "path": "pages/quote-manage/detail",
  369 + "style": {
  370 + "navigationBarTitleText": "报价管理详情",
  371 + "navigationBarBackgroundColor": "#ffffff",
  372 + "navigationBarTextStyle": "black"
  373 + }
  374 + },
  375 + {
336 376 "path": "pages/peer-info/index",
337 377 "style": {
338 378 "navigationBarTitleText": "同行信息",
... ...
  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 + </view>
  20 +</template>
  21 +
  22 +<script>
  23 +import { supplierQuotationDealGetApi } from '@/api/procure-manage/supplierQuotationDeal.js'
  24 +
  25 +export default {
  26 + name: 'QuoteManageDetail',
  27 + data() {
  28 + return {
  29 + id: '',
  30 + loading: false,
  31 + form: {}
  32 + }
  33 + },
  34 + onShow() {
  35 + const options = (this.$route && this.$route.query) ? this.$route.query : {}
  36 + const id = options && options.id ? String(options.id) : ''
  37 + this.id = id
  38 + this.loadDetail()
  39 + },
  40 + methods: {
  41 + safeText(v) {
  42 + const s = v == null ? '' : String(v)
  43 + return s.trim() ? s : '-'
  44 + },
  45 + formatDateTimeMinute(v) {
  46 + const s = v == null ? '' : String(v).trim()
  47 + if (!s) return '-'
  48 + if (s.length >= 16) return s.slice(0, 16)
  49 + if (s.length >= 10) return s.slice(0, 10)
  50 + return s
  51 + },
  52 + async loadDetail() {
  53 + this.loading = true
  54 + try {
  55 + const res = await supplierQuotationDealGetApi(this.id)
  56 + const data = (res && res.data) ? res.data : {}
  57 + this.form = data
  58 + } catch (e) {
  59 + this.form = {}
  60 + uni.showToast({ title: '详情加载失败', icon: 'none' })
  61 + } finally {
  62 + this.loading = false
  63 + }
  64 + }
  65 + }
  66 +}
  67 +</script>
  68 +
  69 +<style lang="scss" scoped>
  70 +.page {
  71 + display: flex;
  72 + flex-direction: column;
  73 + height: 100%;
  74 +}
  75 +
  76 +.scroll {
  77 + flex: 1;
  78 + padding: 8rpx 0 144rpx;
  79 +}
  80 +
  81 +.detail-page {
  82 + background: #f3f3f3;
  83 +}
  84 +
  85 +.section {
  86 + padding: 32rpx;
  87 + background: #fff;
  88 + margin-bottom: 20rpx;
  89 +}
  90 +
  91 +.row {
  92 + display: flex;
  93 + margin-bottom: 28rpx;
  94 +
  95 + &:last-child {
  96 + margin-bottom: 0;
  97 + }
  98 +
  99 + &.company {
  100 + font-size: 36rpx;
  101 + font-weight: 600;
  102 + color: rgba(0, 0, 0, 0.9);
  103 + padding-top: 8rpx;
  104 + line-height: 50rpx;
  105 + margin-bottom: 32rpx;
  106 + }
  107 +
  108 + .label {
  109 + max-width: 420rpx;
  110 + margin-right: 20rpx;
  111 + line-height: 32rpx;
  112 + font-size: 28rpx;
  113 + color: rgba(0, 0, 0, 0.6);
  114 + }
  115 +
  116 + .value {
  117 + flex: 1;
  118 + line-height: 32rpx;
  119 + font-size: 28rpx;
  120 + color: rgba(0, 0, 0, 0.9);
  121 + text-align: right;
  122 + word-break: break-all;
  123 + }
  124 +}
  125 +</style>
... ...
... ... @@ -15,7 +15,7 @@
15 15 <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id"
16 16 :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError">
17 17 <template v-slot="{ item }">
18   - <view class="card">
  18 + <view class="card" @click.stop="onCardClick(item)">
19 19 <view class="card-header">
20 20 <text class="title omit2">{{ item.projectName || '-' }}</text>
21 21 </view>
... ... @@ -142,6 +142,9 @@ export default {
142 142 supplierName: payload.supplierName || ''
143 143 }
144 144 },
  145 + onCardClick(item) {
  146 + uni.navigateTo({ url: '/pages/quote-manage/detail?id=' + item.id })
  147 + },
145 148 fetchList({ pageIndex, pageSize, query }) {
146 149 const q = query || {}
147 150 const params = {
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <!-- 项目名称 -->
  6 + <uni-list-item class="select-item" :class="form.name ? 'is-filled' : 'is-empty'">
  7 + <template v-slot:body>
  8 + <view class="item-title"><text class="required">*</text><text>项目名称</text></view>
  9 + </template>
  10 + <template v-slot:footer>
  11 + <uni-easyinput v-model="form.name" :inputBorder="false" placeholder="请输入项目名称" />
  12 + </template>
  13 + </uni-list-item>
  14 +
  15 + <!-- 品种 -->
  16 + <uni-list-item class="select-item" :class="form.productId ? 'is-filled' : 'is-empty'" clickable
  17 + @click="openProductSheet" :rightText="form.productName || '请选择品种'" showArrow>
  18 + <template v-slot:body>
  19 + <view class="item-title"><text class="required">*</text><text>品种</text></view>
  20 + </template>
  21 + </uni-list-item>
  22 +
  23 + <!-- 数量 -->
  24 + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'">
  25 + <template v-slot:body>
  26 + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view>
  27 + </template>
  28 + <template v-slot:footer>
  29 + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" />
  30 + </template>
  31 + </uni-list-item>
  32 +
  33 + <!-- 开始时间 -->
  34 + <uni-list-item class="select-item" :class="form.startTime ? 'is-filled' : 'is-empty'">
  35 + <template v-slot:body>
  36 + <view class="item-title"><text class="required">*</text><text>开始时间</text></view>
  37 + </template>
  38 + <template v-slot:footer>
  39 + <uni-datetime-picker type="datetime" v-model="form.startTime" :hide-second="true" :clear-icon="false" />
  40 + </template>
  41 + </uni-list-item>
  42 +
  43 + <!-- 截止时间 -->
  44 + <uni-list-item class="select-item" :class="form.endTime ? 'is-filled' : 'is-empty'">
  45 + <template v-slot:body>
  46 + <view class="item-title"><text class="required">*</text><text>截止时间</text></view>
  47 + </template>
  48 + <template v-slot:footer>
  49 + <uni-datetime-picker type="datetime" v-model="form.endTime" :hide-second="true" :clear-icon="false" />
  50 + </template>
  51 + </uni-list-item>
  52 +
  53 + <!-- 项目类型 -->
  54 + <uni-list-item class="select-item" :class="form.projectType ? 'is-filled' : 'is-empty'" clickable
  55 + @click="openDictSheet('projectType', 'PROJECT_TYPE', '项目类型')" :rightText="getProjectTypeLabel() || '请选择项目类型'" showArrow>
  56 + <template v-slot:body>
  57 + <view class="item-title"><text class="required">*</text><text>项目类型</text></view>
  58 + </template>
  59 + </uni-list-item>
  60 +
  61 + <!-- 开标时间 - 仅项目类型为集中开标时显示 -->
  62 + <uni-list-item v-if="isConcentratedBid" class="select-item" :class="form.bidTime ? 'is-filled' : 'is-empty'">
  63 + <template v-slot:body>
  64 + <view class="item-title"><text class="required">*</text><text>开标时间</text></view>
  65 + </template>
  66 + <template v-slot:footer>
  67 + <uni-datetime-picker type="datetime" v-model="form.bidTime" :hide-second="true" :clear-icon="false" />
  68 + </template>
  69 + </uni-list-item>
  70 +
  71 + <!-- 采购规则 -->
  72 + <uni-list-item class="select-item textarea-item" :class="form.purchaseRule ? 'is-filled' : 'is-empty'">
  73 + <template v-slot:body>
  74 + <view class="item-title"><text class="required">*</text><text>采购规则</text></view>
  75 + </template>
  76 + <template v-slot:footer>
  77 + <uni-easyinput type="textarea" v-model="form.purchaseRule" :inputBorder="false"
  78 + placeholder="请输入采购规则" :autoHeight="true" />
  79 + </template>
  80 + </uni-list-item>
  81 + </uni-list>
  82 + </scroll-view>
  83 +
  84 + <view class="footer">
  85 + <button class="btn submit" @click="onSubmit">提交</button>
  86 + </view>
  87 +
  88 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  89 + v-model="sheet.value" @confirm="onSheetConfirm" />
  90 + </view>
  91 +</template>
  92 +
  93 +<script>
  94 +import SingleSelectSheet from '@/components/single-select/index.vue'
  95 +import { supplierQuotationProjectSaveApi } from '@/api/procure-manage/supplierQuotationProject.js'
  96 +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js'
  97 +import { getDicByCodes } from '@/utils/dic.js'
  98 +
  99 +export default {
  100 + name: 'AddSupplierPrice',
  101 + components: { SingleSelectSheet },
  102 + data() {
  103 + return {
  104 + form: {
  105 + name: '',
  106 + productId: '',
  107 + productName: '',
  108 + quantity: '',
  109 + startTime: '',
  110 + endTime: '',
  111 + projectType: '',
  112 + bidTime: '',
  113 + purchaseRule: ''
  114 + },
  115 + productList: [],
  116 + dictData: {},
  117 + sheet: {
  118 + visible: false,
  119 + title: '',
  120 + options: [],
  121 + value: '',
  122 + field: '',
  123 + isDict: false
  124 + }
  125 + }
  126 + },
  127 + computed: {
  128 + isConcentratedBid() {
  129 + // 项目类型为"集中开标"时显示开标时间
  130 + const opts = this.getDictOptions('PROJECT_TYPE') || []
  131 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  132 + const label = m ? (m.name || m.label || '') : ''
  133 + return label.indexOf('集中开标') > -1
  134 + }
  135 + },
  136 + onLoad() {
  137 + this.loadDictData()
  138 + this.loadProducts()
  139 + },
  140 + methods: {
  141 + async loadDictData() {
  142 + try {
  143 + const res = await getDicByCodes(['PROJECT_TYPE'])
  144 + this.dictData = res || {}
  145 + } catch (e) {
  146 + this.dictData = {}
  147 + }
  148 + },
  149 + getDictOptions(dictCode) {
  150 + const data = this.dictData[dictCode]
  151 + return (data && data.data) ? data.data : (Array.isArray(data) ? data : [])
  152 + },
  153 + async loadProducts() {
  154 + try {
  155 + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 })
  156 + const _data = res && res.data ? res.data : {}
  157 + const list = _data.datas || _data.list || _data.records || []
  158 + this.productList = list.map(it => ({
  159 + label: it.productName || it.name || '',
  160 + value: it.productId || it.id || ''
  161 + }))
  162 + } catch (e) {
  163 + this.productList = []
  164 + }
  165 + },
  166 + getProjectTypeLabel() {
  167 + const opts = this.getDictOptions('PROJECT_TYPE') || []
  168 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  169 + return m ? (m.name || m.label || '') : ''
  170 + },
  171 + openProductSheet() {
  172 + const opts = (this.productList || []).map(p => ({ label: p.label, value: p.value }))
  173 + const cur = this.form.productId
  174 + const match = opts.find(o => String(o.value) === String(cur))
  175 + this.sheet = {
  176 + visible: true,
  177 + title: '请选择品种',
  178 + options: opts,
  179 + value: match ? match.value : '',
  180 + field: 'productId',
  181 + isDict: false
  182 + }
  183 + },
  184 + openDictSheet(field, dictCode, title) {
  185 + const raw = this.getDictOptions(dictCode) || []
  186 + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') }))
  187 + const cur = this.form[field]
  188 + const match = opts.find(o => String(o.value) === String(cur))
  189 + this.sheet = {
  190 + visible: true,
  191 + title: title || '请选择',
  192 + options: opts,
  193 + value: match ? match.value : '',
  194 + field,
  195 + isDict: true
  196 + }
  197 + },
  198 + onSheetConfirm({ value }) {
  199 + const field = this.sheet.field
  200 + if (!field) return
  201 + this.form[field] = value
  202 + if (field === 'productId') {
  203 + const m = (this.productList || []).find(p => String(p.value) === String(value))
  204 + this.form.productName = m ? m.label : ''
  205 + }
  206 + // 切换项目类型时,若不再是集中开标则清空开标时间
  207 + if (field === 'projectType' && !this.isConcentratedBid) {
  208 + this.form.bidTime = ''
  209 + }
  210 + },
  211 + onQuantityBlur() {
  212 + let v = this.form.quantity
  213 + if (v === '' || v == null) return
  214 + v = parseFloat(v)
  215 + if (isNaN(v)) {
  216 + this.form.quantity = ''
  217 + return
  218 + }
  219 + this.form.quantity = String(v)
  220 + },
  221 + formatDateTimeMinute(v) {
  222 + const s = v == null ? '' : String(v).trim()
  223 + if (!s) return ''
  224 + if (s.length >= 16) return s.slice(0, 16)
  225 + return s
  226 + },
  227 + formatDateTimeSecond(v) {
  228 + const s = v == null ? '' : String(v).trim()
  229 + if (!s) return ''
  230 + // 截取到分钟,补齐秒
  231 + const minute = s.length >= 16 ? s.slice(0, 16) : s
  232 + return minute + ':00'
  233 + },
  234 + validateRequired() {
  235 + const checks = [
  236 + { key: 'name', msg: '请输入项目名称' },
  237 + { key: 'productId', msg: '请选择品种' },
  238 + { key: 'quantity', msg: '请输入数量' },
  239 + { key: 'startTime', msg: '请选择开始时间' },
  240 + { key: 'endTime', msg: '请选择截止时间' },
  241 + { key: 'projectType', msg: '请选择项目类型' },
  242 + { key: 'purchaseRule', msg: '请输入采购规则' }
  243 + ]
  244 + for (const it of checks) {
  245 + const val = this.form[it.key]
  246 + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === ''))
  247 + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false }
  248 + }
  249 + if (this.isConcentratedBid && !this.form.bidTime) {
  250 + uni.showToast({ title: '请选择开标时间', icon: 'none' })
  251 + return false
  252 + }
  253 + // 数量必须为有效数字
  254 + if (isNaN(parseFloat(this.form.quantity))) {
  255 + uni.showToast({ title: '数量格式不正确', icon: 'none' })
  256 + return false
  257 + }
  258 + // 开始时间不能晚于截止时间
  259 + if (this.form.startTime && this.form.endTime && this.formatDateTimeMinute(this.form.startTime) > this.formatDateTimeMinute(this.form.endTime)) {
  260 + uni.showToast({ title: '开始时间不能晚于截止时间', icon: 'none' })
  261 + return false
  262 + }
  263 + return true
  264 + },
  265 + async onSubmit() {
  266 + if (!this.validateRequired()) return
  267 + const confirmRes = await new Promise(resolve => {
  268 + uni.showModal({ title: '提示', content: '确定新增报价项目吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  269 + })
  270 + if (!(confirmRes && confirmRes.confirm)) return
  271 + const clean = (obj) => {
  272 + const out = {}
  273 + Object.keys(obj || {}).forEach(k => {
  274 + const v = obj[k]
  275 + const isEmptyString = typeof v === 'string' && v.trim() === ''
  276 + const isUndef = v === undefined || v === null
  277 + const isNaNNumber = typeof v === 'number' && isNaN(v)
  278 + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v
  279 + })
  280 + return out
  281 + }
  282 + const payload = clean({
  283 + name: this.form.name,
  284 + productId: this.form.productId,
  285 + productName: this.form.productName,
  286 + quantity: this.form.quantity,
  287 + startTime: this.formatDateTimeSecond(this.form.startTime),
  288 + endTime: this.formatDateTimeSecond(this.form.endTime),
  289 + projectType: this.form.projectType,
  290 + bidTime: this.isConcentratedBid ? this.formatDateTimeSecond(this.form.bidTime) : '',
  291 + purchaseRule: this.form.purchaseRule
  292 + })
  293 + try {
  294 + await supplierQuotationProjectSaveApi(payload)
  295 + uni.showToast({ title: '新增成功', icon: 'none' })
  296 + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {}
  297 + setTimeout(() => { uni.redirectTo({ url: '/pages/supplier-price/index' }) }, 400)
  298 + } catch (e) {
  299 + uni.showToast({ title: e.msg || '新增失败', icon: 'none' })
  300 + }
  301 + }
  302 + }
  303 +}
  304 +</script>
  305 +
  306 +<style lang="scss" scoped>
  307 +.page {
  308 + display: flex;
  309 + flex-direction: column;
  310 + height: 100%;
  311 +}
  312 +
  313 +.scroll {
  314 + flex: 1;
  315 + padding: 12rpx 0 200rpx !important;
  316 +}
  317 +
  318 +.footer {
  319 + z-index: 2;
  320 + position: fixed;
  321 + left: 0;
  322 + right: 0;
  323 + bottom: 0;
  324 + padding: 32rpx;
  325 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  326 + background: #fff;
  327 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  328 +
  329 + .btn {
  330 + height: 80rpx;
  331 + line-height: 80rpx;
  332 + border-radius: 12rpx;
  333 + font-size: 32rpx;
  334 + }
  335 +
  336 + .submit {
  337 + background: $theme-primary;
  338 + color: #fff;
  339 + }
  340 +}
  341 +
  342 +::v-deep .uni-list {
  343 + .uni-easyinput {
  344 + display: flex;
  345 +
  346 + .uni-input-input {
  347 + color: rgba(0, 0, 0, 0.9);
  348 + }
  349 + }
  350 +
  351 + .uni-input-placeholder {
  352 + z-index: 1;
  353 + }
  354 +
  355 + .uni-input-input {
  356 + background-color: #ffffff;
  357 + }
  358 +
  359 + background: transparent;
  360 +
  361 + &-item {
  362 + &__extra-text {
  363 + font-size: 32rpx;
  364 + }
  365 +
  366 + &__content-title {
  367 + font-size: 32rpx;
  368 + color: rgba(0, 0, 0, 0.9);
  369 + }
  370 +
  371 + &__container {
  372 + padding: 32rpx;
  373 +
  374 + .uni-easyinput {
  375 + &__placeholder-class {
  376 + font-size: 32rpx;
  377 + color: rgba(0, 0, 0, 0.4);
  378 + }
  379 +
  380 + &__content {
  381 + border: none;
  382 + background-color: #ffffff !important;
  383 +
  384 + &-input {
  385 + padding-left: 0 !important;
  386 + height: 48rpx;
  387 + line-height: 48rpx;
  388 + font-size: 32rpx;
  389 + }
  390 +
  391 + .content-clear-icon {
  392 + font-size: 44rpx !important;
  393 + }
  394 + }
  395 + }
  396 +
  397 + .item-title,
  398 + .uni-list-item__content {
  399 + flex: none;
  400 + min-height: 48rpx;
  401 + line-height: 48rpx;
  402 + font-size: 32rpx;
  403 + position: relative;
  404 + width: 240rpx;
  405 + margin-right: 32rpx;
  406 + color: rgba(0, 0, 0, 0.9);
  407 +
  408 + .required {
  409 + color: red;
  410 + position: absolute;
  411 + top: 50%;
  412 + transform: translateY(-50%);
  413 + left: -16rpx;
  414 + }
  415 + }
  416 + }
  417 +
  418 + &.select-item {
  419 + &.is-empty {
  420 + .uni-list-item__extra-text {
  421 + color: rgba(0, 0, 0, 0.4) !important;
  422 + }
  423 + }
  424 +
  425 + &.is-filled {
  426 + .uni-list-item__extra-text {
  427 + color: rgba(0, 0, 0, 0.9) !important;
  428 + }
  429 + }
  430 + }
  431 + }
  432 +}
  433 +
  434 +::v-deep .uni-datetime-picker {
  435 + flex: 1;
  436 +
  437 + .uni-input-wrapper {
  438 + justify-content: flex-end;
  439 + }
  440 +
  441 + .uni-input-input {
  442 + text-align: right;
  443 + font-size: 32rpx;
  444 + color: rgba(0, 0, 0, 0.9);
  445 + }
  446 +}
  447 +
  448 +.textarea-item {
  449 + ::v-deep .uni-list-item__container {
  450 + flex-direction: column;
  451 + align-items: stretch;
  452 +
  453 + .item-title {
  454 + width: auto;
  455 + margin-right: 0;
  456 + margin-bottom: 16rpx;
  457 + }
  458 +
  459 + .uni-easyinput {
  460 + width: 100%;
  461 + }
  462 +
  463 + .uni-textarea-textarea {
  464 + text-align: left;
  465 + min-height: 120rpx;
  466 + font-size: 32rpx;
  467 + color: rgba(0, 0, 0, 0.9);
  468 + }
  469 + }
  470 +}
  471 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <!-- 基础信息 -->
  5 + <view class="section">
  6 + <text class="row company">{{ safeText(form.name) }}</text>
  7 + <view class="row"><text class="label">编号</text><text class="value">{{ safeText(form.code) }}</text></view>
  8 + <view class="row"><text class="label">品种</text><text class="value">{{ safeText(form.productName) }}</text></view>
  9 + <view class="row"><text class="label">数量(吨)</text><text class="value">{{ safeText(form.quantity) }}</text></view>
  10 + <view class="row"><text class="label">开始时间</text><text class="value">{{ formatDateTimeMinute(form.startTime) }}</text></view>
  11 + <view class="row"><text class="label">截止时间</text><text class="value">{{ formatDateTimeMinute(form.endTime) }}</text></view>
  12 + <view class="row"><text class="label">项目类型</text><text class="value">{{ getProjectTypeLabel() }}</text></view>
  13 + <view v-if="showBidTime" class="row"><text class="label">开标时间</text><text class="value">{{ formatDateTimeMinute(form.bidTime) }}</text></view>
  14 + <view class="row"><text class="label">采购规则</text><text class="value">{{ safeText(form.purchaseRule) }}</text></view>
  15 + <view class="row"><text class="label">状态</text><text class="value">{{ safeText(form.status) }}</text></view>
  16 + <view class="row"><text class="label">发布人</text><text class="value">{{ safeText(form.createBy) }}</text></view>
  17 + </view>
  18 + </scroll-view>
  19 +
  20 + <detail-buttons :buttons="displayButtons" @click="handleButtonClick" />
  21 + </view>
  22 +</template>
  23 +
  24 +<script>
  25 +import DetailButtons from '@/components/detail-buttons/index.vue'
  26 +import { supplierQuotationProjectDetailApi, supplierQuotationProjectUpdateStatusApi } from '@/api/procure-manage/supplierQuotationProject.js'
  27 +import { getDicByCodes } from '@/utils/dic.js'
  28 +
  29 +export default {
  30 + name: 'DetailSupplierPrice',
  31 + components: { DetailButtons },
  32 + data() {
  33 + return {
  34 + id: '',
  35 + form: {},
  36 + dictData: {},
  37 + buttons: [{
  38 + text: '编辑',
  39 + visible: true,
  40 + variant: 'primary',
  41 + event: 'edit'
  42 + },{
  43 + text: '报价',
  44 + visible: true,
  45 + event: 'bid'
  46 + },{
  47 + text: '发布',
  48 + visible: true,
  49 + event: 'publish'
  50 + }]
  51 + }
  52 + },
  53 + computed: {
  54 + showBidTime() {
  55 + const opts = (this.dictData.PROJECT_TYPE && this.dictData.PROJECT_TYPE.data) || []
  56 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  57 + const label = m ? (m.name || m.label || '') : ''
  58 + return label.indexOf('集中开标') > -1
  59 + },
  60 + displayButtons() {
  61 + const s = this.form.status || ''
  62 + return [
  63 + { ...this.buttons[0], visible: this.$auth.hasPermi('procure-manage:supplier-price:modify') && s === 'UNPUBLISHED' },
  64 + { ...this.buttons[1], visible: this.$auth.hasPermi('procure-manage:supplier-price:quotation') && s === 'QUOTING' },
  65 + { ...this.buttons[2], visible: this.$auth.hasPermi('procure-manage:supplier-price:publish') && s === 'UNPUBLISHED' }
  66 + ]
  67 + }
  68 + },
  69 + onLoad(options) {
  70 + this.id = (options && options.id) ? options.id : ''
  71 + if (!this.id) return
  72 + this.loadDictData().then(() => {
  73 + this.loadDetail()
  74 + })
  75 + },
  76 + methods: {
  77 + async loadDictData() {
  78 + try {
  79 + const res = await getDicByCodes(['PROJECT_TYPE'])
  80 + this.dictData = res || {}
  81 + } catch (e) {
  82 + this.dictData = {}
  83 + }
  84 + },
  85 + getProjectTypeLabel() {
  86 + const opts = (this.dictData.PROJECT_TYPE && this.dictData.PROJECT_TYPE.data) || []
  87 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  88 + return m ? (m.name || m.label || '-') : '-'
  89 + },
  90 + async loadDetail() {
  91 + try {
  92 + const res = await supplierQuotationProjectDetailApi({ id: this.id })
  93 + this.form = (res && res.data) ? res.data : {}
  94 + } catch (e) {
  95 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  96 + }
  97 + },
  98 + formatDateTimeMinute(v) {
  99 + const s = v == null ? '' : String(v).trim()
  100 + if (!s) return '-'
  101 + if (s.length >= 16) return s.slice(0, 16)
  102 + return s
  103 + },
  104 + safeText(v) {
  105 + const s = v == null ? '' : String(v)
  106 + return s.trim() ? s : '-'
  107 + },
  108 + handleButtonClick(btn) {
  109 + if (!btn || btn.disabled) return
  110 + const e = btn.event || ''
  111 + if (e === 'edit') return this.onEdit()
  112 + if (e === 'bid') return this.onQuote()
  113 + if (e === 'publish') return this.onPublish()
  114 + },
  115 + onEdit() {
  116 + uni.navigateTo({ url: '/pages/supplier-price/modify?id=' + this.id })
  117 + },
  118 + onQuote() {
  119 + uni.navigateTo({ url: '/pages/supplier-price/quote?id=' + this.id })
  120 + },
  121 + async onPublish() {
  122 + const confirmRes = await new Promise(resolve => {
  123 + uni.showModal({ title: '提示', content: '确定发布该项目吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  124 + })
  125 + if (!(confirmRes && confirmRes.confirm)) return
  126 + try {
  127 + await supplierQuotationProjectUpdateStatusApi({ id: this.id, status: 'PUBLISHED' })
  128 + uni.showToast({ title: '发布成功', icon: 'none' })
  129 + this.loadDetail()
  130 + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {}
  131 + } catch (e) {
  132 + uni.showToast({ title: e.msg || '发布失败', icon: 'none' })
  133 + }
  134 + }
  135 + }
  136 +}
  137 +</script>
  138 +
  139 +<style lang="scss" scoped>
  140 +.page {
  141 + display: flex;
  142 + flex-direction: column;
  143 + height: 100vh;
  144 + background: #f3f3f3;
  145 +}
  146 +
  147 +.scroll {
  148 + flex: 1;
  149 + padding: 8rpx 0 144rpx;
  150 +}
  151 +
  152 +.section {
  153 + padding: 32rpx;
  154 + background: #fff;
  155 + margin-bottom: 20rpx;
  156 +}
  157 +
  158 +.row {
  159 + display: flex;
  160 + margin-bottom: 28rpx;
  161 +
  162 + &:last-child {
  163 + margin-bottom: 0;
  164 + }
  165 +
  166 + &.company {
  167 + font-size: 36rpx;
  168 + font-weight: 600;
  169 + color: rgba(0, 0, 0, 0.9);
  170 + padding-top: 8rpx;
  171 + line-height: 50rpx;
  172 + }
  173 +
  174 + .label {
  175 + max-width: 420rpx;
  176 + margin-right: 20rpx;
  177 + line-height: 32rpx;
  178 + font-size: 28rpx;
  179 + color: rgba(0, 0, 0, 0.6);
  180 + }
  181 +
  182 + .value {
  183 + flex: 1;
  184 + line-height: 32rpx;
  185 + font-size: 28rpx;
  186 + color: rgba(0, 0, 0, 0.9);
  187 + text-align: right;
  188 + word-break: break-all;
  189 + }
  190 +}
  191 +</style>
... ...
... ... @@ -7,6 +7,7 @@
7 7 @input="onSearchInput" />
8 8 <view class="tool-icons">
9 9 <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  10 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
10 11 </view>
11 12 </view>
12 13 </view>
... ... @@ -188,6 +189,9 @@ export default {
188 189 },
189 190 onCardClick(item) {
190 191 uni.navigateTo({ url: '/pages/supplier-price/detail?id=' + item.id })
  192 + },
  193 + onAdd() {
  194 + uni.navigateTo({ url: '/pages/supplier-price/add' })
191 195 }
192 196 }
193 197 }
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <!-- 项目名称 -->
  6 + <uni-list-item class="select-item" :class="form.name ? 'is-filled' : 'is-empty'">
  7 + <template v-slot:body>
  8 + <view class="item-title"><text class="required">*</text><text>项目名称</text></view>
  9 + </template>
  10 + <template v-slot:footer>
  11 + <uni-easyinput v-model="form.name" :inputBorder="false" placeholder="请输入项目名称" />
  12 + </template>
  13 + </uni-list-item>
  14 +
  15 + <!-- 品种 -->
  16 + <uni-list-item class="select-item" :class="form.productId ? 'is-filled' : 'is-empty'" clickable
  17 + @click="openProductSheet" :rightText="form.productName || '请选择品种'" showArrow>
  18 + <template v-slot:body>
  19 + <view class="item-title"><text class="required">*</text><text>品种</text></view>
  20 + </template>
  21 + </uni-list-item>
  22 +
  23 + <!-- 数量 -->
  24 + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'">
  25 + <template v-slot:body>
  26 + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view>
  27 + </template>
  28 + <template v-slot:footer>
  29 + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" />
  30 + </template>
  31 + </uni-list-item>
  32 +
  33 + <!-- 开始时间 -->
  34 + <uni-list-item class="select-item" :class="form.startTime ? 'is-filled' : 'is-empty'">
  35 + <template v-slot:body>
  36 + <view class="item-title"><text class="required">*</text><text>开始时间</text></view>
  37 + </template>
  38 + <template v-slot:footer>
  39 + <uni-datetime-picker type="datetime" v-model="form.startTime" :hide-second="true" :clear-icon="false" />
  40 + </template>
  41 + </uni-list-item>
  42 +
  43 + <!-- 截止时间 -->
  44 + <uni-list-item class="select-item" :class="form.endTime ? 'is-filled' : 'is-empty'">
  45 + <template v-slot:body>
  46 + <view class="item-title"><text class="required">*</text><text>截止时间</text></view>
  47 + </template>
  48 + <template v-slot:footer>
  49 + <uni-datetime-picker type="datetime" v-model="form.endTime" :hide-second="true" :clear-icon="false" />
  50 + </template>
  51 + </uni-list-item>
  52 +
  53 + <!-- 项目类型 -->
  54 + <uni-list-item class="select-item" :class="form.projectType ? 'is-filled' : 'is-empty'" clickable
  55 + @click="openDictSheet('projectType', 'PROJECT_TYPE', '项目类型')" :rightText="getProjectTypeLabel() || '请选择项目类型'" showArrow>
  56 + <template v-slot:body>
  57 + <view class="item-title"><text class="required">*</text><text>项目类型</text></view>
  58 + </template>
  59 + </uni-list-item>
  60 +
  61 + <!-- 开标时间 - 仅项目类型为集中开标时显示 -->
  62 + <uni-list-item v-if="isConcentratedBid" class="select-item" :class="form.bidTime ? 'is-filled' : 'is-empty'">
  63 + <template v-slot:body>
  64 + <view class="item-title"><text class="required">*</text><text>开标时间</text></view>
  65 + </template>
  66 + <template v-slot:footer>
  67 + <uni-datetime-picker type="datetime" v-model="form.bidTime" :hide-second="true" :clear-icon="false" />
  68 + </template>
  69 + </uni-list-item>
  70 +
  71 + <!-- 采购规则 -->
  72 + <uni-list-item class="select-item textarea-item" :class="form.purchaseRule ? 'is-filled' : 'is-empty'">
  73 + <template v-slot:body>
  74 + <view class="item-title"><text class="required">*</text><text>采购规则</text></view>
  75 + </template>
  76 + <template v-slot:footer>
  77 + <uni-easyinput type="textarea" v-model="form.purchaseRule" :inputBorder="false"
  78 + placeholder="请输入采购规则" :autoHeight="true" />
  79 + </template>
  80 + </uni-list-item>
  81 + </uni-list>
  82 + </scroll-view>
  83 +
  84 + <view class="footer">
  85 + <button class="btn submit" @click="onSubmit">保存</button>
  86 + </view>
  87 +
  88 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  89 + v-model="sheet.value" @confirm="onSheetConfirm" />
  90 + </view>
  91 +</template>
  92 +
  93 +<script>
  94 +import SingleSelectSheet from '@/components/single-select/index.vue'
  95 +import { supplierQuotationProjectDetailApi, supplierQuotationProjectUpdateApi } from '@/api/procure-manage/supplierQuotationProject.js'
  96 +import { breedRelationshipQueryApi } from '@/api/procure-manage/salesmanQuotation.js'
  97 +import { getDicByCodes } from '@/utils/dic.js'
  98 +
  99 +export default {
  100 + name: 'ModifySupplierPrice',
  101 + components: { SingleSelectSheet },
  102 + data() {
  103 + return {
  104 + id: '',
  105 + form: {
  106 + id: '',
  107 + name: '',
  108 + code: '',
  109 + productId: '',
  110 + productName: '',
  111 + quantity: '',
  112 + startTime: '',
  113 + endTime: '',
  114 + projectType: '',
  115 + bidTime: '',
  116 + purchaseRule: ''
  117 + },
  118 + productList: [],
  119 + dictData: {},
  120 + sheet: {
  121 + visible: false,
  122 + title: '',
  123 + options: [],
  124 + value: '',
  125 + field: '',
  126 + isDict: false
  127 + }
  128 + }
  129 + },
  130 + computed: {
  131 + isConcentratedBid() {
  132 + const opts = this.getDictOptions('PROJECT_TYPE') || []
  133 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  134 + const label = m ? (m.name || m.label || '') : ''
  135 + return label.indexOf('集中开标') > -1
  136 + }
  137 + },
  138 + onLoad(options) {
  139 + this.id = (options && options.id) ? options.id : ''
  140 + this.loadDictData().then(() => {
  141 + this.loadProducts().then(() => {
  142 + if (this.id) this.loadDetail()
  143 + })
  144 + })
  145 + },
  146 + methods: {
  147 + async loadDictData() {
  148 + try {
  149 + const res = await getDicByCodes(['PROJECT_TYPE'])
  150 + this.dictData = res || {}
  151 + } catch (e) {
  152 + this.dictData = {}
  153 + }
  154 + },
  155 + getDictOptions(dictCode) {
  156 + const data = this.dictData[dictCode]
  157 + return (data && data.data) ? data.data : (Array.isArray(data) ? data : [])
  158 + },
  159 + async loadProducts() {
  160 + try {
  161 + const res = await breedRelationshipQueryApi({ pageIndex: 1, pageSize: 9999 })
  162 + const _data = res && res.data ? res.data : {}
  163 + const list = _data.datas || _data.list || _data.records || []
  164 + this.productList = list.map(it => ({
  165 + label: it.productName || it.name || '',
  166 + value: it.productId || it.id || ''
  167 + }))
  168 + } catch (e) {
  169 + this.productList = []
  170 + }
  171 + },
  172 + getProjectTypeLabel() {
  173 + const opts = this.getDictOptions('PROJECT_TYPE') || []
  174 + const m = opts.find(o => String(o.code != null ? o.code : o.value) === String(this.form.projectType))
  175 + return m ? (m.name || m.label || '') : ''
  176 + },
  177 + openProductSheet() {
  178 + const opts = (this.productList || []).map(p => ({ label: p.label, value: p.value }))
  179 + const cur = this.form.productId
  180 + const match = opts.find(o => String(o.value) === String(cur))
  181 + this.sheet = {
  182 + visible: true,
  183 + title: '请选择品种',
  184 + options: opts,
  185 + value: match ? match.value : '',
  186 + field: 'productId',
  187 + isDict: false
  188 + }
  189 + },
  190 + openDictSheet(field, dictCode, title) {
  191 + const raw = this.getDictOptions(dictCode) || []
  192 + const opts = raw.map(o => ({ label: o.name || o.label || '', value: o.code != null ? o.code : (o.value != null ? o.value : '') }))
  193 + const cur = this.form[field]
  194 + const match = opts.find(o => String(o.value) === String(cur))
  195 + this.sheet = {
  196 + visible: true,
  197 + title: title || '请选择',
  198 + options: opts,
  199 + value: match ? match.value : '',
  200 + field,
  201 + isDict: true
  202 + }
  203 + },
  204 + onSheetConfirm({ value }) {
  205 + const field = this.sheet.field
  206 + if (!field) return
  207 + this.form[field] = value
  208 + if (field === 'productId') {
  209 + const m = (this.productList || []).find(p => String(p.value) === String(value))
  210 + this.form.productName = m ? m.label : ''
  211 + }
  212 + if (field === 'projectType' && !this.isConcentratedBid) {
  213 + this.form.bidTime = ''
  214 + }
  215 + },
  216 + onQuantityBlur() {
  217 + let v = this.form.quantity
  218 + if (v === '' || v == null) return
  219 + v = parseFloat(v)
  220 + if (isNaN(v)) {
  221 + this.form.quantity = ''
  222 + return
  223 + }
  224 + this.form.quantity = String(v)
  225 + },
  226 + formatDateTimeMinute(v) {
  227 + const s = v == null ? '' : String(v).trim()
  228 + if (!s) return ''
  229 + if (s.length >= 16) return s.slice(0, 16)
  230 + return s
  231 + },
  232 + formatDateTimeSecond(v) {
  233 + const s = v == null ? '' : String(v).trim()
  234 + if (!s) return ''
  235 + const minute = s.length >= 16 ? s.slice(0, 16) : s
  236 + return minute + ':00'
  237 + },
  238 + async loadDetail() {
  239 + try {
  240 + const res = await supplierQuotationProjectDetailApi({ id: this.id })
  241 + const d = (res && res.data) ? res.data : {}
  242 + // 时间格式截取到分钟用于选择器回显
  243 + if (d.startTime && d.startTime.length >= 16) d.startTime = d.startTime.slice(0, 16)
  244 + if (d.endTime && d.endTime.length >= 16) d.endTime = d.endTime.slice(0, 16)
  245 + if (d.bidTime && d.bidTime.length >= 16) d.bidTime = d.bidTime.slice(0, 16)
  246 + this.form = { ...this.form, ...d }
  247 + } catch (e) {
  248 + uni.showToast({ title: '加载详情失败', icon: 'none' })
  249 + }
  250 + },
  251 + validateRequired() {
  252 + const checks = [
  253 + { key: 'name', msg: '请输入项目名称' },
  254 + { key: 'productId', msg: '请选择品种' },
  255 + { key: 'quantity', msg: '请输入数量' },
  256 + { key: 'startTime', msg: '请选择开始时间' },
  257 + { key: 'endTime', msg: '请选择截止时间' },
  258 + { key: 'projectType', msg: '请选择项目类型' },
  259 + { key: 'purchaseRule', msg: '请输入采购规则' }
  260 + ]
  261 + for (const it of checks) {
  262 + const val = this.form[it.key]
  263 + const empty = (val === undefined || val === null || (typeof val === 'string' && val.trim() === ''))
  264 + if (empty) { uni.showToast({ title: it.msg, icon: 'none' }); return false }
  265 + }
  266 + if (this.isConcentratedBid && !this.form.bidTime) {
  267 + uni.showToast({ title: '请选择开标时间', icon: 'none' })
  268 + return false
  269 + }
  270 + if (isNaN(parseFloat(this.form.quantity))) {
  271 + uni.showToast({ title: '数量格式不正确', icon: 'none' })
  272 + return false
  273 + }
  274 + if (this.form.startTime && this.form.endTime && this.formatDateTimeMinute(this.form.startTime) > this.formatDateTimeMinute(this.form.endTime)) {
  275 + uni.showToast({ title: '开始时间不能晚于截止时间', icon: 'none' })
  276 + return false
  277 + }
  278 + return true
  279 + },
  280 + async onSubmit() {
  281 + if (!this.validateRequired()) return
  282 + const confirmRes = await new Promise(resolve => {
  283 + uni.showModal({ title: '提示', content: '确定保存修改吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  284 + })
  285 + if (!(confirmRes && confirmRes.confirm)) return
  286 + const clean = (obj) => {
  287 + const out = {}
  288 + Object.keys(obj || {}).forEach(k => {
  289 + const v = obj[k]
  290 + const isEmptyString = typeof v === 'string' && v.trim() === ''
  291 + const isUndef = v === undefined || v === null
  292 + const isNaNNumber = typeof v === 'number' && isNaN(v)
  293 + if (!(isEmptyString || isUndef || isNaNNumber)) out[k] = v
  294 + })
  295 + return out
  296 + }
  297 + const payload = clean({
  298 + id: this.form.id,
  299 + name: this.form.name,
  300 + productId: this.form.productId,
  301 + productName: this.form.productName,
  302 + quantity: this.form.quantity,
  303 + startTime: this.formatDateTimeSecond(this.form.startTime),
  304 + endTime: this.formatDateTimeSecond(this.form.endTime),
  305 + projectType: this.form.projectType,
  306 + bidTime: this.isConcentratedBid ? this.formatDateTimeSecond(this.form.bidTime) : '',
  307 + purchaseRule: this.form.purchaseRule
  308 + })
  309 + try {
  310 + await supplierQuotationProjectUpdateApi(payload)
  311 + uni.showToast({ title: '保存成功', icon: 'none' })
  312 + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {}
  313 + setTimeout(() => { uni.navigateBack() }, 400)
  314 + } catch (e) {
  315 + uni.showToast({ title: e.msg || '保存失败', icon: 'none' })
  316 + }
  317 + }
  318 + }
  319 +}
  320 +</script>
  321 +
  322 +<style lang="scss" scoped>
  323 +.page {
  324 + display: flex;
  325 + flex-direction: column;
  326 + height: 100%;
  327 +}
  328 +
  329 +.scroll {
  330 + flex: 1;
  331 + padding: 12rpx 0 200rpx !important;
  332 +}
  333 +
  334 +.footer {
  335 + z-index: 2;
  336 + position: fixed;
  337 + left: 0;
  338 + right: 0;
  339 + bottom: 0;
  340 + padding: 32rpx;
  341 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  342 + background: #fff;
  343 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  344 +
  345 + .btn {
  346 + height: 80rpx;
  347 + line-height: 80rpx;
  348 + border-radius: 12rpx;
  349 + font-size: 32rpx;
  350 + }
  351 +
  352 + .submit {
  353 + background: $theme-primary;
  354 + color: #fff;
  355 + }
  356 +}
  357 +
  358 +::v-deep .uni-list {
  359 + .uni-easyinput {
  360 + display: flex;
  361 +
  362 + .uni-input-input {
  363 + color: rgba(0, 0, 0, 0.9);
  364 + }
  365 + }
  366 +
  367 + .uni-input-placeholder {
  368 + z-index: 1;
  369 + }
  370 +
  371 + .uni-input-input {
  372 + background-color: #ffffff;
  373 + }
  374 +
  375 + background: transparent;
  376 +
  377 + &-item {
  378 + &__extra-text {
  379 + font-size: 32rpx;
  380 + }
  381 +
  382 + &__content-title {
  383 + font-size: 32rpx;
  384 + color: rgba(0, 0, 0, 0.9);
  385 + }
  386 +
  387 + &__container {
  388 + padding: 32rpx;
  389 +
  390 + .uni-easyinput {
  391 + &__placeholder-class {
  392 + font-size: 32rpx;
  393 + color: rgba(0, 0, 0, 0.4);
  394 + }
  395 +
  396 + &__content {
  397 + border: none;
  398 + background-color: #ffffff !important;
  399 +
  400 + &-input {
  401 + padding-left: 0 !important;
  402 + height: 48rpx;
  403 + line-height: 48rpx;
  404 + font-size: 32rpx;
  405 + }
  406 +
  407 + .content-clear-icon {
  408 + font-size: 44rpx !important;
  409 + }
  410 + }
  411 + }
  412 +
  413 + .item-title,
  414 + .uni-list-item__content {
  415 + flex: none;
  416 + min-height: 48rpx;
  417 + line-height: 48rpx;
  418 + font-size: 32rpx;
  419 + position: relative;
  420 + width: 240rpx;
  421 + margin-right: 32rpx;
  422 + color: rgba(0, 0, 0, 0.9);
  423 +
  424 + .required {
  425 + color: red;
  426 + position: absolute;
  427 + top: 50%;
  428 + transform: translateY(-50%);
  429 + left: -16rpx;
  430 + }
  431 + }
  432 + }
  433 +
  434 + &.select-item {
  435 + &.is-empty {
  436 + .uni-list-item__extra-text {
  437 + color: rgba(0, 0, 0, 0.4) !important;
  438 + }
  439 + }
  440 +
  441 + &.is-filled {
  442 + .uni-list-item__extra-text {
  443 + color: rgba(0, 0, 0, 0.9) !important;
  444 + }
  445 + }
  446 + }
  447 + }
  448 +}
  449 +
  450 +::v-deep .uni-datetime-picker {
  451 + flex: 1;
  452 +
  453 + .uni-input-wrapper {
  454 + justify-content: flex-end;
  455 + }
  456 +
  457 + .uni-input-input {
  458 + text-align: right;
  459 + font-size: 32rpx;
  460 + color: rgba(0, 0, 0, 0.9);
  461 + }
  462 +}
  463 +
  464 +.textarea-item {
  465 + ::v-deep .uni-list-item__container {
  466 + flex-direction: column;
  467 + align-items: stretch;
  468 +
  469 + .item-title {
  470 + width: auto;
  471 + margin-right: 0;
  472 + margin-bottom: 16rpx;
  473 + }
  474 +
  475 + .uni-easyinput {
  476 + width: 100%;
  477 + }
  478 +
  479 + .uni-textarea-textarea {
  480 + text-align: left;
  481 + min-height: 120rpx;
  482 + font-size: 32rpx;
  483 + color: rgba(0, 0, 0, 0.9);
  484 + }
  485 + }
  486 +}
  487 +</style>
... ...
  1 +<template>
  2 + <view class="page">
  3 + <scroll-view class="scroll" scroll-y>
  4 + <uni-list>
  5 + <uni-list-item class="select-item is-filled">
  6 + <template v-slot:body>
  7 + <view class="item-title">项目名称</view>
  8 + </template>
  9 + <template v-slot:footer>
  10 + <text class="readonly-text">{{ form.name || '-' }}</text>
  11 + </template>
  12 + </uni-list-item>
  13 + <uni-list-item class="select-item is-filled">
  14 + <template v-slot:body>
  15 + <view class="item-title">开始时间</view>
  16 + </template>
  17 + <template v-slot:footer>
  18 + <text class="readonly-text">{{ form.startTime || '-' }}</text>
  19 + </template>
  20 + </uni-list-item>
  21 + <uni-list-item class="select-item is-filled">
  22 + <template v-slot:body>
  23 + <view class="item-title">截止时间</view>
  24 + </template>
  25 + <template v-slot:footer>
  26 + <text class="readonly-text">{{ form.endTime || '-' }}</text>
  27 + </template>
  28 + </uni-list-item>
  29 + <!-- 品种 -->
  30 + <uni-list-item class="select-item is-filled">
  31 + <template v-slot:body>
  32 + <view class="item-title"><text class="required">*</text><text>品种</text></view>
  33 + </template>
  34 + <template v-slot:footer>
  35 + <text class="readonly-text">{{ form.productName || '-' }}</text>
  36 + </template>
  37 + </uni-list-item>
  38 +
  39 + <!-- 数量 -->
  40 + <uni-list-item class="select-item" :class="form.quantity !== '' && form.quantity != null ? 'is-filled' : 'is-empty'">
  41 + <template v-slot:body>
  42 + <view class="item-title"><text class="required">*</text><text>数量(吨)</text></view>
  43 + </template>
  44 + <template v-slot:footer>
  45 + <uni-easyinput v-model="form.quantity" type="digit" :inputBorder="false" placeholder="请输入数量" @blur="onQuantityBlur" />
  46 + </template>
  47 + </uni-list-item>
  48 +
  49 + <!-- 价格 -->
  50 + <uni-list-item class="select-item" :class="form.price !== '' && form.price != null ? 'is-filled' : 'is-empty'">
  51 + <template v-slot:body>
  52 + <view class="item-title"><text class="required">*</text><text>价格</text></view>
  53 + </template>
  54 + <template v-slot:footer>
  55 + <uni-easyinput v-model="form.price" type="digit" :inputBorder="false" placeholder="请输入价格" @blur="onPriceBlur" />
  56 + </template>
  57 + </uni-list-item>
  58 + </uni-list>
  59 + </scroll-view>
  60 +
  61 + <view class="footer">
  62 + <button class="btn submit" @click="onSubmit">提交报价</button>
  63 + </view>
  64 + </view>
  65 +</template>
  66 +
  67 +<script>
  68 +import { supplierQuotationProjectDetailApi, supplierQuotationProjectQuoteApi } from '@/api/procure-manage/supplierQuotationProject.js'
  69 +
  70 +export default {
  71 + name: 'QuoteSupplierPrice',
  72 + data() {
  73 + return {
  74 + projectId: '',
  75 + form: {
  76 + projectId: '',
  77 + productId: '',
  78 + productName: '',
  79 + quantity: '',
  80 + price: '',
  81 + startTime: '',
  82 + endTime: '',
  83 + name: ''
  84 + }
  85 + }
  86 + },
  87 + onLoad(options) {
  88 + this.projectId = (options && options.id) ? options.id : ''
  89 + this.form.projectId = this.projectId
  90 + if (!this.projectId) return
  91 + this.loadDetail()
  92 + },
  93 + methods: {
  94 + async loadDetail() {
  95 + try {
  96 + const res = await supplierQuotationProjectDetailApi({ id: this.projectId })
  97 + const d = (res && res.data) ? res.data : {}
  98 + this.form.productId = d.productId || ''
  99 + this.form.productName = d.productName || ''
  100 + this.form.name = d.name || ''
  101 + this.form.startTime = d.startTime?.substring(0, 16) || ''
  102 + this.form.endTime = d.endTime?.substring(0, 16) || ''
  103 + // 数量默认带出项目数量,可修改
  104 + if (d.quantity != null && d.quantity !== '') this.form.quantity = String(d.quantity)
  105 + } catch (e) {
  106 + uni.showToast({ title: '加载项目信息失败', icon: 'none' })
  107 + }
  108 + },
  109 + onQuantityBlur() {
  110 + let v = this.form.quantity
  111 + if (v === '' || v == null) return
  112 + v = parseFloat(v)
  113 + if (isNaN(v)) { this.form.quantity = ''; return }
  114 + this.form.quantity = String(v)
  115 + },
  116 + onPriceBlur() {
  117 + let v = this.form.price
  118 + if (v === '' || v == null) return
  119 + v = parseFloat(v)
  120 + if (isNaN(v)) { this.form.price = ''; return }
  121 + this.form.price = String(v)
  122 + },
  123 + validateRequired() {
  124 + if (!this.form.projectId) { uni.showToast({ title: '缺少项目ID', icon: 'none' }); return false }
  125 + if (!this.form.productId) { uni.showToast({ title: '缺少品种', icon: 'none' }); return false }
  126 + if (this.form.quantity === '' || this.form.quantity == null) { uni.showToast({ title: '请输入数量', icon: 'none' }); return false }
  127 + if (isNaN(parseFloat(this.form.quantity))) { uni.showToast({ title: '数量格式不正确', icon: 'none' }); return false }
  128 + if (this.form.price === '' || this.form.price == null) { uni.showToast({ title: '请输入价格', icon: 'none' }); return false }
  129 + if (isNaN(parseFloat(this.form.price))) { uni.showToast({ title: '价格格式不正确', icon: 'none' }); return false }
  130 + return true
  131 + },
  132 + async onSubmit() {
  133 + if (!this.validateRequired()) return
  134 + const confirmRes = await new Promise(resolve => {
  135 + uni.showModal({ title: '提示', content: '确定提交报价吗?', confirmText: '确定', cancelText: '取消', success: resolve })
  136 + })
  137 + if (!(confirmRes && confirmRes.confirm)) return
  138 + const payload = {
  139 + projectId: this.form.projectId,
  140 + productId: this.form.productId,
  141 + quantity: parseFloat(this.form.quantity),
  142 + price: parseFloat(this.form.price)
  143 + }
  144 + try {
  145 + await supplierQuotationProjectQuoteApi(payload)
  146 + uni.showToast({ title: '报价成功', icon: 'none' })
  147 + try { uni.setStorageSync('SUPPLIER_PRICE_LIST_NEED_REFRESH', '1') } catch (e) {}
  148 + setTimeout(() => { uni.navigateBack() }, 400)
  149 + } catch (e) {
  150 + uni.showToast({ title: e.msg || '报价失败', icon: 'none' })
  151 + }
  152 + }
  153 + }
  154 +}
  155 +</script>
  156 +
  157 +<style lang="scss" scoped>
  158 +.page {
  159 + display: flex;
  160 + flex-direction: column;
  161 + height: 100%;
  162 +}
  163 +
  164 +.scroll {
  165 + flex: 1;
  166 + padding: 12rpx 0 200rpx !important;
  167 +}
  168 +
  169 +.footer {
  170 + z-index: 2;
  171 + position: fixed;
  172 + left: 0;
  173 + right: 0;
  174 + bottom: 0;
  175 + padding: 32rpx;
  176 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  177 + background: #fff;
  178 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  179 +
  180 + .btn {
  181 + height: 80rpx;
  182 + line-height: 80rpx;
  183 + border-radius: 12rpx;
  184 + font-size: 32rpx;
  185 + }
  186 +
  187 + .submit {
  188 + background: $theme-primary;
  189 + color: #fff;
  190 + }
  191 +}
  192 +
  193 +::v-deep .uni-list {
  194 + .uni-easyinput {
  195 + display: flex;
  196 +
  197 + .uni-input-input {
  198 + color: rgba(0, 0, 0, 0.9);
  199 + }
  200 + }
  201 +
  202 + .uni-input-placeholder {
  203 + z-index: 1;
  204 + }
  205 +
  206 + .uni-input-input {
  207 + background-color: #ffffff;
  208 + }
  209 +
  210 + background: transparent;
  211 +
  212 + &-item {
  213 + &__extra-text {
  214 + font-size: 32rpx;
  215 + }
  216 +
  217 + &__content-title {
  218 + font-size: 32rpx;
  219 + color: rgba(0, 0, 0, 0.9);
  220 + }
  221 +
  222 + &__container {
  223 + padding: 32rpx;
  224 +
  225 + .uni-easyinput {
  226 + &__placeholder-class {
  227 + font-size: 32rpx;
  228 + color: rgba(0, 0, 0, 0.4);
  229 + }
  230 +
  231 + &__content {
  232 + border: none;
  233 + background-color: #ffffff !important;
  234 +
  235 + &-input {
  236 + padding-left: 0 !important;
  237 + height: 48rpx;
  238 + line-height: 48rpx;
  239 + font-size: 32rpx;
  240 + }
  241 +
  242 + .content-clear-icon {
  243 + font-size: 44rpx !important;
  244 + }
  245 + }
  246 + }
  247 +
  248 + .item-title,
  249 + .uni-list-item__content {
  250 + flex: none;
  251 + min-height: 48rpx;
  252 + line-height: 48rpx;
  253 + font-size: 32rpx;
  254 + position: relative;
  255 + width: 240rpx;
  256 + margin-right: 32rpx;
  257 + color: rgba(0, 0, 0, 0.9);
  258 +
  259 + .required {
  260 + color: red;
  261 + position: absolute;
  262 + top: 50%;
  263 + transform: translateY(-50%);
  264 + left: -16rpx;
  265 + }
  266 + }
  267 + }
  268 +
  269 + &.select-item {
  270 + .readonly-text {
  271 + font-size: 32rpx;
  272 + color: rgba(0, 0, 0, 0.9);
  273 + text-align: right;
  274 + }
  275 +
  276 + &.is-empty {
  277 + .uni-list-item__extra-text {
  278 + color: rgba(0, 0, 0, 0.4) !important;
  279 + }
  280 + }
  281 +
  282 + &.is-filled {
  283 + .uni-list-item__extra-text {
  284 + color: rgba(0, 0, 0, 0.9) !important;
  285 + }
  286 + }
  287 + }
  288 + }
  289 +}
  290 +</style>
... ...