Commit 8726c1f4ee12394ecf1a000f1f195bd99a23c2e7

Authored by 史婷婷
1 parent 71164877

feat: 开发列表-新增&编辑&查看&单选优化&关联表优化+详情按钮组件

1 import request from '@/utils/request' 1 import request from '@/utils/request'
2 import { ContentTypeEnum } from '@/utils/httpEnum'; 2 import { ContentTypeEnum } from '@/utils/httpEnum';
3 3
4 -// 模拟字典接口,后续你可以替换为真实接口请求 utils/request.js  
5 -export function getTabsDict() {  
6 - return Promise.resolve([  
7 - { label: '全部', value: 'all' },  
8 - { label: '待办', value: 'todo' },  
9 - { label: '已办', value: 'done' }  
10 - ])  
11 -}  
12 -  
13 -export function getFactoryDict() {  
14 - return Promise.resolve([  
15 - { label: '全部', value: '' },  
16 - { label: '一、二分厂', value: '12' },  
17 - { label: '三、四分厂', value: '34' }  
18 - ])  
19 -}  
20 -  
21 -export function getDeptOptionsDict() {  
22 - return Promise.resolve([  
23 - { value: '一分厂', text: '一分厂' },  
24 - { value: '二分厂', text: '二分厂' },  
25 - { value: '三分厂', text: '三分厂' },  
26 - { value: '四分厂', text: '四分厂' },  
27 - { value: '四分厂2', text: '四分厂2' },  
28 - { value: '四分厂3', text: '四分厂3' },  
29 - { value: '四分厂4', text: '四分厂4' },  
30 - { value: '四分厂5', text: '四分厂5' }  
31 - ])  
32 -}  
33 4
34 export const statusOptions = [ 5 export const statusOptions = [
35 { value: 1, text: '审核中' }, 6 { value: 1, text: '审核中' },
@@ -130,4 +101,13 @@ export function productVarietyQueryApi(params) { @@ -130,4 +101,13 @@ export function productVarietyQueryApi(params) {
130 method: 'get', 101 method: 'get',
131 params 102 params
132 }) 103 })
  104 +}
  105 +
  106 +// 生产厂 查询
  107 +export function workshopQueryApi(params) {
  108 + return request({
  109 + url: `/baseData/workshop/query`,
  110 + method: 'get',
  111 + params
  112 + })
133 } 113 }
1 import upload from '@/utils/upload' 1 import upload from '@/utils/upload'
2 import request from '@/utils/request' 2 import request from '@/utils/request'
3 - const baseUrl = '/center';  
4 - const region = 'cloud-api';  
5 - 3 +const baseUrl = '/center';
  4 +const region = 'cloud-api';
6 5
7 - const selectorBaseUrl = '/selector'; 6 +
  7 +const selectorBaseUrl = '/selector';
8 /** 8 /**
9 * 查询selector 9 * 查询selector
10 */ 10 */
11 -export function userSelector(data){ 11 +export function userSelector(data) {
12 12
13 - return request({  
14 - url: selectorBaseUrl + '/user',  
15 - method: 'get',  
16 - params: data  
17 - }); 13 + return request({
  14 + url: selectorBaseUrl + '/user',
  15 + method: 'get',
  16 + params: data
  17 + });
18 } 18 }
19 - 19 +
20 // 用户密码重置 20 // 用户密码重置
21 -export function updateUserPwd(oldPsw,newPsw,confirmPsw){  
22 -  
23 - const data = {  
24 - oldPsw,  
25 - newPsw,  
26 - confirmPsw  
27 - }  
28 - return request({  
29 - url: baseUrl + '/password',  
30 - method: 'patch',  
31 - data: {  
32 - ...data  
33 - }  
34 - }) 21 +export function updateUserPwd(oldPsw, newPsw, confirmPsw) {
  22 +
  23 + const data = {
  24 + oldPsw,
  25 + newPsw,
  26 + confirmPsw
  27 + }
  28 + return request({
  29 + url: baseUrl + '/password',
  30 + method: 'patch',
  31 + data: {
  32 + ...data
  33 + }
  34 + })
35 } 35 }
36 36
37 // 查询用户个人信息 37 // 查询用户个人信息
38 export function getUserProfile() { 38 export function getUserProfile() {
39 - return request({ 39 + return request({
40 40
41 - url: baseUrl + '/info',  
42 - method: 'get'  
43 - }) 41 + url: baseUrl + '/info',
  42 + method: 'get'
  43 + })
44 } 44 }
45 45
46 // 修改用户个人信息 46 // 修改用户个人信息
47 export function updateUserProfile(data) { 47 export function updateUserProfile(data) {
48 - return request({  
49 - url: '/system/user/profile',  
50 - method: 'put',  
51 - data: data  
52 - }) 48 + return request({
  49 + url: '/system/user/profile',
  50 + method: 'put',
  51 + data: data
  52 + })
53 } 53 }
54 54
55 // 用户头像上传 55 // 用户头像上传
56 export function uploadAvatar(data) { 56 export function uploadAvatar(data) {
57 - return upload({  
58 - url: '/system/user/profile/avatar',  
59 - name: data.name,  
60 - filePath: data.filePath  
61 - })  
62 -} 57 + return upload({
  58 + url: '/system/user/profile/avatar',
  59 + name: data.name,
  60 + filePath: data.filePath
  61 + })
  62 +}
@@ -22,10 +22,11 @@ @@ -22,10 +22,11 @@
22 <view 22 <view
23 v-for="(item, idx) in items" 23 v-for="(item, idx) in items"
24 :key="getKey(item, idx)" 24 :key="getKey(item, idx)"
25 - :class="['card-item', { 'select-item': selectable }]" 25 + :class="['card-item', { 'select-item': selectable && showCheck }]"
26 @click="toggleSelect(item, idx)" 26 @click="toggleSelect(item, idx)"
  27 + @tap="toggleSelect(item, idx)"
27 > 28 >
28 - <view v-if="selectable" class="check"> 29 + <view v-if="selectable && showCheck" class="check">
29 <view class="dot" :class="{ checked: isSelected(item, idx) }"> 30 <view class="dot" :class="{ checked: isSelected(item, idx) }">
30 <uni-icons v-if="isSelected(item, idx)" type="checkmarkempty" color="#fff" size="14" /> 31 <uni-icons v-if="isSelected(item, idx)" type="checkmarkempty" color="#fff" size="14" />
31 </view> 32 </view>
@@ -58,6 +59,8 @@ export default { @@ -58,6 +59,8 @@ export default {
58 enableLoadMore: { type: Boolean, default: true }, 59 enableLoadMore: { type: Boolean, default: true },
59 // 支持多选 60 // 支持多选
60 selectable: { type: Boolean, default: false }, 61 selectable: { type: Boolean, default: false },
  62 + // 是否显示左侧选择圆点
  63 + showCheck: { type: Boolean, default: true },
61 rowKey: { type: String, default: 'id' }, 64 rowKey: { type: String, default: 'id' },
62 // v-model:selectedKeys 65 // v-model:selectedKeys
63 selectedKeys: { type: Array, default: () => [] } 66 selectedKeys: { type: Array, default: () => [] }
  1 +<template>
  2 + <view class="button-bar">
  3 + <view v-if="showMoreButton" class="more-link" @click="toggleMore">更多</view>
  4 + <view class="button-grid" :style="{ gridTemplateColumns: gridColumns }">
  5 + <view v-for="(btn, idx) in visibleButtons" :key="idx" class="action-button" :class="buttonClass(btn)" :style="buttonStyle(btn)" @click="emitClick(btn)">{{ btn.text }}</view>
  6 + </view>
  7 + <view v-if="moreVisible" class="popover-mask" @click="closeMore"></view>
  8 + <view v-if="moreVisible" class="more-popover" :style="popoverStyle()">
  9 + <view v-for="(btn, idx) in moreButtons" :key="idx" class="menu-item" :class="menuClass(btn)" :style="menuStyle(btn)" @click="emitMoreClick(btn)">{{ btn.text }}</view>
  10 + </view>
  11 + </view>
  12 +</template>
  13 +
  14 +<script>
  15 +export default {
  16 + name: 'DetailButtons',
  17 + props: {
  18 + buttons: { type: Array, default: () => [] }
  19 + },
  20 + data() {
  21 + return { moreVisible: false }
  22 + },
  23 + computed: {
  24 + effectiveButtons() {
  25 + const list = this.buttons || []
  26 + return list.filter(it => it && it.visible !== false)
  27 + },
  28 + showMoreButton() {
  29 + return (this.effectiveButtons || []).length >= 4
  30 + },
  31 + visibleButtons() {
  32 + const list = this.effectiveButtons || []
  33 + if (list.length <= 3) return list
  34 + return list.slice(0, 2)
  35 + },
  36 + moreButtons() {
  37 + const list = this.effectiveButtons || []
  38 + return list.length >= 4 ? list.slice(2) : []
  39 + },
  40 + gridColumns() {
  41 + const len = (this.effectiveButtons || []).length
  42 + if (this.showMoreButton) return '1fr 1fr'
  43 + if (len <= 1) return '1fr'
  44 + if (len === 2) return '1fr 1fr'
  45 + return '1fr 1fr 1fr'
  46 + }
  47 + },
  48 + methods: {
  49 + toggleMore() { this.moreVisible = !this.moreVisible },
  50 + closeMore() { this.moreVisible = false },
  51 + emitClick(btn) {
  52 + if (!btn || btn.disabled) return
  53 + this.$emit('click', btn)
  54 + },
  55 + emitMoreClick(btn) { this.emitClick(btn); this.closeMore() },
  56 + buttonClass(btn) {
  57 + const v = (btn && btn.variant) || 'outline'
  58 + let cls = ''
  59 + if (v === 'primary') cls = 'action-button--primary'
  60 + else if (v === 'danger') cls = 'action-button--danger'
  61 + else cls = 'action-button--outline'
  62 + if (btn && btn.disabled) cls += ' is-disabled'
  63 + return cls
  64 + },
  65 + buttonStyle(btn) { return (btn && btn.style) ? btn.style : {} },
  66 + menuClass(btn) {
  67 + const v = (btn && btn.variant) || 'outline'
  68 + if (v === 'danger') return 'menu-item--danger'
  69 + return 'menu-item--primary'
  70 + },
  71 + menuStyle(btn) { return (btn && btn.style) ? btn.style : {} },
  72 + popoverStyle() { return { left: '24rpx' } }
  73 + }
  74 +}
  75 +</script>
  76 +
  77 +<style lang="scss" scoped>
  78 + .button-bar {
  79 + position: fixed;
  80 + left: 0;
  81 + right: 0;
  82 + bottom: 0;
  83 + background: #fff;
  84 + padding: 16rpx 24rpx;
  85 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.03);
  86 + padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
  87 + display: flex;
  88 + align-items: center;
  89 + gap: 24rpx;
  90 + position: fixed;
  91 + z-index: 10;
  92 + }
  93 +
  94 + .more-link {
  95 + color: $theme-primary;
  96 + font-size: 28rpx;
  97 + line-height: 1;
  98 + padding: 20rpx 0;
  99 + }
  100 +
  101 + .button-grid {
  102 + flex: 1;
  103 + display: grid;
  104 + gap: 16rpx;
  105 + }
  106 +
  107 + .action-button {
  108 + text-align: center;
  109 + padding: 20rpx 0;
  110 + border-radius: 12rpx;
  111 + white-space: nowrap;
  112 + overflow: hidden;
  113 + text-overflow: ellipsis;
  114 + }
  115 +
  116 + .action-button--outline { border: 2rpx solid $theme-primary; color: $theme-primary; background: #fff; }
  117 + .action-button--primary { border: 2rpx solid $theme-primary; background: $theme-primary; color: #fff; }
  118 + .action-button--danger { border: 2rpx solid #ff4d4f; background: #ff4d4f; color: #fff; }
  119 + .is-disabled { opacity: 0.5; }
  120 +
  121 + .popover-mask {
  122 + position: fixed;
  123 + left: 0;
  124 + right: 0;
  125 + top: 0;
  126 + bottom: 0;
  127 + background: transparent;
  128 + z-index: 9;
  129 + }
  130 +
  131 + .more-popover {
  132 + position: absolute;
  133 + bottom: calc(100% + 12rpx);
  134 + background: #fff;
  135 + border-radius: 12rpx;
  136 + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
  137 + padding: 8rpx 0;
  138 + min-width: 360rpx;
  139 + z-index: 11;
  140 + }
  141 +
  142 + .menu-item {
  143 + padding: 16rpx 20rpx;
  144 + font-size: 28rpx;
  145 + white-space: nowrap;
  146 + overflow: hidden;
  147 + text-overflow: ellipsis;
  148 + }
  149 +
  150 + .menu-item--primary { color: $theme-primary; }
  151 + .menu-item--danger { color: #ff4d4f; }
  152 +</style>
  1 +<template>
  2 + <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
  3 + <view class="relate-sheet">
  4 + <view class="sheet-header">
  5 + <text class="cancel" @click="onCancel">取消</text>
  6 + <text class="title">{{ title }}</text>
  7 + <text class="ok" @click="onConfirm">确认{{ confirmCountText }}</text>
  8 + </view>
  9 + <view class="sheet-search">
  10 + <uni-search-bar v-model="searchKeyword" @input="onSearchInput" @confirm="onConfirmSearch" :placeholder="placeholder" />
  11 + </view>
  12 + <view class="sheet-body">
  13 + <CardList
  14 + ref="cardRef"
  15 + :fetchFn="fetchList"
  16 + :pageSize="pageSize"
  17 + :query="listQuery"
  18 + :extra="listExtra"
  19 + :selectable="true"
  20 + :row-key="rowKey"
  21 + :selectedKeys.sync="innerSelectedKeys"
  22 + :show-check="false"
  23 + @loaded="onLoaded"
  24 + >
  25 + <template v-slot="{ item, selected }">
  26 + <view :class="['card', { selected }]">
  27 + <view v-for="(f,i) in displayFields" :key="i" class="row">
  28 + <text class="label">{{ f.label }}</text>
  29 + <text class="value">{{ formatValue(item, f) }}</text>
  30 + </view>
  31 + </view>
  32 + </template>
  33 + </CardList>
  34 + </view>
  35 + </view>
  36 + </uni-popup>
  37 +</template>
  38 +
  39 +<script>
  40 +import CardList from '@/components/card/index.vue'
  41 +import { customerQueryApi } from '@/api/devManage.js'
  42 +import { userSelector } from '@/api/system/user.js'
  43 +
  44 +export default {
  45 + name: 'RelateSelectSheet',
  46 + components: { CardList },
  47 + props: {
  48 + visible: { type: Boolean, default: false },
  49 + title: { type: String, default: '选择' },
  50 + // 传入显示字段配置:[{ label, field }]
  51 + displayFields: { type: Array, default: () => [ { label: '名称', field: 'name' } ] },
  52 + // 如果未提供 fetchFn,可通过 source 指定内置数据源:'customer' | 'user'
  53 + source: { type: String, default: '' },
  54 + fetchFn: { type: Function, default: null },
  55 + pageSize: { type: Number, default: 10 },
  56 + multiple: { type: Boolean, default: false },
  57 + rowKey: { type: String, default: 'id' },
  58 + // 选中回显:由父组件传入,支持 .sync / v-model:selectedKeys
  59 + selectedKeys: { type: Array, default: () => [] }
  60 + },
  61 + data() {
  62 + return {
  63 + innerSelectedKeys: this.selectedKeys.slice(0),
  64 + currentItems: [],
  65 + searchKeyword: '',
  66 + searchKeywordDebounced: '',
  67 + searchDebounceTimer: null,
  68 + placeholder: '搜索',
  69 + // 使用稳定对象,避免因重新渲染创建新对象导致列表 reload
  70 + listQuery: {},
  71 + listExtra: { keyword: '' }
  72 + }
  73 + },
  74 + computed: {
  75 + confirmCountText() {
  76 + const n = Array.isArray(this.innerSelectedKeys) ? this.innerSelectedKeys.length : 0
  77 + return n > 0 ? `(${n})` : ''
  78 + }
  79 + },
  80 + watch: {
  81 + visible(v) { v ? this.open() : this.close() },
  82 + selectedKeys(keys) {
  83 + // 父传入选中回显,只同步到内部,不向父回推,避免循环
  84 + const incoming = Array.isArray(keys) ? keys : []
  85 + if (!this.multiple && incoming.length > 1) {
  86 + const last = incoming[incoming.length - 1]
  87 + this.innerSelectedKeys = [last]
  88 + } else {
  89 + this.innerSelectedKeys = incoming.slice(0)
  90 + }
  91 + },
  92 + innerSelectedKeys(keys) {
  93 + // 内部选择变化时,保证单选只保留最后一个
  94 + const arr = Array.isArray(keys) ? keys : []
  95 + if (!this.multiple && arr.length > 1) {
  96 + const last = arr[arr.length - 1]
  97 + // 仅当确实需要收敛时再赋值,减少不必要的触发
  98 + if (!(arr.length === 1 && arr[0] === last)) {
  99 + this.innerSelectedKeys = [last]
  100 + }
  101 + }
  102 + }
  103 + },
  104 + mounted() {
  105 + if (this.visible) this.open()
  106 + },
  107 + methods: {
  108 + open() {
  109 + this.$refs.popup && this.$refs.popup.open()
  110 + this.$emit('update:visible', true)
  111 + },
  112 + close() {
  113 + this.$refs.popup && this.$refs.popup.close()
  114 + this.$emit('update:visible', false)
  115 + },
  116 + fetchList({ pageIndex, pageSize, query, extra }) {
  117 + const keyword = (extra && (extra.keyword || extra.key || extra.name)) || ''
  118 + if (typeof this.fetchFn === 'function') {
  119 + return this.fetchFn({ pageIndex, pageSize, query, extra })
  120 + }
  121 + // 内置数据源
  122 + try {
  123 + // 客户池
  124 + if (this.source === 'customer') {
  125 + const params = { pageIndex, pageSize, keyword }
  126 + return customerQueryApi(params).then(res => {
  127 + const _data = res.data || {}
  128 + const records = _data.datas || []
  129 + const totalCount = _data.totalCount || 0
  130 + const hasNext = _data.hasNext || false
  131 + return { records, totalCount, hasNext }
  132 + })
  133 + } else if (this.source === 'user') {
  134 + // 人员表
  135 + const params = { pageIndex, pageSize, keyword, key: keyword, name: keyword, username: keyword }
  136 + return userSelector(params).then(res => {
  137 + const _data = res.data || {}
  138 + const records = _data.datas || _data.records || _data.list || []
  139 + const totalCount = _data.totalCount || _data.count || 0
  140 + const hasNext = _data.hasNext || false
  141 + return { records, totalCount, hasNext }
  142 + })
  143 + }
  144 + } catch (e) {
  145 + return Promise.resolve({ records: [], totalCount: 0, hasNext: false })
  146 + }
  147 + return Promise.resolve({ records: [], totalCount: 0, hasNext: false })
  148 + },
  149 + formatValue(item, f) {
  150 + const v = f && f.field ? item[f.field] : ''
  151 + if (f && typeof f.format === 'function') {
  152 + const r = f.format(v, item)
  153 + return r == null ? '' : String(r)
  154 + }
  155 + if (f && f.map && typeof f.map === 'object' && f.map !== null) {
  156 + if (Object.prototype.hasOwnProperty.call(f.map, v)) {
  157 + const r = f.map[v]
  158 + return r == null ? '' : String(r)
  159 + }
  160 + }
  161 + return v == null ? '' : String(v)
  162 + },
  163 + onLoaded({ items }) {
  164 + this.currentItems = items || []
  165 + },
  166 + onSearchInput(val) {
  167 + this.searchKeyword = val
  168 + clearTimeout(this.searchDebounceTimer)
  169 + this.searchDebounceTimer = setTimeout(() => {
  170 + this.searchKeywordDebounced = this.searchKeyword
  171 + this.listExtra.keyword = this.searchKeyword
  172 + }, 300)
  173 + },
  174 + onConfirmSearch() {
  175 + this.searchKeywordDebounced = this.searchKeyword
  176 + this.listExtra.keyword = this.searchKeyword
  177 + },
  178 + onCancel() { this.close() },
  179 + onConfirm() {
  180 + const rowKey = this.rowKey || 'id'
  181 + const selected = (this.currentItems || []).filter(it => this.innerSelectedKeys.includes(it[rowKey]))
  182 + this.$emit('confirm', { items: selected })
  183 + this.close()
  184 + }
  185 + }
  186 +}
  187 +</script>
  188 +
  189 +<style lang="scss" scoped>
  190 +.relate-sheet {
  191 + width: 100%;
  192 + height: 80vh;
  193 + background: #fff;
  194 + border-radius: 20rpx 20rpx 0 0;
  195 + display: flex;
  196 + flex-direction: column;
  197 +}
  198 +.sheet-header {
  199 + display: flex;
  200 + align-items: center;
  201 + justify-content: space-between;
  202 + padding: 30rpx 32rpx;
  203 + border-bottom: 1rpx solid #f0f0f0;
  204 +}
  205 +.title { font-size: 36rpx; font-weight: 600; }
  206 +.cancel { color: rgba(0,0,0,0.6); font-size: 28rpx; }
  207 +.ok { color: $theme-primary; font-size: 28rpx; }
  208 +.sheet-search { padding: 16rpx 24rpx; }
  209 +.sheet-body {
  210 + flex: 1 1 auto; overflow-y: auto; padding: 24rpx;
  211 + background: #f3f3f3;
  212 +}
  213 +.card { background: #fff; }
  214 +.card.selected {
  215 + background-color: #fff;
  216 + position: relative;
  217 + &::after {
  218 + content: '';
  219 + position: absolute;
  220 + top: -32rpx;
  221 + left: -32rpx;
  222 + bottom: -32rpx;
  223 + right: -32rpx;
  224 + border: 1px solid $theme-primary-alpha-50;
  225 + }
  226 +}
  227 +.row { display: flex; gap: 16rpx; margin-bottom: 12rpx; &:last-child { margin-bottom: 0; } }
  228 +.label { color: rgba(0,0,0,0.6); }
  229 +.value { color: rgba(0,0,0,0.9); }
  230 +</style>
@@ -92,7 +92,7 @@ export default { @@ -92,7 +92,7 @@ export default {
92 <style lang="scss" scoped> 92 <style lang="scss" scoped>
93 .sheet { 93 .sheet {
94 width: 100%; 94 width: 100%;
95 - height: 45vh; 95 + max-height: 45vh;
96 background: #fff; 96 background: #fff;
97 border-radius: 20rpx 20rpx 0 0; 97 border-radius: 20rpx 20rpx 0 0;
98 display: flex; 98 display: flex;
@@ -54,6 +54,27 @@ @@ -54,6 +54,27 @@
54 "navigationBarBackgroundColor": "#ffffff", 54 "navigationBarBackgroundColor": "#ffffff",
55 "navigationBarTextStyle": "black" 55 "navigationBarTextStyle": "black"
56 } 56 }
  57 + }, {
  58 + "path": "pages/dev_manage/add",
  59 + "style": {
  60 + "navigationBarTitleText": "新增客户开发",
  61 + "navigationBarBackgroundColor": "#ffffff",
  62 + "navigationBarTextStyle": "black"
  63 + }
  64 + }, {
  65 + "path": "pages/dev_manage/modify",
  66 + "style": {
  67 + "navigationBarTitleText": "修改客户开发",
  68 + "navigationBarBackgroundColor": "#ffffff",
  69 + "navigationBarTextStyle": "black"
  70 + }
  71 + }, {
  72 + "path": "pages/dev_manage/detail",
  73 + "style": {
  74 + "navigationBarTitleText": "查看客户开发",
  75 + "navigationBarBackgroundColor": "#ffffff",
  76 + "navigationBarTextStyle": "black"
  77 + }
57 } 78 }
58 ], 79 ],
59 "subPackages": [ 80 "subPackages": [
  1 +<template>
  2 + <view class="page">
  3 +
  4 + <scroll-view class="scroll" scroll-y>
  5 + <uni-list>
  6 + <!-- 生产厂 单选 -->
  7 + <uni-list-item class="select-item" :class="form.workshopId ? 'is-filled' : 'is-empty'" clickable
  8 + @click="openSheet('workshopId')" :rightText="displayLabel('workshopIdName')" showArrow>
  9 + <template v-slot:body>
  10 + <view class="item-title"><text class="required">*</text><text>生产厂</text></view>
  11 + </template>
  12 + </uni-list-item>
  13 + <!-- 科办 单选 -->
  14 + <uni-list-item class="mgb10 select-item" :class="form.officeId ? 'is-filled' : 'is-empty'" clickable
  15 + @click="openSheet('officeId')" :rightText="displayLabel('officeIdName')" 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 + <!-- 客户名称 关联页选择 -->
  22 + <uni-list-item class="select-item" :class="form.customerId ? 'is-filled' : 'is-empty'" clickable @click="openRelate('customerId')" :rightText="form.customerIdName || '请选择客户名称'"
  23 + showArrow>
  24 + <template v-slot:body>
  25 + <view class="item-title"><text class="required">*</text><text>客户名称</text></view>
  26 + </template>
  27 + </uni-list-item>
  28 +
  29 + <!-- 客户类型、产品品种 单选 -->
  30 + <uni-list-item class="select-item" :class="form.customerType ? 'is-filled' : 'is-empty'" clickable
  31 + @click="openSheet('customerType')" :rightText="displayLabel('customerTypeName')" showArrow>
  32 + <template v-slot:body>
  33 + <view class="item-title"><text class="required">*</text><text>客户类型</text></view>
  34 + </template>
  35 + </uni-list-item>
  36 + <uni-list-item class="select-item" :class="form.productVarietyId ? 'is-filled' : 'is-empty'" clickable
  37 + @click="openSheet('productVarietyId')" :rightText="displayLabel('productVarietyIdName')" showArrow>
  38 + <template v-slot:body>
  39 + <view class="item-title"><text class="required">*</text><text>产品品种</text></view>
  40 + </template>
  41 + </uni-list-item>
  42 +
  43 + <!-- 其余输入项 -->
  44 + <uni-list-item title="月用量">
  45 + <template v-slot:footer>
  46 + <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量" :inputBorder="false" />
  47 + </template>
  48 + </uni-list-item>
  49 + <uni-list-item title="目标量">
  50 + <template v-slot:footer>
  51 + <uni-easyinput v-model="form.targetQuantity" placeholder="请输入目标量" :inputBorder="false" />
  52 + </template>
  53 + </uni-list-item>
  54 + <uni-list-item title="行业" class="mgb10">
  55 + <template v-slot:footer>
  56 + <uni-easyinput v-model="form.industry" placeholder="请输入行业" :inputBorder="false" />
  57 + </template>
  58 + </uni-list-item>
  59 + <uni-list-item title="牌号">
  60 + <template v-slot:footer>
  61 + <uni-easyinput v-model="form.mark" placeholder="请输入牌号" :inputBorder="false" />
  62 + </template>
  63 + </uni-list-item>
  64 + <uni-list-item title="厚度">
  65 + <template v-slot:footer>
  66 + <uni-easyinput v-model="form.thickness" placeholder="请输入厚度" :inputBorder="false" />
  67 + </template>
  68 + </uni-list-item>
  69 + <uni-list-item title="宽度">
  70 + <template v-slot:footer>
  71 + <uni-easyinput v-model="form.width" placeholder="请输入宽度" :inputBorder="false" />
  72 + </template>
  73 + </uni-list-item>
  74 + <uni-list-item title="材质要求">
  75 + <template v-slot:footer>
  76 + <uni-easyinput v-model="form.materialRequire" placeholder="请输入材质要求" :inputBorder="false" />
  77 + </template>
  78 + </uni-list-item>
  79 + <uni-list-item title="品质要求">
  80 + <template v-slot:footer>
  81 + <uni-easyinput v-model="form.qualityRequire" placeholder="请输入品质要求" :inputBorder="false" />
  82 + </template>
  83 + </uni-list-item>
  84 + <uni-list-item title="同行" class="mgb10">
  85 + <template v-slot:footer>
  86 + <uni-easyinput v-model="form.peer" placeholder="请输入同行" :inputBorder="false" />
  87 + </template>
  88 + </uni-list-item>
  89 + <uni-list-item title="核价模式">
  90 + <template v-slot:footer>
  91 + <uni-easyinput v-model="form.pricingMode" placeholder="请输入核价模式" :inputBorder="false" />
  92 + </template>
  93 + </uni-list-item>
  94 + <uni-list-item title="结算天数" class="mgb10">
  95 + <template v-slot:footer>
  96 + <uni-easyinput v-model="form.settleDays" placeholder="请输入结算天数" :inputBorder="false" />
  97 + </template>
  98 + </uni-list-item>
  99 + <!-- 责任人 关联页选择 -->
  100 + <uni-list-item class="select-item" :class="form.chargeUserIdName ? 'is-filled' : 'is-empty'" title="责任人"
  101 + clickable @click="openRelate('chargeUserId')" :rightText="form.chargeUserIdName || '请选择责任人'" showArrow />
  102 + </uni-list>
  103 +
  104 + </scroll-view>
  105 +
  106 + <view class="footer">
  107 + <button class="btn submit" type="primary" @click="onSubmit">提交</button>
  108 + </view>
  109 +
  110 + <!-- 单选弹框:生产厂、科办、客户类型、产品品种 -->
  111 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  112 + v-model="sheet.value" @confirm="onSheetConfirm" />
  113 +
  114 + <!-- 关联选择弹框:客户名称、责任人 -->
  115 + <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
  116 + :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
  117 + :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
  118 + </view>
  119 +</template>
  120 +
  121 +<script>
  122 + import SingleSelectSheet from '@/components/single-select/index.vue'
  123 + import RelateSelectSheet from '@/components/relate-select/index.vue'
  124 + import constant from '@/utils/constant'
  125 + import storage from '@/utils/storage'
  126 + import {
  127 + createApi,
  128 + officeQueryApi,
  129 + productVarietyQueryApi,
  130 + workshopQueryApi
  131 + } from '@/api/devManage.js'
  132 + import {
  133 + getDicByCodeApi
  134 + } from '@/api/base.js'
  135 +
  136 + export default {
  137 + name: 'DevManageAdd',
  138 + components: {
  139 + SingleSelectSheet,
  140 + RelateSelectSheet
  141 + },
  142 + data() {
  143 + return {
  144 + form: {
  145 + workshopIdName: '',
  146 + workshopId: '',
  147 + officeIdName: '',
  148 + officeId: '',
  149 + customerIdName: '',
  150 + customerId: '',
  151 + customerType: '',
  152 + customerTypeName: '',
  153 + chargeUserId: '',
  154 + chargeUserIdName: '',
  155 + productVarietyId: '',
  156 + productVarietyIdName: '',
  157 + monthlyUsage: '',
  158 + targetQuantity: '',
  159 + industry: '',
  160 + mark: '',
  161 + thickness: '',
  162 + width: '',
  163 + materialRequire: '',
  164 + qualityRequire: '',
  165 + peer: '',
  166 + pricingMode: '',
  167 + settleDays: '',
  168 + },
  169 + sheet: {
  170 + visible: false,
  171 + title: '请选择',
  172 + field: '',
  173 + options: [],
  174 + value: ''
  175 + },
  176 + relate: {
  177 + visible: false,
  178 + title: '选择',
  179 + source: '',
  180 + display: [],
  181 + multiple: false,
  182 + rowKey: 'id',
  183 + selectedKeys: []
  184 + },
  185 + officeOptions: [],
  186 + customerTypeOptions: [],
  187 + productVarietyIdOptions: []
  188 + }
  189 + },
  190 + created() {
  191 + // 责任人默认使用当前用户昵称/用户名
  192 + const user = storage.get(constant.name);
  193 + const userId = storage.get(constant.id);
  194 + this.form.chargeUserIdName = user || '';
  195 + this.form.chargeUserId = userId || '';
  196 + // 科办
  197 + this.loadOfficeOptions()
  198 + // 客户类型
  199 + this.loadCustomerTypeOptions()
  200 + // 生产厂
  201 + this.loadproductVarietyIdOptions()
  202 + },
  203 + methods: {
  204 + // 校验必填项:生产厂、科办、客户名称、客户类型、产品品种
  205 + validateRequired() {
  206 + const checks = [{
  207 + key: 'workshopId',
  208 + label: '生产厂'
  209 + },
  210 + {
  211 + key: 'officeId',
  212 + label: '科办'
  213 + },
  214 + {
  215 + key: 'customerId',
  216 + label: '客户名称'
  217 + },
  218 + {
  219 + key: 'customerType',
  220 + label: '客户类型'
  221 + },
  222 + {
  223 + key: 'productVarietyId',
  224 + label: '产品品种'
  225 + }
  226 + ]
  227 + for (const it of checks) {
  228 + const val = this.form[it.key]
  229 + if (val === undefined || val === null || String(val).trim() === '') {
  230 + uni.showToast({
  231 + title: `请先选择${it.label}`,
  232 + icon: 'none'
  233 + })
  234 + return false
  235 + }
  236 + }
  237 + return true
  238 + },
  239 + async loadOfficeOptions() {
  240 + try {
  241 + const res = await officeQueryApi({
  242 + pageIndex: 1,
  243 + pageSize: 9999
  244 + })
  245 + const _data = res.data || {}
  246 + const list = _data.datas || []
  247 + this.officeOptions = (list || []).map(it => ({
  248 + label: it.name || it.text || it.officeName || it.label,
  249 + value: it.id || it.code || it.value
  250 + }))
  251 + } catch (e) {
  252 + this.officeOptions = []
  253 + }
  254 + },
  255 + displayLabel(field) {
  256 + const m = this.form
  257 + const map = {
  258 + workshopIdName: '请选择生产厂',
  259 + officeIdName: '请选择科办',
  260 + customerTypeName: '请选择客户类型',
  261 + productVarietyIdName: '请选择产品品种'
  262 + }
  263 + const val = m[field]
  264 + return val ? String(val) : map[field]
  265 + },
  266 + async openSheet(field) {
  267 + const setSheet = (title, options) => {
  268 + const current = this.form[field]
  269 + const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
  270 + this.sheet = {
  271 + ...this.sheet,
  272 + visible: true,
  273 + title,
  274 + options,
  275 + field,
  276 + value: match ? match.value : ''
  277 + }
  278 + }
  279 + if (field === 'workshopId') {
  280 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  281 + const _data = res.data || {}
  282 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  283 + const opts = (list || []).map(it => ({
  284 + label: it.name ,
  285 + value: it.id
  286 + }))
  287 + setSheet('生产厂', opts)
  288 + } else if (field === 'officeId') {
  289 + setSheet('科办', this.officeOptions)
  290 + } else if (field === 'customerType') {
  291 + const opts = this.customerTypeOptions || [];
  292 + setSheet('客户类型', opts)
  293 + } else if (field === 'productVarietyId') {
  294 + const opts = this.productVarietyIdOptions;
  295 + setSheet('产品品种', opts)
  296 + }
  297 + },
  298 + onSheetConfirm({
  299 + value,
  300 + label
  301 + }) {
  302 + const field = this.sheet.field
  303 + if (!field) return
  304 + this.form[field] = value || '';
  305 + this.form[field + 'Name'] = label || ''
  306 + this.sheet.visible = false
  307 + },
  308 + openRelate(fieldKey) {
  309 + let config = {};
  310 + switch(fieldKey) {
  311 + case 'customerId':
  312 + config = {
  313 + title: '客户名称',
  314 + source: 'customer',
  315 + rowKey: 'id',
  316 + multiple: false,
  317 + display: [{
  318 + label: '姓名',
  319 + field: 'name'
  320 + },
  321 + {
  322 + label: '编号',
  323 + field: 'code'
  324 + },
  325 + {
  326 + label: '状态',
  327 + field: 'available',
  328 + format: v => (v ? '启用' : '停用')
  329 + }
  330 + ]
  331 + }
  332 + break
  333 + case 'chargeUserId':
  334 + config = {
  335 + title: '责任人',
  336 + source: 'user',
  337 + rowKey: 'id',
  338 + multiple: false,
  339 + display: [{
  340 + label: '姓名',
  341 + field: 'name'
  342 + },
  343 + {
  344 + label: '编号',
  345 + field: 'code'
  346 + },
  347 + {
  348 + label: '状态',
  349 + field: 'available',
  350 + format: v => (v ? '启用' : '停用')
  351 + }
  352 + ]};
  353 + break
  354 + }
  355 + const selectedKeys = this.form[fieldKey] ? [this.form[fieldKey]] : [];
  356 + // 逐一赋值,避免整体替换对象导致子组件 prop 更新不及时
  357 + this.sheet.visible = false
  358 + this.relate.title = config.title
  359 + this.relate.source = config.source
  360 + this.relate.display = config.display
  361 + this.relate.multiple = config.multiple
  362 + this.relate.rowKey = config.rowKey
  363 + this.relate.selectedKeys = selectedKeys
  364 + this.relate.fieldKey = fieldKey;
  365 + // 最后再打开弹框,确保选中键已传入
  366 + this.$nextTick(() => {
  367 + this.relate.visible = true
  368 + })
  369 + },
  370 + onRelateConfirm({
  371 + items
  372 + }) {
  373 + console.log('onRelateConfirm__item', items)
  374 + const _fieldKey = this.relate.fieldKey;
  375 + console.log('onRelateConfirm__fieldKey', _fieldKey)
  376 + const first = (items && items.length > 0) ? items[0] : null
  377 + // 若无选中项,确认时清空之前的值
  378 + this.form[_fieldKey] = first.id || '';
  379 + this.form[_fieldKey + 'Name'] = first.name || '';
  380 + },
  381 + async loadCustomerTypeOptions() {
  382 + try {
  383 + const res = await getDicByCodeApi('ENTERPRISE_TYPE');
  384 + const list = res.data || [];
  385 + this.customerTypeOptions = (list || []).map(it => ({
  386 + label: it.name || '',
  387 + value: it.code || ''
  388 + }))
  389 + } catch (e) {
  390 + this.customerTypeOptions = []
  391 + }
  392 + },
  393 +
  394 + async loadproductVarietyIdOptions() {
  395 + try {
  396 + const res = await productVarietyQueryApi({ pageIndex: 1, pageSize: 9999 })
  397 + const _data = res.data || {}
  398 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  399 + this.productVarietyIdOptions = (list || []).map(it => ({
  400 + label: it.name || it.text || it.label,
  401 + value: it.id || it.code || it.value
  402 + }))
  403 + } catch (e) {
  404 + this.productVarietyIdOptions = []
  405 + }
  406 + },
  407 + async onSubmit() {
  408 + // 必填校验
  409 + if (!this.validateRequired()) return
  410 + const payload = {
  411 + ...this.form
  412 + }
  413 + delete payload.workshopIdName
  414 + delete payload.officeIdName
  415 + delete payload.customerTypeName
  416 + delete payload.customerIdName
  417 + delete payload.productVarietyIdName
  418 + delete payload.chargeUserIdName
  419 + createApi(payload).then(res => {
  420 + uni.showToast({
  421 + title: '提交成功',
  422 + icon: 'success'
  423 + })
  424 + setTimeout(() => uni.navigateBack(), 600)
  425 + }).catch(e => {
  426 + console.log('createApi__e', e)
  427 + uni.showToast({
  428 + title: e.msg || '提交失败',
  429 + icon: 'none'
  430 + })
  431 + })
  432 + }
  433 + }
  434 + }
  435 +</script>
  436 +
  437 +<style lang="scss" scoped>
  438 + .page {
  439 + display: flex;
  440 + flex-direction: column;
  441 + height: 100%;
  442 + }
  443 +
  444 + .scroll {
  445 + flex: 1;
  446 + padding: 12rpx 0 160rpx;
  447 + }
  448 +
  449 + .footer {
  450 + position: fixed;
  451 + left: 0;
  452 + right: 0;
  453 + bottom: 0;
  454 + padding: 32rpx;
  455 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  456 + background: #fff;
  457 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  458 +
  459 + .btn {
  460 + height: 80rpx;
  461 + line-height: 80rpx;
  462 + border-radius: 12rpx;
  463 + font-size: 32rpx;
  464 + }
  465 +
  466 + .submit {
  467 + background: $theme-primary;
  468 + color: #fff;
  469 + }
  470 + }
  471 +
  472 + ::v-deep .uni-list {
  473 + background: transparent;
  474 + &-item {
  475 + &__extra-text {
  476 + font-size: 32rpx;
  477 + }
  478 + &__content-title {
  479 + font-size: 32rpx;
  480 + color: rgba(0, 0, 0, 0.9);
  481 + }
  482 +
  483 + &__container {
  484 + padding: 32rpx;
  485 +
  486 + .uni-easyinput {
  487 + &__placeholder-class {
  488 + font-size: 32rpx;
  489 + color: rgba(0, 0, 0, 0.4);
  490 + }
  491 +
  492 + &__content {
  493 + border: none;
  494 +
  495 + &-input {
  496 + padding-left: 0 !important;
  497 + height: 48rpx;
  498 + line-height: 48rpx;
  499 + font-size: 32rpx;
  500 + }
  501 +
  502 + .content-clear-icon {
  503 + font-size: 44rpx !important;
  504 + }
  505 + }
  506 + }
  507 +
  508 + .item-title,
  509 + .uni-list-item__content {
  510 + flex: none;
  511 + min-height: 48rpx;
  512 + line-height: 48rpx;
  513 + font-size: 32rpx;
  514 + position: relative;
  515 + width: 162rpx;
  516 + margin-right: 32rpx;
  517 + color: rgba(0, 0, 0, 0.9);
  518 +
  519 + .required {
  520 + color: red;
  521 + position: absolute;
  522 + top: 50%;
  523 + transform: translateY(-50%);
  524 + left: -16rpx;
  525 + }
  526 + }
  527 +
  528 + }
  529 + &.select-item {
  530 + &.is-empty {
  531 + .uni-list-item__extra-text {
  532 + color: rgba(0, 0, 0, 0.4) !important;
  533 + }
  534 + }
  535 + &.is-filled {
  536 + .uni-list-item__extra-text {
  537 + color: rgba(0, 0, 0, 0.9) !important;
  538 + }
  539 + }
  540 + }
  541 + &.mgb10 {
  542 + margin-bottom: 20rpx;
  543 + }
  544 + }
  545 + }
  546 +
  547 +</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 customer">{{ form.customer.name }}</text>
  7 + <view :class="['status', `status_${form.status}`]" />
  8 + <view class="row"><text class="label">生产厂</text><text class="value">{{ form.workshop.name }}</text>
  9 + </view>
  10 + <view class="row"><text class="label">科办</text><text class="value">{{ form.office.name }}</text>
  11 + </view>
  12 + <view class="row"><text class="label">客户类型</text><text
  13 + class="value">{{ getDicName('ENTERPRISE_TYPE', form.customerType, customerTypeOptions) }}</text>
  14 + </view>
  15 + <view class="row"><text class="label">产品品种</text><text
  16 + class="value">{{ form.productVariety.name }}</text></view>
  17 + <view class="row"><text class="label">月用量</text><text class="value">{{ form.monthlyUsage }}</text>
  18 + </view>
  19 + <view class="row"><text class="label">目标量</text><text class="value">{{ form.targetQuantity }}</text>
  20 + </view>
  21 + <view class="row"><text class="label">行业</text><text class="value">{{ form.industry }}</text></view>
  22 + <view class="row"><text class="label">创建日期</text><text class="value">{{ form.createTime }}</text>
  23 + </view>
  24 + </view>
  25 + <view class="section">
  26 + <view class="row"><text class="label">牌号</text><text class="value">{{ form.mark }}</text></view>
  27 + <view class="row"><text class="label">厚度</text><text class="value">{{ form.thickness }}</text>
  28 + </view>
  29 + <view class="row"><text class="label">宽度</text><text class="value">{{ form.width }}</text></view>
  30 + <view class="row"><text class="label">材质要求</text><text
  31 + class="value">{{ form.materialRequire }}</text></view>
  32 + <view class="row"><text class="label">品质要求</text><text
  33 + class="value">{{ form.qualityRequire }}</text></view>
  34 + <view class="row"><text class="label">同行</text><text class="value">{{ form.peer }}</text></view>
  35 + </view>
  36 + <view class="section">
  37 + <view class="row"><text class="label">核价模式</text><text class="value">{{ form.pricingMode }}</text>
  38 + </view>
  39 + <view class="row"><text class="label">结算天数</text><text class="value">{{ form.settleDays }}</text>
  40 + </view>
  41 + </view>
  42 + <view class="section">
  43 + <view class="row"><text class="label">责任人</text><text class="value">{{ form.chargeUserName }}</text>
  44 + </view>
  45 + </view>
  46 + </view>
  47 + </scroll-view>
  48 + <detail-buttons :buttons="buttons" @click="handleButtonClick" />
  49 + </view>
  50 +</template>
  51 +
  52 +<script>
  53 + import {
  54 + getDetailApi,
  55 + statusOptions
  56 + } from '@/api/devManage.js'
  57 + import {
  58 + getDicName
  59 + } from '@/utils/dic.js'
  60 + import {
  61 + getDicByCodeApi
  62 + } from '@/api/base.js'
  63 + import DetailButtons from '@/components/detail-buttons/index.vue'
  64 +
  65 + export default {
  66 + name: 'DevManageDetail',
  67 + components: {
  68 + DetailButtons
  69 + },
  70 + data() {
  71 + return {
  72 + form: {
  73 + workshop: {},
  74 + office: {},
  75 + customer: {},
  76 + customerType: '',
  77 + chargeUserId: '',
  78 + chargeUserIdName: '',
  79 + productVariety: {},
  80 + monthlyUsage: '',
  81 + targetQuantity: '',
  82 + industry: '',
  83 + mark: '',
  84 + thickness: '',
  85 + width: '',
  86 + materialRequire: '',
  87 + qualityRequire: '',
  88 + peer: '',
  89 + pricingMode: '',
  90 + settleDays: '',
  91 + createTime: '',
  92 + status: ''
  93 + },
  94 + customerTypeOptions: [],
  95 + buttons: [{
  96 + text: '编辑',
  97 + visible: true,
  98 + variant: 'outline',
  99 + event: 'edit',
  100 + style: {
  101 + color: 'red',
  102 + border: '2rpx dashed #2979ff'
  103 + }
  104 + },
  105 + {
  106 + text: '审核详情',
  107 + visible: true,
  108 + variant: 'outline',
  109 + event: 'auditDetail'
  110 + },
  111 + {
  112 + text: '审核',
  113 + visible: true,
  114 + variant: 'danger',
  115 + event: 'audit'
  116 + },
  117 + {
  118 + text: '取消',
  119 + visible: true,
  120 + variant: 'outline',
  121 + event: 'cancel'
  122 + }
  123 + ],
  124 + }
  125 + },
  126 + created() {
  127 + this.loadCustomerTypeOptions()
  128 + },
  129 + onShow() {
  130 + uni.$on('dev_manage_detail_reload', this.onReload)
  131 + },
  132 + onUnload() {
  133 + uni.$off('dev_manage_detail_reload', this.onReload)
  134 + },
  135 + onLoad(query) {
  136 + const id = (query && (query.id || query.code)) || ''
  137 + if (id) this.loadDetail(id)
  138 + },
  139 + methods: {
  140 + onReload(payload) {
  141 + const id = (payload && (payload.id || payload.code)) || this.form.id || this.form.code || ''
  142 + if (id) this.loadDetail(id)
  143 + },
  144 + async loadDetail(id) {
  145 + try {
  146 + const res = await getDetailApi(id)
  147 + console.log('getDetailApi res', res)
  148 + const data = res.data || {};
  149 + this.form = {
  150 + ...data
  151 + }
  152 + } catch (e) {
  153 + console.error('loadDetail error', e)
  154 + }
  155 + },
  156 + async loadCustomerTypeOptions() {
  157 + try {
  158 + const res = await getDicByCodeApi('ENTERPRISE_TYPE')
  159 + const list = res.data || []
  160 + this.customerTypeOptions = Array.isArray(list) ? list : []
  161 + } catch (e) {
  162 + this.customerTypeOptions = []
  163 + }
  164 + },
  165 + getDicName: getDicName,
  166 + handleButtonClick(btn) {
  167 + if (!btn || btn.disabled) return
  168 + if (typeof btn.onClick === 'function') return btn.onClick(this.form, btn.params)
  169 + const e = btn.event || ''
  170 + if (e === 'edit') return this.onEdit(btn && btn.params)
  171 + if (e === 'auditDetail') return this.onAuditDetail(btn && btn.params)
  172 + if (e === 'audit') return this.onAudit(btn && btn.params)
  173 + if (e === 'cancel') return this.onCancel(btn && btn.params)
  174 + },
  175 + onEdit() {
  176 + const id = this.form.id || this.form.code || ''
  177 + const query = id ? ('?id=' + encodeURIComponent(id)) : ''
  178 + uni.navigateTo({
  179 + url: '/pages/dev_manage/modify' + query
  180 + })
  181 + },
  182 + onAuditDetail() {
  183 + uni.showToast({
  184 + title: '审核详情',
  185 + icon: 'none'
  186 + })
  187 + },
  188 + onAudit() {
  189 + uni.showToast({
  190 + title: '审核',
  191 + icon: 'none'
  192 + })
  193 + },
  194 + onCancel() {
  195 + uni.navigateBack()
  196 + }
  197 + }
  198 + }
  199 +</script>
  200 +
  201 +<style lang="scss" scoped>
  202 + .page {
  203 + display: flex;
  204 + flex-direction: column;
  205 + height: 100%;
  206 + }
  207 +
  208 + .scroll {
  209 + flex: 1;
  210 + padding: 8rpx 0 144rpx 0;
  211 + }
  212 +
  213 + .detail-page {
  214 + background: #f3f3f3;
  215 + }
  216 +
  217 + .section {
  218 + padding: 32rpx;
  219 + background: #fff;
  220 + margin-bottom: 20rpx;
  221 + position: relative;
  222 +
  223 + .status {
  224 + position: absolute;
  225 + top: 16rpx;
  226 + right: 52rpx;
  227 + width: 180rpx;
  228 + height: 146rpx;
  229 + background-repeat: no-repeat;
  230 + background-size: 100% 100%;
  231 + background-position: center;
  232 +
  233 + &_1 {
  234 + background-image: url('/static/images/dev_manage/status_1.png');
  235 + }
  236 +
  237 + &_2 {
  238 + background-image: url('/static/images/dev_manage/status_2.png');
  239 + }
  240 +
  241 + &_3 {
  242 + background-image: url('/static/images/dev_manage/status_3.png');
  243 + }
  244 +
  245 + &_4 {
  246 + background-image: url('/static/images/dev_manage/status_4.png');
  247 + }
  248 +
  249 + }
  250 +
  251 + .row {
  252 + display: flex;
  253 + margin-bottom: 28rpx;
  254 +
  255 + &:last-child {
  256 + margin-bottom: 0;
  257 + }
  258 +
  259 + &.customer {
  260 + font-size: 36rpx;
  261 + font-weight: 600;
  262 + color: rgba(0, 0, 0, 0.9);
  263 + }
  264 +
  265 + .label {
  266 + width: 120rpx;
  267 + line-height: 32rpx;
  268 + font-size: 28rpx;
  269 + color: rgba(0, 0, 0, 0.6);
  270 + }
  271 +
  272 + .value {
  273 + flex: 1;
  274 + line-height: 32rpx;
  275 + font-size: 28rpx;
  276 + color: rgba(0, 0, 0, 0.9);
  277 + text-align: right;
  278 + }
  279 + }
  280 + }
  281 +</style>
1 <template> 1 <template>
2 - <view class="page">  
3 - <view class="dev-list-fixed">  
4 - <view class="search-row">  
5 - <uni-search-bar  
6 - v-model="searchKeyword"  
7 - radius="6"  
8 - placeholder="请输入客户名称"  
9 - clearButton="auto"  
10 - cancelButton="none"  
11 - bgColor="#F3F3F3"  
12 - textColor="rgba(0,0,0,0.4)"  
13 - @confirm="search"  
14 - @input="onSearchInput"  
15 - />  
16 - <view class="tool-icons">  
17 - <image class="tool-icon"  
18 - :src="batchMode ? '/static/images/dev_manage/close_icon.png' : '/static/images/dev_manage/batch_icon.png'"  
19 - @click="toggleBatch"  
20 - />  
21 - <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />  
22 - <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />  
23 - </view>  
24 - </view>  
25 -  
26 - <!-- 页内 tabs -->  
27 - <view class="tabs">  
28 - <view  
29 - v-for="(t, i) in tabs"  
30 - :key="i"  
31 - :class="['tab', { active: todoType === t.value }]"  
32 - @click="switchTab(t)"  
33 - >  
34 - {{ t.label }}  
35 - </view>  
36 - </view>  
37 - <view :class="['sub-tabs', `bg${currentWorkshopTypeIndex + 1}`]">  
38 - <view  
39 - v-for="(f, i) in workshopTypeList"  
40 - :key="i"  
41 - :class="['tab', { active: workshopType === f.value }]"  
42 - @click="switchWorkshopType(f, i)"  
43 - >  
44 - {{ f.label }}  
45 - </view>  
46 - </view>  
47 - </view>  
48 -  
49 -  
50 - <!-- 列表卡片组件 -->  
51 - <view :class="['list-box', { 'pad-batch': batchMode }]">  
52 - <card-list  
53 - ref="cardRef"  
54 - :fetch-fn="fetchList"  
55 - :query="query"  
56 - :extra="extraParams"  
57 - :selectable="batchMode"  
58 - row-key="id"  
59 - :selectedKeys.sync="selectedKeys"  
60 - :enable-refresh="true"  
61 - :enable-load-more="true"  
62 - @loaded="onCardLoaded"  
63 - @error="onCardError"  
64 - >  
65 - <template v-slot="{ item, selected }">  
66 - <view class="card">  
67 - <view class="card-header">  
68 - <text class="title omit2">{{ item.customer.name }}</text>  
69 - <text :class="['status', `status_${item.status}`]">{{ filterStatus(item.status) }}</text>  
70 - </view>  
71 - <view class="info-row">  
72 - <text>生产厂</text><text>{{ item.workshop.name }}</text>  
73 - </view>  
74 - <view class="info-row">  
75 - <text>科办</text><text>{{ item.office.name }}</text>  
76 - </view>  
77 - <view class="info-row">  
78 - <text>产品品种</text><text>{{ item.productVariety.name }}</text>  
79 - </view>  
80 - <view class="info-row">  
81 - <text>创建时间</text><text>{{ item.createTime }}</text>  
82 - </view>  
83 - </view>  
84 - </template>  
85 - </card-list>  
86 - </view>  
87 -  
88 - <!-- 批量操作条 待办时才显示驳回和通过-->  
89 - <view v-if="batchMode && todoType === 'WAIT'" class="batch-bar">  
90 - <button class="bbtn reject" @click="batchReject">驳回</button>  
91 - <button class="bbtn" type="primary" @click="batchPass">通过</button>  
92 - </view>  
93 -  
94 - <!-- 筛选弹框 -->  
95 - <filter-modal  
96 - :visible.sync="filterVisible"  
97 - :value.sync="filterForm"  
98 - title="筛选"  
99 - @reset="onFilterReset"  
100 - @confirm="onFilterConfirm"  
101 - >  
102 - <template v-slot="{ model }">  
103 - <view class="filter-form">  
104 - <view class="form-item">  
105 - <view class="label">审核状态</view>  
106 - <uni-data-checkbox  
107 - mode="tag"  
108 - :multiple="false"  
109 - :value-field="'value'"  
110 - :text-field="'text'"  
111 - v-model="model.status"  
112 - @change="onStatusChange"  
113 - :localdata="statusOptions"  
114 - />  
115 - </view>  
116 - <view class="form-item">  
117 - <view class="label">科办</view>  
118 - <view class="fake-select" @click="openOfficeSelect">  
119 - <text v-if="!model.officeId" class="placeholder">请选择</text>  
120 - <text v-else class="value">{{ model.officeName }}</text>  
121 - </view>  
122 - </view>  
123 - <view class="form-item">  
124 - <view class="label">创建时间</view>  
125 - <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />  
126 - </view>  
127 - </view>  
128 - </template>  
129 - </filter-modal>  
130 - <!-- 科办单选底部弹层(通用组件) -->  
131 - <single-select-sheet  
132 - :visible.sync="officeSelectVisible"  
133 - :options="officeList"  
134 - :value.sync="filterForm.officeId"  
135 - title="科办"  
136 - @confirm="onOfficeConfirm"  
137 - />  
138 - <!-- 批量:统一弹框(通过/驳回) -->  
139 - <uni-popup ref="approvePopup" type="center" :mask-click="false">  
140 - <view class="action-modal">  
141 - <view class="header">{{ approveType === 'PASS' ? '通过' : '驳回' }}</view>  
142 - <view class="body">  
143 - <text class="tip">  
144 - {{ approveType === 'PASS' ? '您将通过该信息的审核' : '您将驳回该信息的审核' }}  
145 - </text>  
146 - <uni-easyinput  
147 - v-model="approveComment"  
148 - :placeholder="approveType === 'PASS' ? '请输入通过原因' : '请输入驳回原因'"  
149 - />  
150 - </view>  
151 - <view class="footer">  
152 - <button class="btn cancel" @click="cancelApprove">取消</button>  
153 - <button  
154 - class="btn confirm"  
155 - type="primary"  
156 - @click="confirmApprove"  
157 - >{{ approveType === 'PASS' ? '通过' : '驳回' }}</button>  
158 - </view>  
159 - </view>  
160 - </uni-popup>  
161 - </view> 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"
  10 + :src="batchMode ? '/static/images/dev_manage/close_icon.png' : '/static/images/dev_manage/batch_icon.png'"
  11 + @click="toggleBatch" />
  12 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  13 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  14 + </view>
  15 + </view>
  16 +
  17 + <!-- 页内 tabs -->
  18 + <view class="tabs">
  19 + <view v-for="(t, i) in tabs" :key="i" :class="['tab', { active: todoType === t.value }]"
  20 + @click="switchTab(t)">
  21 + {{ t.label }}
  22 + </view>
  23 + </view>
  24 + <view :class="['sub-tabs', `bg${currentWorkshopTypeIndex + 1}`]">
  25 + <view v-for="(f, i) in workshopTypeList" :key="i" :class="['tab', { active: workshopType === f.value }]"
  26 + @click="switchWorkshopType(f, i)">
  27 + {{ f.label }}
  28 + </view>
  29 + </view>
  30 + </view>
  31 +
  32 +
  33 + <!-- 列表卡片组件 -->
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" :selectable="batchMode"
  36 + row-key="id" :selectedKeys.sync="selectedKeys" :enable-refresh="true" :enable-load-more="true"
  37 + @loaded="onCardLoaded" @error="onCardError">
  38 + <template v-slot="{ item, selected }">
  39 + <view class="card" @click.stop="onCardClick(item)">
  40 + <view class="card-header">
  41 + <text class="title omit2">{{ item.customer.name }}</text>
  42 + <text :class="['status', `status_${item.status}`]">{{ filterStatus(item.status) }}</text>
  43 + </view>
  44 + <view class="info-row">
  45 + <text>生产厂</text><text>{{ item.workshop.name }}</text>
  46 + </view>
  47 + <view class="info-row">
  48 + <text>科办</text><text>{{ item.office.name }}</text>
  49 + </view>
  50 + <view class="info-row">
  51 + <text>产品品种</text><text>{{ item.productVariety.name }}</text>
  52 + </view>
  53 + <view class="info-row">
  54 + <text>创建时间</text><text>{{ item.createTime }}</text>
  55 + </view>
  56 + </view>
  57 + </template>
  58 + </card-list>
  59 + </view>
  60 +
  61 + <!-- 批量操作条 待办时才显示驳回和通过-->
  62 + <view v-if="batchMode && todoType === 'WAIT'" class="batch-bar">
  63 + <button class="bbtn reject" @click="batchReject">驳回</button>
  64 + <button class="bbtn" type="primary" @click="batchPass">通过</button>
  65 + </view>
  66 +
  67 + <!-- 筛选弹框 -->
  68 + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset"
  69 + @confirm="onFilterConfirm">
  70 + <template v-slot="{ model }">
  71 + <view class="filter-form">
  72 + <view class="form-item">
  73 + <view class="label">审核状态</view>
  74 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  75 + v-model="model.status" @change="onStatusChange" :localdata="statusOptions" />
  76 + </view>
  77 + <view class="form-item">
  78 + <view class="label">科办</view>
  79 + <view class="fake-select" @click="openOfficeSelect">
  80 + <text v-if="!model.officeId" class="placeholder">请选择</text>
  81 + <text v-else class="value">{{ model.officeName }}</text>
  82 + </view>
  83 + </view>
  84 + <view class="form-item">
  85 + <view class="label">创建时间</view>
  86 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  87 + </view>
  88 + </view>
  89 + </template>
  90 + </filter-modal>
  91 + <!-- 科办单选底部弹层(通用组件) -->
  92 + <single-select-sheet :visible.sync="officeSelectVisible" :options="officeList" :value.sync="filterForm.officeId"
  93 + title="科办" @confirm="onOfficeConfirm" />
  94 + <!-- 批量:统一弹框(通过/驳回) -->
  95 + <uni-popup ref="approvePopup" type="center" :mask-click="false">
  96 + <view class="action-modal">
  97 + <view class="header">{{ approveType === 'PASS' ? '通过' : '驳回' }}</view>
  98 + <view class="body">
  99 + <text class="tip">
  100 + {{ approveType === 'PASS' ? '您将通过该信息的审核' : '您将驳回该信息的审核' }}
  101 + </text>
  102 + <uni-easyinput v-model="approveComment"
  103 + :placeholder="approveType === 'PASS' ? '请输入通过原因' : '请输入驳回原因'" />
  104 + </view>
  105 + <view class="footer">
  106 + <button class="btn cancel" @click="cancelApprove">取消</button>
  107 + <button class="btn confirm" type="primary"
  108 + @click="confirmApprove">{{ approveType === 'PASS' ? '通过' : '驳回' }}</button>
  109 + </view>
  110 + </view>
  111 + </uni-popup>
  112 + </view>
162 </template> 113 </template>
163 114
164 <script> 115 <script>
165 -import CardList from '@/components/card/index.vue'  
166 -import FilterModal from '@/components/filter/index.vue'  
167 -import SingleSelectSheet from '@/components/single-select/index.vue'  
168 -import { queryApi, officeQueryApi, statusOptions, getTodoTypeStatisticsApi, batchApproveApi } from '@/api/devManage.js'  
169 -import {getDicByCodes, getDicName} from '@/utils/dic';  
170 -  
171 -export default {  
172 - components: { CardList, FilterModal, SingleSelectSheet },  
173 - data() {  
174 - return {  
175 - searchKeyword: '',  
176 - searchKeywordDebounced: '',  
177 - officeList: [], // 科办列表下拉选  
178 - tabs: [],  
179 - todoType: '',  
180 - workshopTypeList: [  
181 - {  
182 - label: '全部',  
183 - value: ''  
184 - },  
185 - {  
186 - label: '一、二分厂',  
187 - value: 'TYPE_1'  
188 - },  
189 - {  
190 - label: '三、四分厂',  
191 - value: 'TYPE_2'  
192 - }  
193 - ],  
194 - workshopType: '',  
195 - currentWorkshopTypeIndex: 0,  
196 - // 字典选项缓存  
197 - // dicOptions: {  
198 - // ENTERPRISE_TYPE: [], // 客户类型  
199 - // },  
200 - // 给到 card 的筛选值  
201 - query: { status: '', officeId: '', officeName: '', dateRange: [] },  
202 - extraParams: {},  
203 -  
204 - // 批量选择  
205 - batchMode: false,  
206 - selectedKeys: [],  
207 - rowKey: 'id',  
208 - selectedRows: [],  
209 - currentItems: [],  
210 -  
211 - // 筛选弹框  
212 - filterVisible: false,  
213 - filterForm: { status: '', officeId: '', officeName: '', dateRange: [] },  
214 - officeSelectVisible: false,  
215 - // 审核状态枚举(来自 api/devManage.js)  
216 - statusOptions,  
217 - // 批量操作弹框数据  
218 - approveType: 'PASS', // PASS:通过;REFUSE:驳回  
219 - approveComment: ''  
220 - }  
221 - },  
222 - computed: {  
223 - extraCombined() {  
224 - return {  
225 - customerName: this.searchKeywordDebounced || undefined,  
226 - todoType: this.todoType || undefined,  
227 - workshopType: this.workshopType || undefined  
228 - }  
229 - }  
230 - },  
231 - watch: {  
232 - extraCombined: {  
233 - deep: true,  
234 - handler(v) {  
235 - this.extraParams = v  
236 - },  
237 - immediate: true  
238 - },  
239 - // 勾选变化时同步 selectedRows  
240 - selectedKeys: {  
241 - deep: true,  
242 - handler() {  
243 - this.updateSelectedRows()  
244 - }  
245 - }  
246 - },  
247 - created() {  
248 - this.getOffice();  
249 - // this.loadAllDicData();  
250 - // 获取待办类型数量统计  
251 - this.getTodoTypeStatisticsFun()  
252 - },  
253 - // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多  
254 - onReachBottom() {  
255 - if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {  
256 - this.$refs.cardRef.onLoadMore()  
257 - }  
258 - },  
259 - beforeDestroy() {  
260 - if (this.searchDebounceTimer) {  
261 - clearTimeout(this.searchDebounceTimer)  
262 - this.searchDebounceTimer = null  
263 - }  
264 - },  
265 - methods: {  
266 - onCardLoaded({ items }) {  
267 - this.currentItems = items  
268 - this.updateSelectedRows()  
269 - },  
270 - onCardError() {  
271 - uni.showToast({ title: '列表加载失败', icon: 'none' })  
272 - },  
273 - // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新  
274 - onSearchInput(val) {  
275 - if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)  
276 - this.searchDebounceTimer = setTimeout(() => {  
277 - this.searchKeywordDebounced = this.searchKeyword  
278 - this.searchDebounceTimer = null  
279 - }, 1200)  
280 - },  
281 - // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新  
282 - search(e) {  
283 - const val = e && e.value != null ? e.value : this.searchKeyword  
284 - this.searchKeyword = val  
285 - this.searchKeywordDebounced = val  
286 - },  
287 - switchTab(item) {  
288 - this.todoType = item.value;  
289 - },  
290 - switchWorkshopType(item, i) {  
291 - this.workshopType = item.value;  
292 - this.currentWorkshopTypeIndex = i;  
293 - },  
294 - openFilter() {  
295 - this.filterVisible = true  
296 - },  
297 - onFilterReset(payload) {  
298 - // 保持弹框不关闭,仅同步表单  
299 - this.filterForm = payload  
300 - },  
301 - onFilterConfirm(payload) {  
302 - // 兜底:部分端上 uni-data-checkbox(mode=tag) 不触发 v-model  
303 - if ((payload.status === '' || payload.status == null) && this.filterForm.status !== '') {  
304 - payload.status = this.filterForm.status  
305 - }  
306 - console.log('onFilterConfirm',payload)  
307 - // 关闭弹框,父组件给到 card 的条件  
308 - this.query = { ...payload }  
309 - },  
310 - onStatusChange(e) {  
311 - const raw = e && e.detail && e.detail.value !== undefined  
312 - ? e.detail.value  
313 - : (e && e.value !== undefined ? e.value : '')  
314 - // 直接同步到外层 filterForm,驱动 FilterModal 的 innerModel 更新  
315 - this.filterForm.status = raw  
316 - },  
317 - openOfficeSelect() {  
318 - this.officeSelectVisible = true  
319 - },  
320 - onOfficeConfirm(payload) {  
321 - const val = payload && payload.value != null ? payload.value : ''  
322 - const label = payload && payload.label != null ? payload.label : ''  
323 - this.filterForm.officeId = val  
324 - this.filterForm.officeName = label  
325 - },  
326 - toggleBatch() {  
327 - this.batchMode = !this.batchMode  
328 - if (!this.batchMode) this.selectedKeys = []  
329 - },  
330 - // 根据 selectedKeys 与当前页 items 计算选中行对象  
331 - updateSelectedRows() {  
332 - const key = this.rowKey || 'id'  
333 - const keys = Array.isArray(this.selectedKeys) ? this.selectedKeys : []  
334 - const items = Array.isArray(this.currentItems) ? this.currentItems : []  
335 - this.selectedRows = items.filter(it => keys.includes(it && it[key]))  
336 - },  
337 - batchReject() {  
338 - if (this.selectedKeys.length === 0) return uni.showToast({ title: '请选择记录', icon: 'none' })  
339 - this.approveType = 'REFUSE'  
340 - this.approveComment = ''  
341 - this.$refs.approvePopup && this.$refs.approvePopup.open()  
342 - },  
343 - batchPass() {  
344 - if (this.selectedKeys.length === 0) return uni.showToast({ title: '请选择记录', icon: 'none' })  
345 - this.approveType = 'PASS'  
346 - this.approveComment = ''  
347 - this.$refs.approvePopup && this.$refs.approvePopup.open()  
348 - },  
349 - cancelApprove() {  
350 - this.$refs.approvePopup && this.$refs.approvePopup.close()  
351 - this.approveComment = ''  
352 - },  
353 - async confirmApprove() {  
354 - this.flowTaskIds = this.selectedRows.map((item) => item.flowTaskId || '' + "");  
355 - batchApproveApi({ taskIds: this.flowTaskIds, approveType: this.approveType, message: this.approveComment }).then((res) => {  
356 - console.log('batchApproveApi_res', res)  
357 - uni.showToast({ title: this.approveType === 'PASS' ? '已通过' : '已驳回', icon: 'none' })  
358 - this.$refs.approvePopup && this.$refs.approvePopup.close()  
359 - this.approveComment = '';  
360 - this.selectedKeys = [];  
361 - this.selectedRows = [];  
362 - this.batchMode = false;  
363 - this.$refs.cardRef && this.$refs.cardRef.reload && this.$refs.cardRef.reload();  
364 - this.getTodoTypeStatisticsFun()  
365 - }).catch(() => {  
366 - console.error('confirmApprove error', e)  
367 - uni.showToast({ title: '操作失败', icon: 'none' })  
368 - })  
369 - },  
370 - onAdd() {  
371 - uni.showToast({ title: '点击新增', icon: 'none' })  
372 - },  
373 - // 列表接口(真实请求)  
374 - fetchList({ pageIndex, pageSize, query, extra }) {  
375 - const params = { pageIndex, pageSize, ...extra, ...query }  
376 - // 处理日期范围  
377 - if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {  
378 - params.createStartTime = params.dateRange[0] + ' 00:00:00'  
379 - params.createEndTime = params.dateRange[1] + ' 23:59:59'  
380 - delete params.dateRange  
381 - }  
382 - // 关键字(使用去抖后的值避免频繁触发)  
383 - if (this.searchKeywordDebounced) {  
384 - params.customerName = this.searchKeywordDebounced  
385 - }  
386 - return queryApi(params)  
387 - .then(res => {  
388 - console.log('fetchList', res)  
389 - const _data = res.data || {};  
390 - const records = _data.datas || [];  
391 - const totalCount = _data.totalCount || 0;  
392 - const hasNext = _data.hasNext || false  
393 - return { records, totalCount, hasNext }  
394 - })  
395 - .catch(err => {  
396 - console.error('fetchList error', err)  
397 - this.onCardError()  
398 - return { records: [], totalCount: 0, hasNext: false }  
399 - })  
400 - },  
401 - getOffice() {  
402 - officeQueryApi({  
403 - pageIndex: 1,  
404 - pageSize: 9999,  
405 - }).then((res) => {  
406 - const _data = res.data.datas.map(item => {  
407 - return {  
408 - label: item.name,  
409 - value: item.id  
410 - }  
411 - }) || []  
412 - console.log('officeList_data', _data)  
413 - this.officeList = _data;  
414 - });  
415 - },  
416 - getTodoTypeStatisticsFun() {  
417 - getTodoTypeStatisticsApi().then((res) => {  
418 - this.tabs = [  
419 - {  
420 - label: `全部(${res.data.allCount > 999 ? '999+' : res.data.allCount || 0})`,  
421 - value: '' 116 + import CardList from '@/components/card/index.vue'
  117 + import FilterModal from '@/components/filter/index.vue'
  118 + import SingleSelectSheet from '@/components/single-select/index.vue'
  119 + import {
  120 + queryApi,
  121 + officeQueryApi,
  122 + statusOptions,
  123 + getTodoTypeStatisticsApi,
  124 + batchApproveApi
  125 + } from '@/api/devManage.js'
  126 + import {
  127 + getDicByCodes,
  128 + getDicName
  129 + } from '@/utils/dic';
  130 +
  131 + export default {
  132 + components: {
  133 + CardList,
  134 + FilterModal,
  135 + SingleSelectSheet
  136 + },
  137 + data() {
  138 + return {
  139 + searchKeyword: '',
  140 + searchKeywordDebounced: '',
  141 + officeList: [], // 科办列表下拉选
  142 + tabs: [],
  143 + todoType: '',
  144 + workshopTypeList: [{
  145 + label: '全部',
  146 + value: ''
  147 + },
  148 + {
  149 + label: '一、二分厂',
  150 + value: 'TYPE_1'
  151 + },
  152 + {
  153 + label: '三、四分厂',
  154 + value: 'TYPE_2'
  155 + }
  156 + ],
  157 + workshopType: '',
  158 + currentWorkshopTypeIndex: 0,
  159 + // 给到 card 的筛选值
  160 + query: {
  161 + status: '',
  162 + officeId: '',
  163 + officeName: '',
  164 + dateRange: []
  165 + },
  166 + extraParams: {},
  167 +
  168 + // 批量选择
  169 + batchMode: false,
  170 + selectedKeys: [],
  171 + rowKey: 'id',
  172 + selectedRows: [],
  173 + currentItems: [],
  174 +
  175 + // 筛选弹框
  176 + filterVisible: false,
  177 + filterForm: {
  178 + status: '',
  179 + officeId: '',
  180 + officeName: '',
  181 + dateRange: []
  182 + },
  183 + officeSelectVisible: false,
  184 + // 审核状态枚举(来自 api/devManage.js)
  185 + statusOptions,
  186 + // 批量操作弹框数据
  187 + approveType: 'PASS', // PASS:通过;REFUSE:驳回
  188 + approveComment: ''
  189 + }
  190 + },
  191 + computed: {
  192 + extraCombined() {
  193 + return {
  194 + customerName: this.searchKeywordDebounced || undefined,
  195 + todoType: this.todoType || undefined,
  196 + workshopType: this.workshopType || undefined
  197 + }
  198 + }
  199 + },
  200 + watch: {
  201 + extraCombined: {
  202 + deep: true,
  203 + handler(v) {
  204 + this.extraParams = v
  205 + },
  206 + immediate: true
  207 + },
  208 + // 勾选变化时同步 selectedRows
  209 + selectedKeys: {
  210 + deep: true,
  211 + handler() {
  212 + this.updateSelectedRows()
  213 + }
  214 + }
  215 + },
  216 + created() {
  217 + this.getOffice();
  218 + // this.loadAllDicData();
  219 + // 获取待办类型数量统计
  220 + this.getTodoTypeStatisticsFun()
  221 + },
  222 + onLoad() {},
  223 + // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  224 + onReachBottom() {
  225 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  226 + this.$refs.cardRef.onLoadMore()
  227 + }
  228 + },
  229 + beforeDestroy() {
  230 + if (this.searchDebounceTimer) {
  231 + clearTimeout(this.searchDebounceTimer)
  232 + this.searchDebounceTimer = null
  233 + }
  234 + },
  235 + methods: {
  236 + onCardLoaded({
  237 + items
  238 + }) {
  239 + this.currentItems = items
  240 + this.updateSelectedRows()
  241 + },
  242 + onCardError() {
  243 + uni.showToast({
  244 + title: '列表加载失败',
  245 + icon: 'none'
  246 + })
  247 + },
  248 + // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
  249 + onSearchInput(val) {
  250 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  251 + this.searchDebounceTimer = setTimeout(() => {
  252 + this.searchKeywordDebounced = this.searchKeyword
  253 + this.searchDebounceTimer = null
  254 + }, 1200)
  255 + },
  256 + // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
  257 + search(e) {
  258 + const val = e && e.value != null ? e.value : this.searchKeyword
  259 + this.searchKeyword = val
  260 + this.searchKeywordDebounced = val
  261 + },
  262 + switchTab(item) {
  263 + this.todoType = item.value;
  264 + },
  265 + switchWorkshopType(item, i) {
  266 + this.workshopType = item.value;
  267 + this.currentWorkshopTypeIndex = i;
  268 + },
  269 + openFilter() {
  270 + this.filterVisible = true
  271 + },
  272 + onFilterReset(payload) {
  273 + // 保持弹框不关闭,仅同步表单
  274 + this.filterForm = payload
  275 + },
  276 + onFilterConfirm(payload) {
  277 + // 兜底:部分端上 uni-data-checkbox(mode=tag) 不触发 v-model
  278 + if ((payload.status === '' || payload.status == null) && this.filterForm.status !== '') {
  279 + payload.status = this.filterForm.status
  280 + }
  281 + console.log('onFilterConfirm', payload)
  282 + // 关闭弹框,父组件给到 card 的条件
  283 + this.query = {
  284 + ...payload
  285 + }
  286 + },
  287 + onStatusChange(e) {
  288 + const raw = e && e.detail && e.detail.value !== undefined ?
  289 + e.detail.value :
  290 + (e && e.value !== undefined ? e.value : '')
  291 + // 直接同步到外层 filterForm,驱动 FilterModal 的 innerModel 更新
  292 + this.filterForm.status = raw
  293 + },
  294 + openOfficeSelect() {
  295 + this.officeSelectVisible = true
  296 + },
  297 + onOfficeConfirm(payload) {
  298 + const val = payload && payload.value != null ? payload.value : ''
  299 + const label = payload && payload.label != null ? payload.label : ''
  300 + this.filterForm.officeId = val
  301 + this.filterForm.officeName = label
  302 + },
  303 + toggleBatch() {
  304 + this.batchMode = !this.batchMode
  305 + if (!this.batchMode) this.selectedKeys = []
  306 + },
  307 + // 根据 selectedKeys 与当前页 items 计算选中行对象
  308 + updateSelectedRows() {
  309 + const key = this.rowKey || 'id'
  310 + const keys = Array.isArray(this.selectedKeys) ? this.selectedKeys : []
  311 + const items = Array.isArray(this.currentItems) ? this.currentItems : []
  312 + this.selectedRows = items.filter(it => keys.includes(it && it[key]))
  313 + },
  314 + batchReject() {
  315 + if (this.selectedKeys.length === 0) return uni.showToast({
  316 + title: '请选择记录',
  317 + icon: 'none'
  318 + })
  319 + this.approveType = 'REFUSE'
  320 + this.approveComment = ''
  321 + this.$refs.approvePopup && this.$refs.approvePopup.open()
  322 + },
  323 + batchPass() {
  324 + if (this.selectedKeys.length === 0) return uni.showToast({
  325 + title: '请选择记录',
  326 + icon: 'none'
  327 + })
  328 + this.approveType = 'PASS'
  329 + this.approveComment = ''
  330 + this.$refs.approvePopup && this.$refs.approvePopup.open()
  331 + },
  332 + cancelApprove() {
  333 + this.$refs.approvePopup && this.$refs.approvePopup.close()
  334 + this.approveComment = ''
  335 + },
  336 + async confirmApprove() {
  337 + this.flowTaskIds = this.selectedRows.map((item) => item.flowTaskId || '' + "");
  338 + batchApproveApi({
  339 + taskIds: this.flowTaskIds,
  340 + approveType: this.approveType,
  341 + message: this.approveComment
  342 + }).then((res) => {
  343 + console.log('batchApproveApi_res', res)
  344 + uni.showToast({
  345 + title: this.approveType === 'PASS' ? '已通过' : '已驳回',
  346 + icon: 'none'
  347 + })
  348 + this.$refs.approvePopup && this.$refs.approvePopup.close()
  349 + this.approveComment = '';
  350 + this.selectedKeys = [];
  351 + this.selectedRows = [];
  352 + this.batchMode = false;
  353 + this.$refs.cardRef && this.$refs.cardRef.reload && this.$refs.cardRef.reload();
  354 + this.getTodoTypeStatisticsFun()
  355 + }).catch(() => {
  356 + console.error('confirmApprove error', e)
  357 + uni.showToast({
  358 + title: '操作失败',
  359 + icon: 'none'
  360 + })
  361 + })
  362 + },
  363 + onAdd() {
  364 + uni.navigateTo({
  365 + url: '/pages/dev_manage/add'
  366 + })
  367 + },
  368 + // 列表接口(真实请求)
  369 + fetchList({
  370 + pageIndex,
  371 + pageSize,
  372 + query,
  373 + extra
  374 + }) {
  375 + const params = {
  376 + pageIndex,
  377 + pageSize,
  378 + ...extra,
  379 + ...query
  380 + }
  381 + // 处理日期范围
  382 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  383 + params.createStartTime = params.dateRange[0] + ' 00:00:00'
  384 + params.createEndTime = params.dateRange[1] + ' 23:59:59'
  385 + delete params.dateRange
  386 + }
  387 + // 关键字(使用去抖后的值避免频繁触发)
  388 + if (this.searchKeywordDebounced) {
  389 + params.customerName = this.searchKeywordDebounced
  390 + }
  391 + return queryApi(params)
  392 + .then(res => {
  393 + console.log('fetchList', res)
  394 + const _data = res.data || {};
  395 + const records = _data.datas || [];
  396 + const totalCount = _data.totalCount || 0;
  397 + const hasNext = _data.hasNext || false
  398 + return {
  399 + records,
  400 + totalCount,
  401 + hasNext
  402 + }
  403 + })
  404 + .catch(err => {
  405 + console.error('fetchList error', err)
  406 + this.onCardError()
  407 + return {
  408 + records: [],
  409 + totalCount: 0,
  410 + hasNext: false
  411 + }
  412 + })
  413 + },
  414 + getOffice() {
  415 + officeQueryApi({
  416 + pageIndex: 1,
  417 + pageSize: 9999,
  418 + }).then((res) => {
  419 + const _data = res.data.datas.map(item => {
  420 + return {
  421 + label: item.name,
  422 + value: item.id
  423 + }
  424 + }) || []
  425 + console.log('officeList_data', _data)
  426 + this.officeList = _data;
  427 + });
  428 + },
  429 + getTodoTypeStatisticsFun() {
  430 + getTodoTypeStatisticsApi().then((res) => {
  431 + this.tabs = [{
  432 + label: `全部(${res.data.allCount > 999 ? '999+' : res.data.allCount || 0})`,
  433 + value: ''
  434 + },
  435 + {
  436 + label: `已办(${res.data.completedCount > 999 ? '999+' : res.data.completedCount || 0})`,
  437 + value: 'COMPLETED'
  438 + },
  439 + {
  440 + label: `待办(${res.data.todoCount > 999 ? '999+' : res.data.todoCount || 0})`,
  441 + value: 'WAIT'
  442 + }
  443 + ]
  444 + });
  445 + },
  446 + filterStatus(status) {
  447 + return this.statusOptions.filter(item => item.value === status)[0].text || '';
422 }, 448 },
423 - {  
424 - label: `已办(${res.data.completedCount > 999 ? '999+' : res.data.completedCount || 0})`,  
425 - value: 'COMPLETED' 449 + onCardClick(item) {
  450 + const id = (item && (item.id || item.code)) || ''
  451 + if (!id) return
  452 + const query = '?id=' + encodeURIComponent(id)
  453 + uni.navigateTo({ url: '/pages/dev_manage/detail' + query })
426 }, 454 },
427 - {  
428 - label: `待办(${res.data.todoCount > 999 ? '999+' : res.data.todoCount || 0})`,  
429 - value: 'WAIT'  
430 - }  
431 - ]  
432 - });  
433 - },  
434 - // 加载所有需要的字典数据  
435 - // async loadAllDicData() {  
436 - // try {  
437 - // // 批量加载所有字典数据  
438 - // const dicCodes = ['ENTERPRISE_TYPE'];  
439 - // const results = await getDicByCodes(dicCodes);  
440 - // // 更新字典选项  
441 - // this.dicOptions.ENTERPRISE_TYPE = results.ENTERPRISE_TYPE.data || [];  
442 - // } catch (error) {  
443 - // console.error('加载字典数据失败:', error);  
444 - // }  
445 - // },  
446 - filterStatus(status) {  
447 - return this.statusOptions.filter(item => item.value === status)[0].text || ''; 455 +
  456 + }
448 } 457 }
449 - }  
450 -}  
451 </script> 458 </script>
452 459
453 <style lang="scss" scoped> 460 <style lang="scss" scoped>
454 -.page {  
455 - display: flex;  
456 - flex-direction: column;  
457 - height: 100vh;  
458 -}  
459 -.dev-list-fixed {  
460 - position: fixed;  
461 - top: 96rpx;  
462 - left: 0;  
463 - right: 0;  
464 - z-index: 2;  
465 - background: #fff;  
466 -  
467 - .search-row {  
468 - display: flex;  
469 - align-items: center;  
470 - padding: 16rpx 32rpx;  
471 - .uni-searchbar {  
472 - padding: 0;  
473 - flex: 1;  
474 - }  
475 - .tool-icons {  
476 - display: flex;  
477 - .tool-icon {  
478 - width: 48rpx; height: 48rpx; display: block;  
479 - margin-left: 32rpx;  
480 - }  
481 - }  
482 - }  
483 -  
484 - .tabs {  
485 - display: flex;  
486 - align-items: center;  
487 - justify-content: space-between;  
488 - padding: 26rpx 0;  
489 - border-bottom: solid 1px #E7E7E7;  
490 - .tab {  
491 - width: 180rpx;  
492 - text-align: center;  
493 - color: #666;  
494 - color: rgba(0,0,0,0.9);  
495 - line-height: 44rpx;  
496 - position: relative;  
497 - }  
498 - .tab.active { color: $theme-primary; font-weight: 600; }  
499 - .tab.active::after {  
500 - content: '';  
501 - position: absolute;  
502 - left: 50%;  
503 - transform: translateX(-50%);  
504 - bottom: -13px;  
505 - width: 32rpx;  
506 - height: 6rpx;  
507 - border-radius: 4rpx;  
508 - background: $theme-primary;  
509 - }  
510 - }  
511 - .sub-tabs {  
512 - display: flex;  
513 - align-items: center;  
514 - height: 96rpx;  
515 - &.bg1 {  
516 - background-image: url('/static/images/dev_manage/tab_1_icon.png');  
517 - background-repeat: no-repeat;  
518 - background-position: right center;  
519 - background-size: cover;  
520 - }  
521 - &.bg2 {  
522 - background-image: url('/static/images/dev_manage/tab_2_icon.png');  
523 - background-repeat: no-repeat;  
524 - background-position: right center;  
525 - background-size: cover;  
526 - }  
527 - &.bg3 {  
528 - background-image: url('/static/images/dev_manage/tab_3_icon.png');  
529 - background-repeat: no-repeat;  
530 - background-position: right center;  
531 - background-size: cover;  
532 - }  
533 - .tab {  
534 - width: 50%;  
535 - height: 96rpx;  
536 - line-height: 96rpx;  
537 - text-align: center;  
538 - font-weight: 600;  
539 - color: rgba(0,0,0,0.9);  
540 - &.active {  
541 - color: $theme-primary;  
542 - }  
543 - }  
544 - }  
545 -}  
546 -/* 仅当前页覆盖 uni-search-bar 盒子高度 */  
547 -::v-deep .uni-searchbar__box {  
548 - height: 80rpx !important;  
549 - justify-content: start;  
550 - .uni-searchbar__box-search-input {  
551 - font-size: 32rpx !important;  
552 - }  
553 -}  
554 -  
555 -.list-box {  
556 - flex: 1;  
557 - padding-top: 314rpx;  
558 - &.pad-batch { padding-bottom: 144rpx; }  
559 - .card { position: relative; }  
560 - .card-header {  
561 - margin-bottom: 28rpx;  
562 - position: relative;  
563 -  
564 - .title {  
565 - font-size: 36rpx;  
566 - font-weight: 600;  
567 - line-height: 50rpx;  
568 - color: #323241;  
569 - width: 578rpx;  
570 - } 461 + .page {
  462 + display: flex;
  463 + flex-direction: column;
  464 + height: 100vh;
  465 + }
571 466
572 - .status {  
573 - font-size: 30rpx;  
574 - font-weight: 600;  
575 - position: absolute;  
576 - top: -36rpx;  
577 - right: -32rpx;  
578 - height: 48rpx;  
579 - line-height: 48rpx;  
580 - color: #fff;  
581 - font-size: 24rpx;  
582 - padding: 0 14rpx;  
583 - border-radius: 6rpx;  
584 - &.status_1 {  
585 - background: #3D48A3;  
586 - }  
587 - &.status_2 {  
588 - background: #2BA471;  
589 - }  
590 - &.status_3 {  
591 - background: #D54941;  
592 - }  
593 - &.status_4 {  
594 - background: #E7E7E7;  
595 - color: #323241;  
596 - }  
597 - }  
598 - }  
599 - .info-row {  
600 - display: flex;  
601 - align-items: center;  
602 - color: rgba(0,0,0,0.6);  
603 - font-size: 28rpx;  
604 - margin-bottom: 24rpx;  
605 - height: 32rpx;  
606 - &:last-child {  
607 - margin-bottom: 0;  
608 - }  
609 - text {  
610 - width: 60%;  
611 - &:last-child {  
612 - color: rgba(0,0,0,0.9);  
613 - width: 40%;  
614 - }  
615 - }  
616 - }  
617 -}  
618 -  
619 -.batch-bar {  
620 - position: fixed;  
621 - left: 0;  
622 - right: 0;  
623 - bottom: 0;  
624 - display: flex;  
625 - gap: 36rpx;  
626 - padding: 32rpx 30rpx;  
627 - background: #fff;  
628 - box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.04);  
629 - .bbtn {  
630 - flex: 1;  
631 - height: 80rpx;  
632 - line-height: 80rpx;  
633 - border-radius: 12rpx;  
634 - font-size: 32rpx;  
635 - display: flex;  
636 - align-items: center;  
637 - justify-content: center;  
638 - }  
639 - .reject {  
640 - background: #fff;  
641 - border: 1rpx solid $uni-color-error;  
642 - color: $uni-color-error;  
643 - }  
644 -}  
645 -  
646 -.filter-form {  
647 - .form-item { margin-bottom: 24rpx; }  
648 - .label {  
649 - margin-bottom: 20rpx;  
650 - color: rgba(0,0,0,0.9);  
651 - height: 44rpx;  
652 - line-height: 44rpx;  
653 - font-size: 30rpx;  
654 - }  
655 - .fake-select {  
656 - height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;  
657 - .placeholder { color: #999; }  
658 - .value { color: #333; }  
659 - }  
660 -}  
661 -  
662 -/* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */  
663 -::v-deep .filter-form .uni-data-checklist .checklist-group {  
664 - .checklist-box {  
665 - &.is--tag {  
666 - width: 212rpx;  
667 - margin-top: 0;  
668 - margin-bottom: 24rpx;  
669 - margin-right: 24rpx;  
670 - height: 80rpx;  
671 - padding: 0;  
672 - border-radius: 12rpx;  
673 - background-color: #f3f3f3;  
674 - border-color: #f3f3f3;  
675 - &:nth-child(3n) {  
676 - margin-right: 0;  
677 - }  
678 - .checklist-content {  
679 - display: flex;  
680 - justify-content: center;  
681 - }  
682 - .checklist-text {  
683 - color: rgba(0,0,0,0.9);  
684 - font-size: 28rpx;  
685 - }  
686 - }  
687 - &.is-checked {  
688 - background-color: $theme-primary-plain-bg !important;  
689 - border-color: $theme-primary-plain-bg !important;  
690 - .checklist-text {  
691 - color: $theme-primary !important;  
692 - }  
693 - }  
694 - }  
695 -  
696 -}  
697 -  
698 -.action-modal {  
699 - width: 560rpx;  
700 - padding: 32rpx 28rpx 20rpx;  
701 - background: #fff;  
702 - border-radius: 20rpx;  
703 - .header {  
704 - text-align: center;  
705 - font-size: 34rpx;  
706 - font-weight: 600;  
707 - margin-bottom: 12rpx;  
708 - color: rgba(0,0,0,0.9);  
709 - }  
710 - .body {  
711 - padding: 12rpx 4rpx 24rpx;  
712 - .tip {  
713 - display: block;  
714 - text-align: center;  
715 - font-size: 32rpx;  
716 - color: rgba(0,0,0,0.6);  
717 - margin-bottom: 32rpx;  
718 - }  
719 - ::v-deep .uni-easyinput {  
720 - width: 100%;  
721 - }  
722 - }  
723 - .footer {  
724 - display: flex;  
725 - justify-content: space-between;  
726 - gap: 16rpx;  
727 - .btn {  
728 - flex: 1;  
729 - height: 80rpx;  
730 - line-height: 80rpx;  
731 - border-radius: 12rpx;  
732 - font-size: 30rpx;  
733 - font-size: 32rpx;  
734 - &::after {  
735 - border: none;  
736 - }  
737 - }  
738 - .cancel {  
739 - background: #fff;  
740 - color: rgba(0,0,0,0.9);  
741 - }  
742 - .confirm {  
743 - background: #fff !important;  
744 - color: $theme-primary !important;  
745 - }  
746 - }  
747 -} 467 + .dev-list-fixed {
  468 + position: fixed;
  469 + top: 96rpx;
  470 + left: 0;
  471 + right: 0;
  472 + z-index: 2;
  473 + background: #fff;
  474 +
  475 + .search-row {
  476 + display: flex;
  477 + align-items: center;
  478 + padding: 16rpx 32rpx;
  479 +
  480 + .uni-searchbar {
  481 + padding: 0;
  482 + flex: 1;
  483 + }
  484 +
  485 + .tool-icons {
  486 + display: flex;
  487 +
  488 + .tool-icon {
  489 + width: 48rpx;
  490 + height: 48rpx;
  491 + display: block;
  492 + margin-left: 32rpx;
  493 + }
  494 + }
  495 + }
  496 +
  497 + .tabs {
  498 + display: flex;
  499 + align-items: center;
  500 + justify-content: space-between;
  501 + padding: 26rpx 0;
  502 + border-bottom: solid 1px #E7E7E7;
  503 +
  504 + .tab {
  505 + width: 180rpx;
  506 + text-align: center;
  507 + color: #666;
  508 + color: rgba(0, 0, 0, 0.9);
  509 + line-height: 44rpx;
  510 + position: relative;
  511 + }
  512 +
  513 + .tab.active {
  514 + color: $theme-primary;
  515 + font-weight: 600;
  516 + }
  517 +
  518 + .tab.active::after {
  519 + content: '';
  520 + position: absolute;
  521 + left: 50%;
  522 + transform: translateX(-50%);
  523 + bottom: -13px;
  524 + width: 32rpx;
  525 + height: 6rpx;
  526 + border-radius: 4rpx;
  527 + background: $theme-primary;
  528 + }
  529 + }
  530 +
  531 + .sub-tabs {
  532 + display: flex;
  533 + align-items: center;
  534 + height: 96rpx;
  535 +
  536 + &.bg1 {
  537 + background-image: url('/static/images/dev_manage/tab_1_icon.png');
  538 + background-repeat: no-repeat;
  539 + background-position: right center;
  540 + background-size: cover;
  541 + }
  542 +
  543 + &.bg2 {
  544 + background-image: url('/static/images/dev_manage/tab_2_icon.png');
  545 + background-repeat: no-repeat;
  546 + background-position: right center;
  547 + background-size: cover;
  548 + }
  549 +
  550 + &.bg3 {
  551 + background-image: url('/static/images/dev_manage/tab_3_icon.png');
  552 + background-repeat: no-repeat;
  553 + background-position: right center;
  554 + background-size: cover;
  555 + }
  556 +
  557 + .tab {
  558 + width: 50%;
  559 + height: 96rpx;
  560 + line-height: 96rpx;
  561 + text-align: center;
  562 + font-weight: 600;
  563 + color: rgba(0, 0, 0, 0.9);
  564 +
  565 + &.active {
  566 + color: $theme-primary;
  567 + }
  568 + }
  569 + }
  570 + }
  571 +
  572 + /* 仅当前页覆盖 uni-search-bar 盒子高度 */
  573 + ::v-deep .uni-searchbar__box {
  574 + height: 80rpx !important;
  575 + justify-content: start;
  576 +
  577 + .uni-searchbar__box-search-input {
  578 + font-size: 32rpx !important;
  579 + }
  580 + }
  581 +
  582 + .list-box {
  583 + flex: 1;
  584 + padding-top: 314rpx;
  585 +
  586 + &.pad-batch {
  587 + padding-bottom: 144rpx;
  588 + }
  589 +
  590 + .card {
  591 + position: relative;
  592 + }
  593 +
  594 + .card-header {
  595 + margin-bottom: 28rpx;
  596 + position: relative;
  597 +
  598 + .title {
  599 + font-size: 36rpx;
  600 + font-weight: 600;
  601 + line-height: 50rpx;
  602 + color: #323241;
  603 + width: 578rpx;
  604 + }
  605 +
  606 + .status {
  607 + font-size: 30rpx;
  608 + font-weight: 600;
  609 + position: absolute;
  610 + top: -32rpx;
  611 + right: -32rpx;
  612 + height: 48rpx;
  613 + line-height: 48rpx;
  614 + color: #fff;
  615 + font-size: 24rpx;
  616 + padding: 0 14rpx;
  617 + border-radius: 6rpx;
  618 +
  619 + &.status_1 {
  620 + background: #3D48A3;
  621 + }
  622 +
  623 + &.status_2 {
  624 + background: #2BA471;
  625 + }
  626 +
  627 + &.status_3 {
  628 + background: #D54941;
  629 + }
  630 +
  631 + &.status_4 {
  632 + background: #E7E7E7;
  633 + color: #323241;
  634 + }
  635 + }
  636 + }
  637 +
  638 + .info-row {
  639 + display: flex;
  640 + align-items: center;
  641 + color: rgba(0, 0, 0, 0.6);
  642 + font-size: 28rpx;
  643 + margin-bottom: 24rpx;
  644 + height: 32rpx;
  645 +
  646 + &:last-child {
  647 + margin-bottom: 0;
  648 + }
  649 +
  650 + text {
  651 + width: 60%;
  652 +
  653 + &:last-child {
  654 + color: rgba(0, 0, 0, 0.9);
  655 + width: 40%;
  656 + }
  657 + }
  658 + }
  659 + }
  660 +
  661 + .batch-bar {
  662 + position: fixed;
  663 + left: 0;
  664 + right: 0;
  665 + bottom: 0;
  666 + display: flex;
  667 + gap: 36rpx;
  668 + padding: 32rpx 30rpx;
  669 + background: #fff;
  670 + box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
  671 +
  672 + .bbtn {
  673 + flex: 1;
  674 + height: 80rpx;
  675 + line-height: 80rpx;
  676 + border-radius: 12rpx;
  677 + font-size: 32rpx;
  678 + display: flex;
  679 + align-items: center;
  680 + justify-content: center;
  681 + }
  682 +
  683 + .reject {
  684 + background: #fff;
  685 + border: 1rpx solid $uni-color-error;
  686 + color: $uni-color-error;
  687 + }
  688 + }
  689 +
  690 + .filter-form {
  691 + .form-item {
  692 + margin-bottom: 24rpx;
  693 + }
  694 +
  695 + .label {
  696 + margin-bottom: 20rpx;
  697 + color: rgba(0, 0, 0, 0.9);
  698 + height: 44rpx;
  699 + line-height: 44rpx;
  700 + font-size: 30rpx;
  701 + }
  702 +
  703 + .fake-select {
  704 + height: 80rpx;
  705 + line-height: 80rpx;
  706 + padding: 0 20rpx;
  707 + background: #f3f3f3;
  708 + border-radius: 12rpx;
  709 +
  710 + .placeholder {
  711 + color: #999;
  712 + }
  713 +
  714 + .value {
  715 + color: #333;
  716 + }
  717 + }
  718 + }
  719 +
  720 + /* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
  721 + ::v-deep .filter-form .uni-data-checklist .checklist-group {
  722 + .checklist-box {
  723 + &.is--tag {
  724 + width: 212rpx;
  725 + margin-top: 0;
  726 + margin-bottom: 24rpx;
  727 + margin-right: 24rpx;
  728 + height: 80rpx;
  729 + padding: 0;
  730 + border-radius: 12rpx;
  731 + background-color: #f3f3f3;
  732 + border-color: #f3f3f3;
  733 +
  734 + &:nth-child(3n) {
  735 + margin-right: 0;
  736 + }
  737 +
  738 + .checklist-content {
  739 + display: flex;
  740 + justify-content: center;
  741 + }
  742 +
  743 + .checklist-text {
  744 + color: rgba(0, 0, 0, 0.9);
  745 + font-size: 28rpx;
  746 + }
  747 + }
  748 +
  749 + &.is-checked {
  750 + background-color: $theme-primary-plain-bg !important;
  751 + border-color: $theme-primary-plain-bg !important;
  752 +
  753 + .checklist-text {
  754 + color: $theme-primary !important;
  755 + }
  756 + }
  757 + }
  758 +
  759 + }
  760 +
  761 + .action-modal {
  762 + width: 560rpx;
  763 + padding: 32rpx 28rpx 20rpx;
  764 + background: #fff;
  765 + border-radius: 20rpx;
  766 +
  767 + .header {
  768 + text-align: center;
  769 + font-size: 34rpx;
  770 + font-weight: 600;
  771 + margin-bottom: 12rpx;
  772 + color: rgba(0, 0, 0, 0.9);
  773 + }
  774 +
  775 + .body {
  776 + padding: 12rpx 4rpx 24rpx;
  777 +
  778 + .tip {
  779 + display: block;
  780 + text-align: center;
  781 + font-size: 32rpx;
  782 + color: rgba(0, 0, 0, 0.6);
  783 + margin-bottom: 32rpx;
  784 + }
  785 +
  786 + ::v-deep .uni-easyinput {
  787 + width: 100%;
  788 + }
  789 + }
  790 +
  791 + .footer {
  792 + display: flex;
  793 + justify-content: space-between;
  794 + gap: 16rpx;
  795 +
  796 + .btn {
  797 + flex: 1;
  798 + height: 80rpx;
  799 + line-height: 80rpx;
  800 + border-radius: 12rpx;
  801 + font-size: 30rpx;
  802 + font-size: 32rpx;
  803 +
  804 + &::after {
  805 + border: none;
  806 + }
  807 + }
  808 +
  809 + .cancel {
  810 + background: #fff;
  811 + color: rgba(0, 0, 0, 0.9);
  812 + }
748 813
  814 + .confirm {
  815 + background: #fff !important;
  816 + color: $theme-primary !important;
  817 + }
  818 + }
  819 + }
749 </style> 820 </style>
  1 +<template>
  2 + <view class="page">
  3 +
  4 + <scroll-view class="scroll" scroll-y>
  5 + <uni-list>
  6 + <!-- 生产厂 单选 -->
  7 + <uni-list-item class="select-item" :class="form.workshopId ? 'is-filled' : 'is-empty'" clickable
  8 + @click="openSheet('workshopId')" :rightText="displayLabel('workshopIdName')" showArrow>
  9 + <template v-slot:body>
  10 + <view class="item-title"><text class="required">*</text><text>生产厂</text></view>
  11 + </template>
  12 + </uni-list-item>
  13 + <!-- 科办 单选 -->
  14 + <uni-list-item class="mgb10 select-item" :class="form.officeId ? 'is-filled' : 'is-empty'" clickable
  15 + @click="openSheet('officeId')" :rightText="displayLabel('officeIdName')" 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 + <!-- 客户名称 关联页选择 -->
  22 + <uni-list-item class="select-item" :class="form.customerId ? 'is-filled' : 'is-empty'" clickable @click="openRelate('customerId')" :rightText="form.customerIdName || '请选择客户名称'"
  23 + showArrow>
  24 + <template v-slot:body>
  25 + <view class="item-title"><text class="required">*</text><text>客户名称</text></view>
  26 + </template>
  27 + </uni-list-item>
  28 +
  29 + <!-- 客户类型、产品品种 单选 -->
  30 + <uni-list-item class="select-item" :class="form.customerType ? 'is-filled' : 'is-empty'" clickable
  31 + @click="openSheet('customerType')" :rightText="displayLabel('customerTypeName')" showArrow>
  32 + <template v-slot:body>
  33 + <view class="item-title"><text class="required">*</text><text>客户类型</text></view>
  34 + </template>
  35 + </uni-list-item>
  36 + <uni-list-item class="select-item" :class="form.productVarietyId ? 'is-filled' : 'is-empty'" clickable
  37 + @click="openSheet('productVarietyId')" :rightText="displayLabel('productVarietyIdName')" showArrow>
  38 + <template v-slot:body>
  39 + <view class="item-title"><text class="required">*</text><text>产品品种</text></view>
  40 + </template>
  41 + </uni-list-item>
  42 +
  43 + <!-- 其余输入项 -->
  44 + <uni-list-item title="月用量">
  45 + <template v-slot:footer>
  46 + <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量" :inputBorder="false" />
  47 + </template>
  48 + </uni-list-item>
  49 + <uni-list-item title="目标量">
  50 + <template v-slot:footer>
  51 + <uni-easyinput v-model="form.targetQuantity" placeholder="请输入目标量" :inputBorder="false" />
  52 + </template>
  53 + </uni-list-item>
  54 + <uni-list-item title="行业" class="mgb10">
  55 + <template v-slot:footer>
  56 + <uni-easyinput v-model="form.industry" placeholder="请输入行业" :inputBorder="false" />
  57 + </template>
  58 + </uni-list-item>
  59 + <uni-list-item title="牌号">
  60 + <template v-slot:footer>
  61 + <uni-easyinput v-model="form.mark" placeholder="请输入牌号" :inputBorder="false" />
  62 + </template>
  63 + </uni-list-item>
  64 + <uni-list-item title="厚度">
  65 + <template v-slot:footer>
  66 + <uni-easyinput v-model="form.thickness" placeholder="请输入厚度" :inputBorder="false" />
  67 + </template>
  68 + </uni-list-item>
  69 + <uni-list-item title="宽度">
  70 + <template v-slot:footer>
  71 + <uni-easyinput v-model="form.width" placeholder="请输入宽度" :inputBorder="false" />
  72 + </template>
  73 + </uni-list-item>
  74 + <uni-list-item title="材质要求">
  75 + <template v-slot:footer>
  76 + <uni-easyinput v-model="form.materialRequire" placeholder="请输入材质要求" :inputBorder="false" />
  77 + </template>
  78 + </uni-list-item>
  79 + <uni-list-item title="品质要求">
  80 + <template v-slot:footer>
  81 + <uni-easyinput v-model="form.qualityRequire" placeholder="请输入品质要求" :inputBorder="false" />
  82 + </template>
  83 + </uni-list-item>
  84 + <uni-list-item title="同行" class="mgb10">
  85 + <template v-slot:footer>
  86 + <uni-easyinput v-model="form.peer" placeholder="请输入同行" :inputBorder="false" />
  87 + </template>
  88 + </uni-list-item>
  89 + <uni-list-item title="核价模式">
  90 + <template v-slot:footer>
  91 + <uni-easyinput v-model="form.pricingMode" placeholder="请输入核价模式" :inputBorder="false" />
  92 + </template>
  93 + </uni-list-item>
  94 + <uni-list-item title="结算天数" class="mgb10">
  95 + <template v-slot:footer>
  96 + <uni-easyinput v-model="form.settleDays" placeholder="请输入结算天数" :inputBorder="false" />
  97 + </template>
  98 + </uni-list-item>
  99 + <!-- 责任人 关联页选择 -->
  100 + <uni-list-item class="select-item" :class="form.chargeUserIdName ? 'is-filled' : 'is-empty'" title="责任人"
  101 + clickable @click="openRelate('chargeUserId')" :rightText="form.chargeUserIdName || '请选择责任人'" showArrow />
  102 + </uni-list>
  103 +
  104 + </scroll-view>
  105 +
  106 + <view class="footer">
  107 + <button class="btn submit" type="primary" @click="onSubmit">提交</button>
  108 + </view>
  109 +
  110 + <!-- 单选弹框:生产厂、科办、客户类型、产品品种 -->
  111 + <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options"
  112 + v-model="sheet.value" @confirm="onSheetConfirm" />
  113 +
  114 + <!-- 关联选择弹框:客户名称、责任人 -->
  115 + <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
  116 + :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
  117 + :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
  118 + </view>
  119 +</template>
  120 +
  121 +<script>
  122 + import SingleSelectSheet from '@/components/single-select/index.vue'
  123 + import RelateSelectSheet from '@/components/relate-select/index.vue'
  124 + import constant from '@/utils/constant'
  125 + import storage from '@/utils/storage'
  126 + import {
  127 + updateApi,
  128 + getDetailApi,
  129 + officeQueryApi,
  130 + productVarietyQueryApi,
  131 + workshopQueryApi
  132 + } from '@/api/devManage.js'
  133 + import {
  134 + getDicByCodeApi
  135 + } from '@/api/base.js'
  136 +
  137 + export default {
  138 + name: 'DevManageModify',
  139 + components: {
  140 + SingleSelectSheet,
  141 + RelateSelectSheet
  142 + },
  143 + data() {
  144 + return {
  145 + form: {
  146 + id: '',
  147 + workshopIdName: '',
  148 + workshopId: '',
  149 + officeIdName: '',
  150 + officeId: '',
  151 + customerIdName: '',
  152 + customerId: '',
  153 + customerType: '',
  154 + customerTypeName: '',
  155 + chargeUserId: '',
  156 + chargeUserIdName: '',
  157 + productVarietyId: '',
  158 + productVarietyIdName: '',
  159 + monthlyUsage: '',
  160 + targetQuantity: '',
  161 + industry: '',
  162 + mark: '',
  163 + thickness: '',
  164 + width: '',
  165 + materialRequire: '',
  166 + qualityRequire: '',
  167 + peer: '',
  168 + pricingMode: '',
  169 + settleDays: '',
  170 + },
  171 + sheet: {
  172 + visible: false,
  173 + title: '请选择',
  174 + field: '',
  175 + options: [],
  176 + value: ''
  177 + },
  178 + relate: {
  179 + visible: false,
  180 + title: '选择',
  181 + source: '',
  182 + display: [],
  183 + multiple: false,
  184 + rowKey: 'id',
  185 + selectedKeys: []
  186 + },
  187 + officeOptions: [],
  188 + customerTypeOptions: [],
  189 + productVarietyIdOptions: [],
  190 + currentId: ''
  191 + }
  192 + },
  193 + created() {
  194 + // 责任人默认使用当前用户昵称/用户名
  195 + const user = storage.get(constant.name);
  196 + const userId = storage.get(constant.id);
  197 + this.form.chargeUserIdName = user || '';
  198 + this.form.chargeUserId = userId || '';
  199 + // 科办
  200 + this.loadOfficeOptions()
  201 + // 客户类型
  202 + this.loadCustomerTypeOptions()
  203 + // 生产厂
  204 + this.loadproductVarietyIdOptions()
  205 + },
  206 + onLoad(query) {
  207 + const id = (query && (query.id || query.code)) || ''
  208 + if (id) {
  209 + this.currentId = id
  210 + this.loadDetail(id)
  211 + }
  212 + },
  213 + methods: {
  214 + async loadDetail(id) {
  215 + try {
  216 + const res = await getDetailApi(id)
  217 + const data = res.data || {}
  218 + const w = data.workshop || {}
  219 + const o = data.office || {}
  220 + const c = data.customer || {}
  221 + const p = data.productVariety || {}
  222 + this.form.id = data.id || ''
  223 + this.form.workshopId = w.id || ''
  224 + this.form.workshopIdName = w.name || ''
  225 + this.form.officeId = o.id || ''
  226 + this.form.officeIdName = o.name || ''
  227 + this.form.customerId = c.id || ''
  228 + this.form.customerIdName = c.name || ''
  229 + this.form.productVarietyId = p.id || ''
  230 + this.form.productVarietyIdName = p.name || ''
  231 + this.form.customerType = data.customerType || ''
  232 + this.form.monthlyUsage = data.monthlyUsage || ''
  233 + this.form.targetQuantity = data.targetQuantity || ''
  234 + this.form.industry = data.industry || ''
  235 + this.form.mark = data.mark || ''
  236 + this.form.thickness = data.thickness || ''
  237 + this.form.width = data.width || ''
  238 + this.form.materialRequire = data.materialRequire || ''
  239 + this.form.qualityRequire = data.qualityRequire || ''
  240 + this.form.peer = data.peer || ''
  241 + this.form.pricingMode = data.pricingMode || ''
  242 + this.form.settleDays = data.settleDays || ''
  243 + this.form.chargeUserId = data.chargeUserId || ''
  244 + this.form.chargeUserIdName = data.chargeUserName || ''
  245 + this.updateCustomerTypeName()
  246 + } catch (e) {
  247 + }
  248 + },
  249 + // 校验必填项:生产厂、科办、客户名称、客户类型、产品品种
  250 + validateRequired() {
  251 + const checks = [{
  252 + key: 'workshopId',
  253 + label: '生产厂'
  254 + },
  255 + {
  256 + key: 'officeId',
  257 + label: '科办'
  258 + },
  259 + {
  260 + key: 'customerId',
  261 + label: '客户名称'
  262 + },
  263 + {
  264 + key: 'customerType',
  265 + label: '客户类型'
  266 + },
  267 + {
  268 + key: 'productVarietyId',
  269 + label: '产品品种'
  270 + }
  271 + ]
  272 + for (const it of checks) {
  273 + const val = this.form[it.key]
  274 + if (val === undefined || val === null || String(val).trim() === '') {
  275 + uni.showToast({
  276 + title: `请先选择${it.label}`,
  277 + icon: 'none'
  278 + })
  279 + return false
  280 + }
  281 + }
  282 + return true
  283 + },
  284 + async loadOfficeOptions() {
  285 + try {
  286 + const res = await officeQueryApi({
  287 + pageIndex: 1,
  288 + pageSize: 9999
  289 + })
  290 + const _data = res.data || {}
  291 + const list = _data.datas || []
  292 + this.officeOptions = (list || []).map(it => ({
  293 + label: it.name || it.text || it.officeName || it.label,
  294 + value: it.id || it.code || it.value
  295 + }))
  296 + } catch (e) {
  297 + this.officeOptions = []
  298 + }
  299 + },
  300 + displayLabel(field) {
  301 + const m = this.form
  302 + const map = {
  303 + workshopIdName: '请选择生产厂',
  304 + officeIdName: '请选择科办',
  305 + customerTypeName: '请选择客户类型',
  306 + productVarietyIdName: '请选择产品品种'
  307 + }
  308 + const val = m[field]
  309 + return val ? String(val) : map[field]
  310 + },
  311 + async openSheet(field) {
  312 + const setSheet = (title, options) => {
  313 + const current = this.form[field]
  314 + const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
  315 + this.sheet = {
  316 + ...this.sheet,
  317 + visible: true,
  318 + title,
  319 + options,
  320 + field,
  321 + value: match ? match.value : ''
  322 + }
  323 + }
  324 + if (field === 'workshopId') {
  325 + const res = await workshopQueryApi({ pageIndex: 1, pageSize: 9999 })
  326 + const _data = res.data || {}
  327 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  328 + const opts = (list || []).map(it => ({
  329 + label: it.name ,
  330 + value: it.id
  331 + }))
  332 + setSheet('生产厂', opts)
  333 + } else if (field === 'officeId') {
  334 + setSheet('科办', this.officeOptions)
  335 + } else if (field === 'customerType') {
  336 + const opts = this.customerTypeOptions || [];
  337 + setSheet('客户类型', opts)
  338 + } else if (field === 'productVarietyId') {
  339 + const opts = this.productVarietyIdOptions;
  340 + setSheet('产品品种', opts)
  341 + }
  342 + },
  343 + onSheetConfirm({
  344 + value,
  345 + label
  346 + }) {
  347 + const field = this.sheet.field
  348 + if (!field) return
  349 + this.form[field] = value || '';
  350 + this.form[field + 'Name'] = label || ''
  351 + this.sheet.visible = false
  352 + },
  353 + openRelate(fieldKey) {
  354 + let config = {};
  355 + switch(fieldKey) {
  356 + case 'customerId':
  357 + config = {
  358 + title: '客户名称',
  359 + source: 'customer',
  360 + rowKey: 'id',
  361 + multiple: false,
  362 + display: [{
  363 + label: '姓名',
  364 + field: 'name'
  365 + },
  366 + {
  367 + label: '编号',
  368 + field: 'code'
  369 + },
  370 + {
  371 + label: '状态',
  372 + field: 'available',
  373 + format: v => (v ? '启用' : '停用')
  374 + }
  375 + ]
  376 + }
  377 + break
  378 + case 'chargeUserId':
  379 + config = {
  380 + title: '责任人',
  381 + source: 'user',
  382 + rowKey: 'id',
  383 + multiple: false,
  384 + display: [{
  385 + label: '姓名',
  386 + field: 'name'
  387 + },
  388 + {
  389 + label: '编号',
  390 + field: 'code'
  391 + },
  392 + {
  393 + label: '状态',
  394 + field: 'available',
  395 + format: v => (v ? '启用' : '停用')
  396 + }
  397 + ]};
  398 + break
  399 + }
  400 + const selectedKeys = this.form[fieldKey] ? [this.form[fieldKey]] : [];
  401 + // 逐一赋值,避免整体替换对象导致子组件 prop 更新不及时
  402 + this.sheet.visible = false
  403 + this.relate.title = config.title
  404 + this.relate.source = config.source
  405 + this.relate.display = config.display
  406 + this.relate.multiple = config.multiple
  407 + this.relate.rowKey = config.rowKey
  408 + this.relate.selectedKeys = selectedKeys
  409 + this.relate.fieldKey = fieldKey;
  410 + // 最后再打开弹框,确保选中键已传入
  411 + this.$nextTick(() => {
  412 + this.relate.visible = true
  413 + })
  414 + },
  415 + onRelateConfirm({
  416 + items
  417 + }) {
  418 + console.log('onRelateConfirm__item', items)
  419 + const _fieldKey = this.relate.fieldKey;
  420 + console.log('onRelateConfirm__fieldKey', _fieldKey)
  421 + const first = (items && items.length > 0) ? items[0] : null
  422 + // 若无选中项,确认时清空之前的值
  423 + this.form[_fieldKey] = first.id || '';
  424 + this.form[_fieldKey + 'Name'] = first.name || '';
  425 + },
  426 + async loadCustomerTypeOptions() {
  427 + try {
  428 + const res = await getDicByCodeApi('ENTERPRISE_TYPE');
  429 + const list = res.data || [];
  430 + this.customerTypeOptions = (list || []).map(it => ({
  431 + label: it.name || '',
  432 + value: it.code || ''
  433 + }))
  434 + this.updateCustomerTypeName()
  435 + } catch (e) {
  436 + this.customerTypeOptions = []
  437 + }
  438 + },
  439 + updateCustomerTypeName() {
  440 + const code = this.form.customerType
  441 + const match = (this.customerTypeOptions || []).find(it => String(it.value) === String(code))
  442 + this.form.customerTypeName = match ? match.label : ''
  443 + },
  444 +
  445 + async loadproductVarietyIdOptions() {
  446 + try {
  447 + const res = await productVarietyQueryApi({ pageIndex: 1, pageSize: 9999 })
  448 + const _data = res.data || {}
  449 + const list = _data.datas || (Array.isArray(_data) ? _data : [])
  450 + this.productVarietyIdOptions = (list || []).map(it => ({
  451 + label: it.name || it.text || it.label,
  452 + value: it.id || it.code || it.value
  453 + }))
  454 + } catch (e) {
  455 + this.productVarietyIdOptions = []
  456 + }
  457 + },
  458 + async onSubmit() {
  459 + // 必填校验
  460 + if (!this.validateRequired()) return
  461 + const payload = {
  462 + ...this.form
  463 + }
  464 + delete payload.workshopIdName
  465 + delete payload.officeIdName
  466 + delete payload.customerTypeName
  467 + delete payload.customerIdName
  468 + delete payload.productVarietyIdName
  469 + delete payload.chargeUserIdName
  470 + payload.id = payload.id || this.currentId || ''
  471 + updateApi(payload).then(res => {
  472 + uni.showToast({
  473 + title: '保存成功',
  474 + icon: 'success'
  475 + })
  476 + const id = payload.id || this.currentId || ''
  477 + uni.$emit('dev_manage_detail_reload', { id })
  478 + setTimeout(() => uni.navigateBack(), 300)
  479 + }).catch(e => {
  480 + uni.showToast({
  481 + title: e.msg || '提交失败',
  482 + icon: 'none'
  483 + })
  484 + })
  485 + }
  486 + }
  487 + }
  488 +</script>
  489 +
  490 +<style lang="scss" scoped>
  491 + .page {
  492 + display: flex;
  493 + flex-direction: column;
  494 + height: 100%;
  495 + }
  496 +
  497 + .scroll {
  498 + flex: 1;
  499 + padding: 12rpx 0 160rpx;
  500 + }
  501 +
  502 + .footer {
  503 + position: fixed;
  504 + left: 0;
  505 + right: 0;
  506 + bottom: 0;
  507 + padding: 32rpx;
  508 + padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  509 + background: #fff;
  510 + box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  511 +
  512 + .btn {
  513 + height: 80rpx;
  514 + line-height: 80rpx;
  515 + border-radius: 12rpx;
  516 + font-size: 32rpx;
  517 + }
  518 +
  519 + .submit {
  520 + background: $theme-primary;
  521 + color: #fff;
  522 + }
  523 + }
  524 +
  525 + ::v-deep .uni-list {
  526 + background: transparent;
  527 + &-item {
  528 + &__extra-text {
  529 + font-size: 32rpx;
  530 + }
  531 + &__content-title {
  532 + font-size: 32rpx;
  533 + color: rgba(0, 0, 0, 0.9);
  534 + }
  535 +
  536 + &__container {
  537 + padding: 32rpx;
  538 +
  539 + .uni-easyinput {
  540 + &__placeholder-class {
  541 + font-size: 32rpx;
  542 + color: rgba(0, 0, 0, 0.4);
  543 + }
  544 +
  545 + &__content {
  546 + border: none;
  547 +
  548 + &-input {
  549 + padding-left: 0 !important;
  550 + height: 48rpx;
  551 + line-height: 48rpx;
  552 + font-size: 32rpx;
  553 + }
  554 +
  555 + .content-clear-icon {
  556 + font-size: 44rpx !important;
  557 + }
  558 + }
  559 + }
  560 +
  561 + .item-title,
  562 + .uni-list-item__content {
  563 + flex: none;
  564 + min-height: 48rpx;
  565 + line-height: 48rpx;
  566 + font-size: 32rpx;
  567 + position: relative;
  568 + width: 162rpx;
  569 + margin-right: 32rpx;
  570 + color: rgba(0, 0, 0, 0.9);
  571 +
  572 + .required {
  573 + color: red;
  574 + position: absolute;
  575 + top: 50%;
  576 + transform: translateY(-50%);
  577 + left: -16rpx;
  578 + }
  579 + }
  580 +
  581 + }
  582 + &.select-item {
  583 + &.is-empty {
  584 + .uni-list-item__extra-text {
  585 + color: rgba(0, 0, 0, 0.4) !important;
  586 + }
  587 + }
  588 + &.is-filled {
  589 + .uni-list-item__extra-text {
  590 + color: rgba(0, 0, 0, 0.9) !important;
  591 + }
  592 + }
  593 + }
  594 + &.mgb10 {
  595 + margin-bottom: 20rpx;
  596 + }
  597 + }
  598 + }
  599 +
  600 +</style>
@@ -54,7 +54,7 @@ config.responseType = 'blob'; @@ -54,7 +54,7 @@ config.responseType = 'blob';
54 return 54 return
55 } 55 }
56 const code = res.data.code || 200 56 const code = res.data.code || 200
57 - const msg = errorCode[code] || res.data.msg || errorCode['default'] 57 + const msg = res.data.msg || '';
58 if (code === 401) { 58 if (code === 401) {
59 showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => { 59 showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
60 if (res.confirm) { 60 if (res.confirm) {
@@ -66,7 +66,7 @@ config.responseType = 'blob'; @@ -66,7 +66,7 @@ config.responseType = 'blob';
66 reject('无效的会话,或者会话已过期,请重新登录。') 66 reject('无效的会话,或者会话已过期,请重新登录。')
67 } else if (code === 500) { 67 } else if (code === 500) {
68 toast(msg) 68 toast(msg)
69 - reject('500') 69 + reject(res.data)
70 } else if (code !== 200) { 70 } else if (code !== 200) {
71 toast(msg) 71 toast(msg)
72 reject(code) 72 reject(code)