Commit a848036cc6ad5e45dc3a97fbe53072f6f464296f

Authored by 史婷婷
1 parent 91ec5a27

feat: 地址级联组件 + 经销标准合同 -新增 使用该组件,并且保存处理地区id数据

@@ -27,4 +27,14 @@ export function uploadFileApi(data) { @@ -27,4 +27,14 @@ export function uploadFileApi(data) {
27 data, 27 data,
28 contentType: ContentTypeEnum.BLOB 28 contentType: ContentTypeEnum.BLOB
29 }) 29 })
  30 +}
  31 +
  32 +/**
  33 + * 回去城市接口 省市区 一次性返回
  34 + */
  35 +export function selectorCityApi() {
  36 + return request({
  37 + url: `/selector/city`,
  38 + method: 'get'
  39 + })
30 } 40 }
  1 +<template>
  2 + <view>
  3 + <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
  4 + <view class="sheet">
  5 + <view class="sheet-header">
  6 + <text class="cancel" @click="onCancel">取消</text>
  7 + <text class="title">{{ title }}</text>
  8 + <text class="ok" @click="onConfirm">确认</text>
  9 + </view>
  10 + <view class="sheet-body">
  11 + <view class="columns">
  12 + <scroll-view class="col" scroll-y>
  13 + <view v-for="(p,i) in provinces" :key="valueOf(p,i)" :class="['option', { selected: isSelectedProvince(p) }]" @click="selectProvince(p)">
  14 + <text class="label">{{ labelOf(p) }}</text>
  15 + </view>
  16 + </scroll-view>
  17 + <scroll-view class="col" scroll-y>
  18 + <view v-for="(c,i) in cities" :key="valueOf(c,i)" :class="['option', { selected: isSelectedCity(c) }]" @click="selectCity(c)">
  19 + <text class="label">{{ labelOf(c) }}</text>
  20 + </view>
  21 + </scroll-view>
  22 + <scroll-view class="col" scroll-y>
  23 + <view v-for="(d,i) in districts" :key="valueOf(d,i)" :class="['option', { selected: isSelectedDistrict(d) }]" @click="selectDistrict(d)">
  24 + <text class="label">{{ labelOf(d) }}</text>
  25 + </view>
  26 + </scroll-view>
  27 + </view>
  28 + </view>
  29 + </view>
  30 + </uni-popup>
  31 + </view>
  32 +
  33 +</template>
  34 +
  35 +<script>
  36 +import { selectorCityApi } from '@/api/base.js'
  37 +export default {
  38 + name: 'CitySelector',
  39 + props: {
  40 + value: { type: Array, default: () => [] },
  41 + title: { type: String, default: '目的地' }
  42 + },
  43 + data() {
  44 + return {
  45 + tree: [],
  46 + provinces: [],
  47 + cities: [],
  48 + districts: [],
  49 + selectedProvince: null,
  50 + selectedCity: null,
  51 + selectedDistrict: null,
  52 + innerLabel: ''
  53 + }
  54 + },
  55 + watch: {
  56 + value(v) {
  57 + const arr = Array.isArray(v) ? v : []
  58 + if (!arr.length) { this.innerLabel = ''; this.selectedProvince = null; this.selectedCity = null; this.selectedDistrict = null }
  59 + if (arr.length && this.tree && this.tree.length > 0) this.restoreSelectionFromArray(arr)
  60 + }
  61 + },
  62 + methods: {
  63 + async open() {
  64 + if (!(this.tree && this.tree.length > 0)) {
  65 + try {
  66 + const res = await selectorCityApi()
  67 + const data = (res && res.data) ? res.data : []
  68 + this.tree = this.normalizeFlat(Array.isArray(data) ? data : [])
  69 + this.provinces = this.tree
  70 + if (Array.isArray(this.value) && this.value.length) this.restoreSelectionFromArray(this.value)
  71 + } catch (e) {
  72 + this.tree = []; this.provinces = []; this.cities = []; this.districts = []
  73 + }
  74 + }
  75 + this.$refs.popup && this.$refs.popup.open()
  76 + },
  77 + async getLabel() {
  78 + if (!(this.tree && this.tree.length > 0)) {
  79 + try {
  80 + const res = await selectorCityApi()
  81 + const data = (res && res.data) ? res.data : []
  82 + this.tree = this.normalizeFlat(Array.isArray(data) ? data : [])
  83 + this.provinces = this.tree
  84 + } catch (e) { this.tree = []; this.provinces = []; }
  85 + }
  86 + if (Array.isArray(this.value) && this.value.length) this.restoreSelectionFromArray(this.value)
  87 + return this.innerLabel
  88 + },
  89 + onCancel() {
  90 + this.$refs.popup && this.$refs.popup.close()
  91 + },
  92 + onConfirm() {
  93 + if (!(this.selectedProvince && this.selectedCity && this.selectedDistrict)) {
  94 + uni.showToast({ title: '请先选择省、市、区', icon: 'none' })
  95 + return
  96 + }
  97 + const a = [
  98 + String(this.selectedProvince.id),
  99 + String(this.selectedCity.id),
  100 + String(this.selectedDistrict.id)
  101 + ]
  102 + const parts = [this.selectedProvince, this.selectedCity, this.selectedDistrict].filter(Boolean).map(n => n.name)
  103 + this.innerLabel = parts.join(' / ')
  104 + this.$emit('input', a)
  105 + this.$emit('update:value', a)
  106 + this.$emit('change', { value: a, label: this.innerLabel })
  107 + this.$refs.popup && this.$refs.popup.close()
  108 + },
  109 + selectProvince(p) {
  110 + this.selectedProvince = p
  111 + this.cities = Array.isArray(p.children) ? p.children : []
  112 + this.selectedCity = null
  113 + this.districts = []
  114 + this.selectedDistrict = null
  115 + },
  116 + selectCity(c) {
  117 + this.selectedCity = c
  118 + this.districts = Array.isArray(c.children) ? c.children : []
  119 + this.selectedDistrict = null
  120 + },
  121 + selectDistrict(d) {
  122 + this.selectedDistrict = d
  123 + },
  124 + isSelectedProvince(p) { return this.selectedProvince && this.selectedProvince.id === p.id },
  125 + isSelectedCity(c) { return this.selectedCity && this.selectedCity.id === c.id },
  126 + isSelectedDistrict(d) { return this.selectedDistrict && this.selectedDistrict.id === d.id },
  127 + valueOf(n,i) { const v = n && (n.id != null ? n.id : (n.value != null ? n.value : (n.code != null ? n.code : i))); return String(v) },
  128 + labelOf(n) { const t = n && (n.name != null ? n.name : (n.label != null ? n.label : (n.text != null ? n.text : ''))); return String(t) },
  129 + normalizeFlat(list) {
  130 + const items = (list || []).map(it => {
  131 + const id = it.id != null ? it.id : (it.fid != null ? it.fid : (it.value != null ? it.value : (it.code != null ? it.code : '')))
  132 + const name = it.name != null ? it.name : (it.label != null ? it.label : (it.text != null ? it.text : ''))
  133 + const parentId = it.parentId != null ? it.parentId : (it.pid != null ? it.pid : (it.parent != null ? it.parent : (it.pId != null ? it.pId : '')))
  134 + return { id: String(id), name: String(name), parentId: parentId != null ? String(parentId) : '' }
  135 + })
  136 + const provinces = items.filter(x => !x.parentId)
  137 + const citiesMap = {}
  138 + for (const c of items.filter(x => x.parentId)) {
  139 + const k = c.parentId
  140 + if (!citiesMap[k]) citiesMap[k] = []
  141 + citiesMap[k].push({ id: c.id, name: c.name })
  142 + }
  143 + const districtsMap = {}
  144 + for (const d of items.filter(x => x.parentId)) {
  145 + districtsMap[d.parentId] = districtsMap[d.parentId] || []
  146 + }
  147 + const provincesWithChildren = provinces.map(p => {
  148 + const cs = citiesMap[p.id] || []
  149 + const csWithChildren = cs.map(c => ({ id: c.id, name: c.name, children: items.filter(x => x.parentId === c.id).map(y => ({ id: y.id, name: y.name })) }))
  150 + return { id: p.id, name: p.name, children: csWithChildren }
  151 + })
  152 + return provincesWithChildren
  153 + },
  154 + restoreSelectionFromArray(arr) {
  155 + const [pid, cid, did] = arr.map(v => String(v || ''))
  156 + const prov = (this.tree || []).find(n => String(n.id) === pid) || null
  157 + this.selectedProvince = prov
  158 + this.cities = prov && Array.isArray(prov.children) ? prov.children : []
  159 + const city = (this.cities || []).find(n => String(n.id) === cid) || null
  160 + this.selectedCity = city
  161 + this.districts = city && Array.isArray(city.children) ? city.children : []
  162 + const dist = (this.districts || []).find(n => String(n.id) === did) || null
  163 + this.selectedDistrict = dist
  164 + const parts = [this.selectedProvince, this.selectedCity, this.selectedDistrict].filter(Boolean).map(n => n.name)
  165 + this.innerLabel = parts.join(' ')
  166 + }
  167 + }
  168 +}
  169 +</script>
  170 +
  171 +<style lang="scss" scoped>
  172 +.sheet {
  173 + width: 100%;
  174 + max-height: 60vh;
  175 + background: #fff;
  176 + border-radius: 20rpx 20rpx 0 0;
  177 + display: flex;
  178 + flex-direction: column;
  179 +}
  180 +.sheet-header {
  181 + display: flex;
  182 + align-items: center;
  183 + justify-content: space-between;
  184 + padding: 30rpx 32rpx;
  185 + border-bottom: 1rpx solid #f0f0f0;
  186 +}
  187 +.title { font-size: 36rpx; font-weight: 600; }
  188 +.cancel { color: rgba(0,0,0,0.6); font-size: 28rpx; }
  189 +.ok { color: $theme-primary; font-size: 28rpx; }
  190 +.sheet-body { flex: 0 0 auto; padding: 0; height: 50vh; overflow: hidden; }
  191 +.columns { display: flex; height: 50vh; }
  192 +.col { flex: 1; height: 50vh; border-right: 1rpx solid #f0f0f0; }
  193 +.col:last-child { border-right: none; }
  194 +.option { line-height: 40rpx; padding: 20rpx; text-align: center; font-size: 32rpx; }
  195 +.option .label { color: rgba(0,0,0,0.6); font-size: 32rpx; }
  196 +.option.selected { background: #f3f3f3; }
  197 +.option.selected .label { color: rgba(0,0,0,0.9); }
  198 +</style>
@@ -123,9 +123,8 @@ export default { @@ -123,9 +123,8 @@ export default {
123 padding: 32rpx; 123 padding: 32rpx;
124 } 124 }
125 .option { 125 .option {
126 - height: 80rpx;  
127 - line-height: 80rpx;  
128 - padding: 0 20rpx; 126 + padding: 20rpx;
  127 + line-height: 40rpx;
129 background: #fff; 128 background: #fff;
130 text-align: center; 129 text-align: center;
131 border-radius: 12rpx; 130 border-radius: 12rpx;
@@ -60,9 +60,12 @@ @@ -60,9 +60,12 @@
60 <uni-easyinput v-model="form.transportMode" placeholder="请输入运输方式" :inputBorder="false" /> 60 <uni-easyinput v-model="form.transportMode" placeholder="请输入运输方式" :inputBorder="false" />
61 </template> 61 </template>
62 </uni-list-item> 62 </uni-list-item>
63 - <uni-list-item title="目的地">  
64 - <template v-slot:footer>  
65 - <uni-easyinput v-model="form.destinationId" placeholder="请输入目的地" :inputBorder="false" /> 63 +
  64 + <uni-list-item class="select-item" :class="(Array.isArray(form.destinationId) && form.destinationId.length) ? 'is-filled' : 'is-empty'" clickable
  65 + @click="openCitySelector" :rightText="form.destinationLabel || '请选择'" showArrow>
  66 + <template v-slot:body>
  67 + <view class="item-title"><text>目的地</text></view>
  68 + <CitySelector ref="citySelectorRef" v-model="form.destinationId" @change="onCityChange" />
66 </template> 69 </template>
67 </uni-list-item> 70 </uni-list-item>
68 <uni-list-item class="select-item" :class="form.includesPackagingFeeName ? 'is-filled' : 'is-empty'" clickable 71 <uni-list-item class="select-item" :class="form.includesPackagingFeeName ? 'is-filled' : 'is-empty'" clickable
@@ -200,13 +203,14 @@ @@ -200,13 +203,14 @@
200 import SingleSelectSheet from '@/components/single-select/index.vue' 203 import SingleSelectSheet from '@/components/single-select/index.vue'
201 import RelateSelectSheet from '@/components/relate-select/index.vue' 204 import RelateSelectSheet from '@/components/relate-select/index.vue'
202 import ProductRel from './productRel.vue' 205 import ProductRel from './productRel.vue'
  206 +import CitySelector from '@/components/city-selector/index.vue'
203 import { getRetailCodeApi, createContractApi } from '@/api/contract' 207 import { getRetailCodeApi, createContractApi } from '@/api/contract'
204 import { getDicByCodes } from '@/utils/dic' 208 import { getDicByCodes } from '@/utils/dic'
205 import { formatCurrencyToChinese } from '@/utils/common' 209 import { formatCurrencyToChinese } from '@/utils/common'
206 210
207 export default { 211 export default {
208 name: 'AddContractRetail', 212 name: 'AddContractRetail',
209 - components: { SingleSelectSheet, RelateSelectSheet, ProductRel }, 213 + components: { SingleSelectSheet, RelateSelectSheet, ProductRel, CitySelector },
210 data() { 214 data() {
211 return { 215 return {
212 form: { 216 form: {
@@ -226,7 +230,9 @@ export default { @@ -226,7 +230,9 @@ export default {
226 includesPackagingFeeName: '', 230 includesPackagingFeeName: '',
227 includesTransportFee: false, 231 includesTransportFee: false,
228 includesTransportFeeName: '', 232 includesTransportFeeName: '',
229 - unit: '元、公斤、元/公斤' 233 + unit: '元、公斤、元/公斤',
  234 + destinationId: [],
  235 + destinationLabel: ''
230 }, 236 },
231 supplierList: [], 237 supplierList: [],
232 specialTermsList: [], 238 specialTermsList: [],
@@ -246,6 +252,11 @@ export default { @@ -246,6 +252,11 @@ export default {
246 this.loadExtraOptions() 252 this.loadExtraOptions()
247 this.initCode() 253 this.initCode()
248 this.form.orderDate = this.formatDate(new Date()) 254 this.form.orderDate = this.formatDate(new Date())
  255 + this.$nextTick(() => {
  256 + if (Array.isArray(this.form.destinationId) && this.form.destinationId.length) {
  257 + this.initDestinationLabel()
  258 + }
  259 + })
249 }, 260 },
250 methods: { 261 methods: {
251 onProductsChange(products) { 262 onProductsChange(products) {
@@ -300,6 +311,20 @@ export default { @@ -300,6 +311,20 @@ export default {
300 const val = m[field] 311 const val = m[field]
301 return val ? String(val) : map[field] 312 return val ? String(val) : map[field]
302 }, 313 },
  314 + openCitySelector() {
  315 + this.$refs.citySelectorRef && this.$refs.citySelectorRef.open()
  316 + },
  317 + async initDestinationLabel() {
  318 + const comp = this.$refs.citySelectorRef
  319 + if (comp && typeof comp.getLabel === 'function') {
  320 + const label = await comp.getLabel()
  321 + this.form.destinationLabel = label || ''
  322 + }
  323 + },
  324 + onCityChange(payload) {
  325 + const label = payload && payload.label != null ? payload.label : ''
  326 + this.form.destinationLabel = label
  327 + },
303 openSheet(field) { 328 openSheet(field) {
304 const setSheet = (title, options) => { 329 const setSheet = (title, options) => {
305 const current = this.form[field] 330 const current = this.form[field]
@@ -360,14 +385,20 @@ export default { @@ -360,14 +385,20 @@ export default {
360 return out 385 return out
361 } 386 }
362 const lines = (this.productLineList || []).map(it => clean(it)) 387 const lines = (this.productLineList || []).map(it => clean(it))
  388 + const { destinationLabel, destinationId, ...formForSubmit } = this.form;
  389 + // 区id
  390 + const destination = destinationId && destinationId.length > 0 ? destinationId[destinationId.length - 1] : '';
363 const payload = clean({ 391 const payload = clean({
364 - ...this.form, 392 + ...formForSubmit,
  393 + destination,
365 type: 'DISTRIB_STD', 394 type: 'DISTRIB_STD',
366 sumQuantity: this.sumQuantity, 395 sumQuantity: this.sumQuantity,
367 sumAmountExcl: this.sumAmountExcl, 396 sumAmountExcl: this.sumAmountExcl,
368 sumTotal: this.sumTotal, 397 sumTotal: this.sumTotal,
369 contractDistributorLineList: lines 398 contractDistributorLineList: lines
370 }) 399 })
  400 + console.log('onSubmit__payload', payload)
  401 +
371 try { 402 try {
372 await createContractApi(payload) 403 await createContractApi(payload)
373 uni.showToast({ title: '新增成功', icon: 'none' }) 404 uni.showToast({ title: '新增成功', icon: 'none' })