Commit 40195402fc9fb10d5165c649e42b25871e0e61d5

Authored by gesilong
2 parents ed46d051 64085952

Merge branch 'cjerp-1.0_20260116' of http://gitlab.qgutech.com/zhuyuanliang/erp-…

…mobile into cjerp-1.0_20260116
1 1 import request from '@/utils/request'
2   -import { ContentTypeEnum } from '@/utils/httpEnum';
3 2 import config from '@/config'
4 3 import { getToken } from '@/utils/auth'
5 4
... ... @@ -32,6 +31,20 @@ export function uploadFileApi(data) {
32 31 }
33 32
34 33 /**
  34 + * 下载文件通用接口
  35 + * @param id
  36 + */
  37 +export function downloadFileApi(id) {
  38 + return request({
  39 + url: `/download/security/url`,
  40 + method: 'get',
  41 + params: {
  42 + id
  43 + },
  44 + })
  45 +}
  46 +
  47 +/**
35 48 * 回去城市接口 省市区 一次性返回
36 49 */
37 50 export function selectorCityApi() {
... ... @@ -63,4 +76,4 @@ export function generateCodeApi(type) {
63 76 type
64 77 },
65 78 })
66   -}
\ No newline at end of file
  79 +}
... ...
  1 +<template>
  2 + <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
  3 + <view class="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">确认</text>
  8 + </view>
  9 + <view class="sheet-body">
  10 + <view
  11 + v-for="(opt, i) in options"
  12 + :key="i"
  13 + :class="['option', { selected: isSelected(opt) }]"
  14 + @click="toggle(opt)"
  15 + >
  16 + <text class="label">{{ displayOf(opt) }}</text>
  17 + </view>
  18 + </view>
  19 + </view>
  20 + </uni-popup>
  21 +</template>
  22 +
  23 +<script>
  24 +export default {
  25 + name: 'MultiSelectSheet',
  26 + props: {
  27 + visible: { type: Boolean, default: false },
  28 + title: { type: String, default: '请选择' },
  29 + // 统一约定:options 为 [{ label: string, value: string|number }]
  30 + options: { type: Array, default: () => [] },
  31 + // 接收逗号分隔的字符串 '111,222,333'
  32 + value: { type: String, default: '' }
  33 + },
  34 + data() {
  35 + return {
  36 + innerValue: [] // 内部维护数组形式
  37 + }
  38 + },
  39 + watch: {
  40 + value(v) { this.initInnerValue(v) },
  41 + visible(v) { v ? this.open() : this.close() }
  42 + },
  43 + mounted() {
  44 + if (this.visible) this.open()
  45 + },
  46 + methods: {
  47 + initInnerValue(val) {
  48 + if (!val) {
  49 + this.innerValue = []
  50 + } else {
  51 + this.innerValue = String(val).split(',').filter(x => x)
  52 + }
  53 + },
  54 + open() {
  55 + this.initInnerValue(this.value)
  56 + this.$refs.popup && this.$refs.popup.open()
  57 + this.$emit('update:visible', true)
  58 + },
  59 + close() {
  60 + this.$refs.popup && this.$refs.popup.close()
  61 + this.$emit('update:visible', false)
  62 + },
  63 + displayOf(opt) {
  64 + if (!opt) return ''
  65 + return opt.label != null ? String(opt.label) : ''
  66 + },
  67 + valueOf(opt) {
  68 + if (!opt) return ''
  69 + return opt.value != null ? String(opt.value) : ''
  70 + },
  71 + isSelected(opt) {
  72 + const v = this.valueOf(opt)
  73 + return this.innerValue.includes(v)
  74 + },
  75 + toggle(opt) {
  76 + const v = this.valueOf(opt)
  77 + const idx = this.innerValue.indexOf(v)
  78 + if (idx > -1) {
  79 + this.innerValue.splice(idx, 1)
  80 + } else {
  81 + this.innerValue.push(v)
  82 + }
  83 + },
  84 + onCancel() {
  85 + this.close()
  86 + this.$emit('cancel')
  87 + },
  88 + onConfirm() {
  89 + const selectedValues = this.innerValue
  90 + // 保持原始选项顺序
  91 + const sortedValues = []
  92 + const sortedLabels = []
  93 +
  94 + this.options.forEach(opt => {
  95 + const v = this.valueOf(opt)
  96 + if (selectedValues.includes(v)) {
  97 + sortedValues.push(v)
  98 + sortedLabels.push(this.displayOf(opt))
  99 + }
  100 + })
  101 +
  102 + const valStr = sortedValues.join(',')
  103 + const labelStr = sortedLabels.join(',')
  104 +
  105 + this.$emit('confirm', { value: valStr, label: labelStr })
  106 + this.$emit('input', valStr)
  107 + this.$emit('update:value', valStr)
  108 + this.close()
  109 + }
  110 + }
  111 +}
  112 +</script>
  113 +
  114 +<style lang="scss" scoped>
  115 +.sheet {
  116 + width: 100%;
  117 + max-height: 45vh;
  118 + background: #fff;
  119 + border-radius: 20rpx 20rpx 0 0;
  120 + display: flex;
  121 + flex-direction: column;
  122 +}
  123 +.sheet-header {
  124 + display: flex;
  125 + align-items: center;
  126 + justify-content: space-between;
  127 + padding: 30rpx 32rpx;
  128 + border-bottom: 1rpx solid #f0f0f0;
  129 +}
  130 +.title {
  131 + font-size: 36rpx;
  132 + font-weight: 600;
  133 +}
  134 +.cancel {
  135 + color: rgba(0,0,0,0.6);
  136 + font-size: 28rpx;
  137 +}
  138 +.ok {
  139 + color: $theme-primary;
  140 + font-size: 28rpx;
  141 +}
  142 +.sheet-body {
  143 + flex: 1 1 auto;
  144 + overflow-y: auto;
  145 + padding: 32rpx;
  146 +}
  147 +.option {
  148 + padding: 20rpx;
  149 + line-height: 40rpx;
  150 + background: #fff;
  151 + text-align: center;
  152 + border-radius: 12rpx;
  153 + font-size: 32rpx;
  154 + margin-bottom: 20rpx;
  155 + .label {
  156 + color: rgba(0,0,0,0.6);
  157 + font-size: 32rpx;
  158 + }
  159 + &.selected {
  160 + background: #f3f3f3;
  161 + .label {
  162 + color: rgba(0,0,0,0.9);
  163 + }
  164 + }
  165 +}
  166 +</style>
\ No newline at end of file
... ...
... ... @@ -300,6 +300,8 @@ export default {
300 300 font-size: 28rpx;
301 301 color: rgba(0, 0, 0, 0.9);
302 302 text-align: right;
  303 + white-space: pre-wrap;
  304 + word-break: break-all;
303 305
304 306 &.act {
305 307 color: $theme-primary;
... ...
... ... @@ -49,6 +49,8 @@
49 49
50 50 <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value"
51 51 @confirm="onSheetConfirm" />
  52 + <MultiSelectSheet :visible.sync="multiSheet.visible" :title="multiSheet.title" :options="multiSheet.options" v-model="multiSheet.value"
  53 + @confirm="onMultiSheetConfirm" />
52 54
53 55 <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
54 56 :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
... ... @@ -57,12 +59,13 @@
57 59 </template>
58 60 <script>
59 61 import SingleSelectSheet from '@/components/single-select/index.vue'
  62 +import MultiSelectSheet from '@/components/multi-select/index.vue'
60 63 import RelateSelectSheet from '@/components/relate-select/index.vue'
61 64 import { productVarietyQuery, getCodeApi, createApi } from '@/api/contract'
62 65 import { getDicByCodes } from '@/utils/dic'
63 66
64 67 export default {
65   - components: { SingleSelectSheet, RelateSelectSheet },
  68 + components: { SingleSelectSheet, RelateSelectSheet, MultiSelectSheet },
66 69 data() {
67 70 return {
68 71 form: {
... ... @@ -80,6 +83,7 @@ export default {
80 83 productVarietyList: [],
81 84 companyList: [],
82 85 sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  86 + multiSheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
83 87 relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' }
84 88 }
85 89 },
... ... @@ -132,14 +136,25 @@ export default {
132 136 const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
133 137 this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
134 138 }
  139 + const setMultiSheet = (title, options) => {
  140 + const current = this.form[field]
  141 + this.multiSheet = { ...this.multiSheet, visible: true, title, options, field, value: current || '' }
  142 + }
  143 +
135 144 if (field === 'company') {
136 145 setSheet('所属单位', this.companyList)
137 146 } else if (field === 'materialTypeId') {
138   - setSheet('品种', this.productVarietyList)
  147 + setMultiSheet('品种', this.productVarietyList)
139 148 } else if (field === 'hasFrameworkAgreement') {
140 149 setSheet('是否签订框架合同', [{ label: '是', value: true }, { label: '否', value: false }])
141 150 }
142 151 },
  152 + onMultiSheetConfirm({ value, label }) {
  153 + const field = this.multiSheet.field
  154 + if (!field) return
  155 + this.form[field] = value || ''
  156 + this.form[field + 'Name'] = label || ''
  157 + },
143 158 onSheetConfirm({ value, label }) {
144 159 const field = this.sheet.field
145 160 if (!field) return
... ...
... ... @@ -49,6 +49,8 @@
49 49
50 50 <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value"
51 51 @confirm="onSheetConfirm" />
  52 + <MultiSelectSheet :visible.sync="multiSheet.visible" :title="multiSheet.title" :options="multiSheet.options" v-model="multiSheet.value"
  53 + @confirm="onMultiSheetConfirm" />
52 54 <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
53 55 :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
54 56 :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
... ... @@ -57,12 +59,13 @@
57 59
58 60 <script>
59 61 import SingleSelectSheet from '@/components/single-select/index.vue'
  62 +import MultiSelectSheet from '@/components/multi-select/index.vue'
60 63 import RelateSelectSheet from '@/components/relate-select/index.vue'
61 64 import { productVarietyQuery, getCodeApi, getDetailApi, updateApi } from '@/api/contract'
62 65 import { getDicByCodes } from '@/utils/dic'
63 66
64 67 export default {
65   - components: { SingleSelectSheet, RelateSelectSheet },
  68 + components: { SingleSelectSheet, RelateSelectSheet, MultiSelectSheet },
66 69 data() {
67 70 return {
68 71 id: '',
... ... @@ -70,6 +73,7 @@ export default {
70 73 companyList: [],
71 74 productVarietyList: [],
72 75 sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  76 + multiSheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
73 77 relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' }
74 78 }
75 79 },
... ... @@ -122,10 +126,21 @@ export default {
122 126 const match = (options || []).find(o => String(o.value) === String(current))
123 127 this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
124 128 }
  129 + const setMultiSheet = (title, options) => {
  130 + const current = this.form[field]
  131 + this.multiSheet = { ...this.multiSheet, visible: true, title, options, field, value: current || '' }
  132 + }
  133 +
125 134 if (field === 'company') setSheet('所属单位', this.companyList)
126   - else if (field === 'materialTypeId') setSheet('品种', this.productVarietyList)
  135 + else if (field === 'materialTypeId') setMultiSheet('品种', this.productVarietyList)
127 136 else if (field === 'hasFrameworkAgreement') setSheet('是否签订框架合同', [{ label: '是', value: true }, { label: '否', value: false }])
128 137 },
  138 + onMultiSheetConfirm({ value, label }) {
  139 + const field = this.multiSheet.field
  140 + if (!field) return
  141 + this.form[field] = value || ''
  142 + this.form[field + 'Name'] = label || ''
  143 + },
129 144 onSheetConfirm({ value, label }) {
130 145 const field = this.sheet.field
131 146 if (!field) return
... ...
... ... @@ -32,9 +32,9 @@
32 32 <view class="row"><text class="label">经营年限</text><text class="value">{{ form.businessYears }}</text></view>
33 33 <view class="row"><text class="label">单位地址</text><text class="value">{{ form.companyAddress }}</text></view>
34 34 <view class="row"><text class="label">经营范围</text><text class="value">{{ form.businessScope }}</text></view>
35   - <view class="row"><text class="label">工商信息</text><text class="value act">{{ form.businessFileName }}</text>
  35 + <view class="row"><text class="label">工商信息</text><text class="value act" @click="downloadFile(form.businessFileId, form.businessFileName)">{{ form.businessFileName }}</text>
36 36 </view>
37   - <view class="row"><text class="label">股东信息</text><text class="value act">{{ form.shareholderFileName }}</text>
  37 + <view class="row"><text class="label">股东信息</text><text class="value act" @click="downloadFile(form.shareholderFileId, form.shareholderFileName)">{{ form.shareholderFileName }}</text>
38 38 </view>
39 39 </view>
40 40
... ... @@ -174,6 +174,7 @@
174 174 <script>
175 175 import { getDetailApi, getByIdCreditHistoryList, cancelApi } from '@/api/credit_manage.js'
176 176 import { getDicName } from '@/utils/dic.js'
  177 +import { downloadFile } from '@/utils/downloadFile.js'
177 178 import { getDicByCodeApi } from '@/api/base.js'
178 179 import CorePersonnel from './corePersonnel.vue'
179 180 import DetailButtons from '@/components/detail-buttons/index.vue'
... ... @@ -350,6 +351,7 @@ export default {
350 351 })
351 352 },
352 353 getDicName,
  354 + downloadFile,
353 355 getCategoryClass(categoryName) {
354 356 if (!categoryName) return ''
355 357 if (categoryName.includes('A') || categoryName.includes('a')) {
... ...
... ... @@ -30,9 +30,9 @@
30 30 <view class="row"><text class="label">经营年限</text><text class="value">{{ form.businessYears }}</text></view>
31 31 <view class="row"><text class="label">单位地址</text><text class="value">{{ form.companyAddress }}</text></view>
32 32 <view class="row"><text class="label">经营范围</text><text class="value">{{ form.businessScope }}</text></view>
33   - <view class="row"><text class="label">工商信息</text><text class="value act">{{ form.businessFileName }}</text>
  33 + <view class="row"><text class="label">工商信息</text><text class="value act" @click="downloadFile(form.businessFileId, form.businessFileName)">{{ form.businessFileName }}</text>
34 34 </view>
35   - <view class="row"><text class="label">股东信息</text><text class="value act">{{ form.shareholderFileName
  35 + <view class="row"><text class="label">股东信息</text><text class="value act" @click="downloadFile(form.shareholderFileId, form.shareholderFileName)">{{ form.shareholderFileName
36 36 }}</text>
37 37 </view>
38 38 </view>
... ... @@ -180,6 +180,7 @@ import { getExamineById, getByIdExamineCustomerCreditHistoryList } from '@/api/c
180 180 import { getDicName } from '@/utils/dic.js'
181 181 import { getDicByCodeApi } from '@/api/base.js'
182 182 import CorePersonnel from './corePersonnel.vue'
  183 +import { downloadFile } from '@/utils/downloadFile.js'
183 184
184 185 export default {
185 186 name: 'CustomerCreditApprove',
... ... @@ -274,6 +275,7 @@ export default {
274 275 uni.navigateTo({ url: '/pages/credit_manage/history_detail' + query })
275 276 },
276 277 getDicName,
  278 + downloadFile,
277 279 getCategoryClass(categoryName) {
278 280 if (!categoryName) return ''
279 281 if (categoryName.includes('A') || categoryName.includes('a')) {
... ...
... ... @@ -41,9 +41,9 @@
41 41 </uni-list-item>
42 42
43 43 <!-- 其余输入项 -->
44   - <uni-list-item title="月用量">
  44 + <uni-list-item title="月用量(吨)">
45 45 <template v-slot:footer>
46   - <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量" :inputBorder="false" />
  46 + <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量(吨)" :inputBorder="false" />
47 47 </template>
48 48 </uni-list-item>
49 49 <uni-list-item title="目标量">
... ...
... ... @@ -16,7 +16,7 @@
16 16 </view>
17 17 <view class="row"><text class="label">产品品种</text><text class="value">{{ form.productVariety.name
18 18 }}</text></view>
19   - <view class="row"><text class="label">月用量</text><text class="value">{{ form.monthlyUsage }}</text>
  19 + <view class="row"><text class="label">月用量(吨)</text><text class="value">{{ form.monthlyUsage }}</text>
20 20 </view>
21 21 <view class="row"><text class="label">目标量</text><text class="value">{{ form.targetQuantity }}</text>
22 22 </view>
... ...
... ... @@ -41,9 +41,9 @@
41 41 </uni-list-item>
42 42
43 43 <!-- 其余输入项 -->
44   - <uni-list-item title="月用量">
  44 + <uni-list-item title="月用量(吨)">
45 45 <template v-slot:footer>
46   - <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量" :inputBorder="false" />
  46 + <uni-easyinput v-model="form.monthlyUsage" placeholder="请输入月用量(吨)" :inputBorder="false" />
47 47 </template>
48 48 </uni-list-item>
49 49 <uni-list-item title="目标量">
... ...
... ... @@ -13,7 +13,7 @@
13 13 </view>
14 14 <view class="row"><text class="label">产品品种</text><text class="value">{{ form.productVariety.name }}</text>
15 15 </view>
16   - <view class="row"><text class="label">月用量</text><text class="value">{{ form.monthlyUsage }}</text>
  16 + <view class="row"><text class="label">月用量(吨)</text><text class="value">{{ form.monthlyUsage }}</text>
17 17 </view>
18 18 <view class="row"><text class="label">目标量</text><text class="value">{{ form.targetQuantity }}</text>
19 19 </view>
... ...
... ... @@ -19,7 +19,7 @@
19 19 @split="splitFun" />
20 20 </view>
21 21 <view class="section">
22   - <view class="row"><text class="label">签收单据</text><text class="value act">{{ form.fileName }}</text></view>
  22 + <view class="row"><text class="label">签收单据</text><text class="value act" @click="downloadFile(form.fileId, form.fileName)">{{ form.fileName }}</text></view>
23 23 </view>
24 24 </view>
25 25 </scroll-view>
... ... @@ -141,6 +141,7 @@ import Product from './product.vue'
141 141 import DetailButtons from '@/components/detail-buttons/index.vue'
142 142 import FileUpload from '@/components/file-upload/index.vue'
143 143 import { getShipmentPlanDetailApi, createApi } from '@/api/delay_invoice.js'
  144 +import { downloadFile } from '@/utils/downloadFile.js'
144 145
145 146 export default {
146 147 name: 'InvoiceDetail',
... ... @@ -220,6 +221,7 @@ export default {
220 221 onUpload() {
221 222 this.$refs.uploadPopup && this.$refs.uploadPopup.open()
222 223 },
  224 + downloadFile,
223 225 closeUploadInfo() {
224 226 this.uploadFile = { id: '', name: '' };
225 227 this.$refs.uploadPopup && this.$refs.uploadPopup.close()
... ...
... ... @@ -95,9 +95,9 @@ export default {
95 95 }
96 96 },
97 97 created() {
98   - const [start, end] = this.getDefaultDateRange()
99   - this.filterForm.dateRange = [start, end]
100   - this.query = { readed: this.filterForm.readed, dateRange: [start, end] }
  98 + // 初始化时清空时间筛选,不设置默认值
  99 + this.filterForm.dateRange = []
  100 + this.query = { readed: this.filterForm.readed, dateRange: [] }
101 101 },
102 102 onReachBottom() {
103 103 if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
... ... @@ -111,19 +111,6 @@ export default {
111 111 }
112 112 },
113 113 methods: {
114   - // 默认日期范围:当前日期前一个月到今日
115   - getDefaultDateRange() {
116   - const fmt = d => {
117   - const y = d.getFullYear()
118   - const m = String(d.getMonth() + 1).padStart(2, '0')
119   - const dd = String(d.getDate()).padStart(2, '0')
120   - return `${y}-${m}-${dd}`
121   - }
122   - const end = new Date()
123   - const start = new Date(end)
124   - start.setMonth(start.getMonth() - 1)
125   - return [fmt(start), fmt(end)]
126   - },
127 114 onCardLoaded({ items }) {
128 115 this.currentItems = items
129 116 },
... ...
... ... @@ -22,7 +22,7 @@
22 22 </view>
23 23
24 24 <view class="section">
25   - <view class="row"><text class="label">撤销确认凭证</text><text class="value act">{{ form.confirmationVoucherFileName }}</text></view>
  25 + <view class="row"><text class="label">撤销确认凭证</text><text class="value act" @click="downloadFile(form.confirmationVoucherFileId, form.confirmationVoucherFileName)">{{ form.confirmationVoucherFileName }}</text></view>
26 26 </view>
27 27 </view>
28 28 </scroll-view>
... ... @@ -34,6 +34,7 @@
34 34 import { getDetailApi, cancelApi } from '@/api/revoke_list.js'
35 35 import Product from './product.vue'
36 36 import DetailButtons from '@/components/detail-buttons/index.vue'
  37 +import { downloadFile } from '@/utils/downloadFile.js'
37 38
38 39 export default {
39 40 name: 'RevokeListDetail',
... ... @@ -111,6 +112,7 @@ export default {
111 112 uni.setStorageSync(CACHE_KEY, this.getBusinessId())
112 113 uni.navigateTo({ url: '/pages/flow/audit_detail' })
113 114 },
  115 + downloadFile,
114 116 onAudit() {
115 117 const CACHE_KEY = 'sourceBusinessId'
116 118 uni.setStorageSync(CACHE_KEY, this.getBusinessId())
... ...
... ... @@ -20,7 +20,7 @@
20 20 </view>
21 21
22 22 <view class="section">
23   - <view class="row"><text class="label">撤销确认凭证</text><text class="value act">{{ form.confirmationVoucherFileName }}</text></view>
  23 + <view class="row"><text class="label">撤销确认凭证</text><text class="value act" @click="downloadFile(form.confirmationVoucherFileId, form.confirmationVoucherFileName)">{{ form.confirmationVoucherFileName }}</text></view>
24 24 </view>
25 25
26 26 </view>
... ... @@ -29,6 +29,7 @@
29 29 <script>
30 30 import { getDetailApi } from '@/api/revoke_list.js'
31 31 import Product from './product.vue'
  32 +import { downloadFile } from '@/utils/downloadFile.js'
32 33
33 34 export default {
34 35 name: 'RevokeListViewer',
... ... @@ -61,6 +62,7 @@ export default {
61 62 this.form = {}
62 63 }
63 64 },
  65 + downloadFile,
64 66 getFormValues() {
65 67 const m = this.form || {}
66 68 return JSON.parse(JSON.stringify(m))
... ...
  1 +
  2 +import { downloadFileApi } from '@/api/base.js'
  3 +import { getToken } from '@/utils/auth.js'
  4 +
  5 +/**
  6 + * 下载文件通用方法
  7 + * @param id
  8 + * @param fileName
  9 + */
  10 +export async function downloadFile(id, fileName) {
  11 + if (!id) return
  12 + uni.showLoading({ title: '下载中' })
  13 + try {
  14 + const res = await downloadFileApi(id);
  15 + console.log('downloadFileApi__res', res)
  16 + let url = res.data
  17 + if (url) {
  18 + // #ifdef H5
  19 + // H5环境(包括微信、钉钉等)直接跳转可能不带header,需要把token拼接到url上
  20 + const token = getToken()
  21 + if (token) {
  22 + const separator = url.includes('?') ? '&' : '?'
  23 + url = `${url}${separator}X-Auth-Token=${token}`
  24 + }
  25 + // #endif
  26 +
  27 + // 统一使用 uni.downloadFile 下载,解决微信浏览器“在浏览器打开”的提示问题
  28 + uni.downloadFile({
  29 + url,
  30 + success: (res) => {
  31 + if (res.statusCode === 200) {
  32 + const filePath = res.tempFilePath
  33 +
  34 + // #ifdef H5
  35 + const ua = navigator.userAgent.toLowerCase()
  36 + const isAndroid = ua.indexOf('android') > -1 || ua.indexOf('adr') > -1
  37 + const isWeChat = ua.indexOf('micromessenger') !== -1
  38 +
  39 + if (isWeChat && isAndroid) {
  40 + // 安卓微信环境:提示用户去浏览器下载
  41 + // 注意:Blob URL在外部浏览器无法访问,所以这里必须使用原始的 url (带token)
  42 + // 重新构建带token的原始url
  43 + let downloadUrl = url
  44 + // 这里我们假设 url 变量在上面已经被处理过(拼接了token),如果没有,重新拼接
  45 + if (!downloadUrl.includes('X-Auth-Token')) {
  46 + const token = getToken()
  47 + if (token) {
  48 + const separator = downloadUrl.includes('?') ? '&' : '?'
  49 + downloadUrl = `${downloadUrl}${separator}X-Auth-Token=${token}`
  50 + }
  51 + }
  52 +
  53 + uni.showModal({
  54 + title: '提示',
  55 + content: '微信环境下不支持直接下载,请复制链接后在浏览器打开下载',
  56 + confirmText: '复制链接',
  57 + showCancel: false,
  58 + success: function (res) {
  59 + if (res.confirm) {
  60 + uni.setClipboardData({
  61 + data: downloadUrl,
  62 + success: function () {
  63 + uni.showToast({ title: '链接已复制', icon: 'success' })
  64 + }
  65 + })
  66 + }
  67 + }
  68 + })
  69 + } else {
  70 + // 其他环境(iOS微信、普通浏览器)使用a标签下载
  71 + const link = document.createElement('a')
  72 + link.href = filePath
  73 + link.download = fileName || 'download'
  74 + document.body.appendChild(link)
  75 + link.click()
  76 + document.body.removeChild(link)
  77 + }
  78 + // #endif
  79 +
  80 + // #ifndef H5
  81 + const fileType = fileName ? fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase() : ''
  82 + uni.openDocument({
  83 + filePath,
  84 + fileType: fileType || undefined,
  85 + showMenu: true,
  86 + success: function () {
  87 + console.log('打开文档成功')
  88 + },
  89 + fail: function (err) {
  90 + console.error('打开文档失败', err)
  91 + uni.showToast({ title: '无法打开此文件', icon: 'none' })
  92 + }
  93 + })
  94 + // #endif
  95 + } else {
  96 + uni.showToast({ title: '下载失败', icon: 'none' })
  97 + }
  98 + },
  99 + fail: (err) => {
  100 + console.error('下载失败', err)
  101 + uni.showToast({ title: '下载失败', icon: 'none' })
  102 + }
  103 + })
  104 + } else {
  105 + uni.showToast({ title: '文件地址无效', icon: 'none' })
  106 + }
  107 + } catch (e) {
  108 + console.error(e)
  109 + uni.showToast({ title: '下载出错', icon: 'none' })
  110 + } finally {
  111 + uni.hideLoading()
  112 + }
  113 +}
... ...