Commit 8726c1f4ee12394ecf1a000f1f195bd99a23c2e7

Authored by 史婷婷
1 parent 71164877

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

1 1 import request from '@/utils/request'
2 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 5 export const statusOptions = [
35 6 { value: 1, text: '审核中' },
... ... @@ -130,4 +101,13 @@ export function productVarietyQueryApi(params) {
130 101 method: 'get',
131 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 }
\ No newline at end of file
... ...
1 1 import upload from '@/utils/upload'
2 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 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 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 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 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 +}
\ No newline at end of file
... ...
... ... @@ -22,10 +22,11 @@
22 22 <view
23 23 v-for="(item, idx) in items"
24 24 :key="getKey(item, idx)"
25   - :class="['card-item', { 'select-item': selectable }]"
  25 + :class="['card-item', { 'select-item': selectable && showCheck }]"
26 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 30 <view class="dot" :class="{ checked: isSelected(item, idx) }">
30 31 <uni-icons v-if="isSelected(item, idx)" type="checkmarkempty" color="#fff" size="14" />
31 32 </view>
... ... @@ -58,6 +59,8 @@ export default {
58 59 enableLoadMore: { type: Boolean, default: true },
59 60 // 支持多选
60 61 selectable: { type: Boolean, default: false },
  62 + // 是否显示左侧选择圆点
  63 + showCheck: { type: Boolean, default: true },
61 64 rowKey: { type: String, default: 'id' },
62 65 // v-model:selectedKeys
63 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>
\ No newline at end of file
... ...
  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>
\ No newline at end of file
... ...
... ... @@ -92,7 +92,7 @@ export default {
92 92 <style lang="scss" scoped>
93 93 .sheet {
94 94 width: 100%;
95   - height: 45vh;
  95 + max-height: 45vh;
96 96 background: #fff;
97 97 border-radius: 20rpx 20rpx 0 0;
98 98 display: flex;
... ...
... ... @@ -54,6 +54,27 @@
54 54 "navigationBarBackgroundColor": "#ffffff",
55 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 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>
\ No newline at end of file
... ...
  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>
\ No newline at end of file
... ...
1 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 113 </template>
163 114
164 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 458 </script>
452 459
453 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 820 </style>
\ No newline at end of file
... ...
  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>
\ No newline at end of file
... ...
... ... @@ -54,7 +54,7 @@ config.responseType = 'blob';
54 54 return
55 55 }
56 56 const code = res.data.code || 200
57   - const msg = errorCode[code] || res.data.msg || errorCode['default']
  57 + const msg = res.data.msg || '';
58 58 if (code === 401) {
59 59 showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
60 60 if (res.confirm) {
... ... @@ -66,7 +66,7 @@ config.responseType = 'blob';
66 66 reject('无效的会话,或者会话已过期,请重新登录。')
67 67 } else if (code === 500) {
68 68 toast(msg)
69   - reject('500')
  69 + reject(res.data)
70 70 } else if (code !== 200) {
71 71 toast(msg)
72 72 reject(code)
... ...