af-mobile-client-vue3 1.6.48 → 1.6.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +1 -1
- package/package.json +1 -1
- package/src/components/core/XMultiSelect/index.vue +54 -45
- package/src/components/core/XSelect/index.vue +18 -1
- package/src/components/data/FilePreview/components/KkPreview.vue +88 -0
- package/src/components/data/FilePreview/components/MediaPreview.vue +64 -0
- package/src/components/data/FilePreview/components/OfficePreview.vue +65 -0
- package/src/components/data/FilePreview/components/TextPreview.vue +27 -0
- package/src/components/data/FilePreview/index.vue +100 -203
- package/src/components/data/OtherCharge/OtherChargeGroupModal.vue +542 -542
- package/src/components/data/XCellList/index.vue +109 -93
- package/src/components/data/step/person-select.vue +57 -39
- package/src/stores/modules/step.ts +88 -88
- package/src/views/component/FilePreviewView/index.vue +6 -3
- package/src/views/component/StepView/index.vue +1 -1
package/.env
CHANGED
|
@@ -5,7 +5,7 @@ VITE_APP_OUT_DIR=dist_af-library-mobile-web
|
|
|
5
5
|
VITE_APP_VCONSOLE=false
|
|
6
6
|
# 自定义参数
|
|
7
7
|
VITE_APP_WEB_CONFIG_KEY=admin.webconfig
|
|
8
|
-
VITE_APP_SYSTEM_NAME=af-
|
|
8
|
+
VITE_APP_SYSTEM_NAME=af-system
|
|
9
9
|
# 最低兼容性 V4(最新产品)V3(V3产品) OA(公司OA)
|
|
10
10
|
VITE_APP_COMPATIBLE=V4
|
|
11
11
|
VITE_RSA_PRIVATE_KEY=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIRrmftLDHCQqREEJ132Onu+W3vmFbdF7QD751SrcDDGDTfzuz1zBuElvkHhuDBb7KZkXrCIe+MhvX2IvxcLObl3faX+evYlnfj2HRbF0hIpQLuIq22tL06ZcV5w7wqLxUZRpFElIFm8gZTkUvfKXVuHw89e4daDVhU5hK3GHNGTAgMBAAECgYABiINrFaE1E8pkBYx1JJA5yuhL73aUktfd2TeCU00vFg6kyrWCI85Sa2RKu/6CJNZWeOFgdubEUv7a22tRrNIZb3yUMaqtTwSso78mspIOJqjWXTkTH9WPElfTcdpdIse/lgZtPz6egxkuhadSvwrM9Y6NgusiW/5+x95Ct08iOQJBAN5aK+7uISURvGQj2EaRtgGEd8+d4oHl+BYvvTeG3qSgUikHQW3j0sp4gXPw2kxw6sjVgLFOc4FB6LGqwzOTzokCQQCYdYG8ty3Uo/ebUlNzeJFxHXjy/KvBSytAUzAXkRu3nZrkEaPQsi3dgOkZgk+F1fMDzfQ4EbDIU6xvqOoZXHg7AkATCW9XfoXR8anKfRMoP5Nwn9HOMbtR2cmaxK2TknV/bMZ8AsYETYwfj5+tuIJIJybC2RyykX/sIiN1CqS5xr7ZAkArj19rMRdaKyMi8MnBM1Cy9g3Jt2HHj5ejAGG8SgyWUOShh1y70z0BjcSMMkxQXAncK2s83ekZw7aADM4eQupjAkARRgTwwMOnn3IoKmQusKhZk0uxilZ4Zc2LH6Z4GiWnvteM0W8Zw4Z1lJUcjgQq3dGqL2RdmzeQZ+HgPIOXrZVK
|
package/package.json
CHANGED
|
@@ -149,52 +149,54 @@ const resultLabel = computed(() => {
|
|
|
149
149
|
:is-link="$attrs.disabled === undefined"
|
|
150
150
|
@click="showPopu($attrs.readonly)"
|
|
151
151
|
/>
|
|
152
|
-
<VanPopup v-model:show="show" position="bottom">
|
|
153
|
-
<div class="
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<button type="button" class="van-picker__confirm" @click="onConfirm">
|
|
161
|
-
确认
|
|
162
|
-
</button>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="x-multi-select-content">
|
|
165
|
-
<VanSearch
|
|
166
|
-
v-if="props.isSearch"
|
|
167
|
-
v-model="searchVal"
|
|
168
|
-
placeholder="搜索"
|
|
169
|
-
@update:model-value="search"
|
|
170
|
-
@cancel="search"
|
|
171
|
-
/>
|
|
172
|
-
<div v-if="isLoading" class="loading-container">
|
|
173
|
-
<div class="loading-text">
|
|
174
|
-
搜索中...
|
|
152
|
+
<VanPopup v-model:show="show" position="bottom" teleport="body" round>
|
|
153
|
+
<div class="x-multi-select-popup">
|
|
154
|
+
<div class="van-picker__toolbar">
|
|
155
|
+
<button type="button" class="van-picker__cancel" @click="cancel">
|
|
156
|
+
取消
|
|
157
|
+
</button>
|
|
158
|
+
<div class="van-ellipsis van-picker__title">
|
|
159
|
+
{{ $attrs.label }}
|
|
175
160
|
</div>
|
|
161
|
+
<button type="button" class="van-picker__confirm" @click="onConfirm">
|
|
162
|
+
确认
|
|
163
|
+
</button>
|
|
176
164
|
</div>
|
|
177
|
-
<div
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
165
|
+
<div class="x-multi-select-content">
|
|
166
|
+
<VanSearch
|
|
167
|
+
v-if="props.isSearch"
|
|
168
|
+
v-model="searchVal"
|
|
169
|
+
placeholder="搜索"
|
|
170
|
+
@update:model-value="search"
|
|
171
|
+
@cancel="search"
|
|
172
|
+
/>
|
|
173
|
+
<div v-if="isLoading" class="loading-container">
|
|
174
|
+
<div class="loading-text">
|
|
175
|
+
搜索中...
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<div v-else class="x-multi-select-checkbox-group">
|
|
179
|
+
<VanCell title="全选">
|
|
180
|
+
<template #right-icon>
|
|
181
|
+
<VanCheckbox v-model="checkedAll" name="all" @click="toggleAll" />
|
|
182
|
+
</template>
|
|
183
|
+
</VanCell>
|
|
184
|
+
<VanCheckboxGroup ref="checkboxGroup" v-model="checkboxValue" @change="change">
|
|
185
|
+
<VanCellGroup>
|
|
186
|
+
<VanCell
|
|
187
|
+
v-for="(item, index) in columnsData"
|
|
188
|
+
:key="item[props.option.value]"
|
|
189
|
+
:title="item[props.option.text]"
|
|
190
|
+
clickable
|
|
191
|
+
@click="toggle(item, index)"
|
|
192
|
+
>
|
|
193
|
+
<template #right-icon>
|
|
194
|
+
<VanCheckbox ref="checkboxes" :name="item[props.option.value]" @click.stop />
|
|
195
|
+
</template>
|
|
196
|
+
</VanCell>
|
|
197
|
+
</VanCellGroup>
|
|
198
|
+
</VanCheckboxGroup>
|
|
199
|
+
</div>
|
|
198
200
|
</div>
|
|
199
201
|
</div>
|
|
200
202
|
</VanPopup>
|
|
@@ -213,12 +215,19 @@ const resultLabel = computed(() => {
|
|
|
213
215
|
left: -8px;
|
|
214
216
|
}
|
|
215
217
|
.x-multi-select-content {
|
|
216
|
-
max-height:
|
|
218
|
+
max-height: calc(70vh - 44px);
|
|
217
219
|
overflow-y: auto;
|
|
218
220
|
.x-multi-select-checkbox-group {
|
|
219
221
|
padding: 0 16px;
|
|
220
222
|
}
|
|
221
223
|
}
|
|
224
|
+
.x-multi-select-popup {
|
|
225
|
+
max-height: 70vh;
|
|
226
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
}
|
|
222
231
|
.loading-container {
|
|
223
232
|
display: flex;
|
|
224
233
|
justify-content: center;
|
|
@@ -197,7 +197,7 @@ onBeforeUnmount(() => {
|
|
|
197
197
|
:border="props.border"
|
|
198
198
|
@click="showPopu($attrs.readonly)"
|
|
199
199
|
/>
|
|
200
|
-
<VanPopup v-model:show="show" position="bottom" teleport="body">
|
|
200
|
+
<VanPopup v-model:show="show" position="bottom" teleport="body" round>
|
|
201
201
|
<div class="x-select-popup">
|
|
202
202
|
<!-- 搜索框 -->
|
|
203
203
|
<VanSearch
|
|
@@ -222,6 +222,23 @@ onBeforeUnmount(() => {
|
|
|
222
222
|
</template>
|
|
223
223
|
|
|
224
224
|
<style scoped>
|
|
225
|
+
.x-select-popup {
|
|
226
|
+
max-height: 70vh;
|
|
227
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.x-select-popup :deep(.van-picker) {
|
|
234
|
+
flex: 1;
|
|
235
|
+
min-height: 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.x-select-popup :deep(.van-picker__columns) {
|
|
239
|
+
max-height: 40vh;
|
|
240
|
+
}
|
|
241
|
+
|
|
225
242
|
.loading-container {
|
|
226
243
|
display: flex;
|
|
227
244
|
justify-content: center;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* kkFileView 预览子组件
|
|
4
|
+
* 通过 iframe 加载 kkFileView 服务端预览页面
|
|
5
|
+
* 适用于 office 渲染器无法处理的格式(如 .doc / .xls 旧格式)或 office 渲染失败的降级场景
|
|
6
|
+
*/
|
|
7
|
+
import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
|
|
8
|
+
import { Base64 } from 'js-base64'
|
|
9
|
+
import { Loading as VanLoading } from 'vant'
|
|
10
|
+
import { onMounted, ref } from 'vue'
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
/** 文件原始路径(相对路径或完整 URL) */
|
|
14
|
+
filePath: string
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const iframeUrl = ref('')
|
|
18
|
+
const iframeLoading = ref(true)
|
|
19
|
+
|
|
20
|
+
function isHttp(path: string) {
|
|
21
|
+
return /^https?:\/\//i.test(path)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 读取 kkFileView 服务配置,拼接预览 URL */
|
|
25
|
+
async function buildPreviewUrl() {
|
|
26
|
+
try {
|
|
27
|
+
const res: any = await getConfigByNameAsync('previewDocServiceConfig')
|
|
28
|
+
if (!res?.previewDocService || !res?.fileServer)
|
|
29
|
+
throw new Error('previewDocServiceConfig 配置缺失')
|
|
30
|
+
|
|
31
|
+
const sourceUrl = isHttp(props.filePath)
|
|
32
|
+
? props.filePath
|
|
33
|
+
: `${res.fileServer}${props.filePath}`
|
|
34
|
+
|
|
35
|
+
// kkFileView 要求 url 参数为 Base64 编码后再 encodeURIComponent
|
|
36
|
+
iframeUrl.value = `${res.previewDocService}${encodeURIComponent(Base64.encode(sourceUrl))}`
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.warn('[KkPreview] 配置加载失败,降级直接加载原始地址', e)
|
|
40
|
+
// 服务未配置时,直接把原始地址塞给 iframe
|
|
41
|
+
iframeUrl.value = isHttp(props.filePath) ? props.filePath : ''
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onMounted(buildPreviewUrl)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div class="preview-doc-container">
|
|
50
|
+
<VanLoading v-if="iframeLoading" class="kk-loading" type="spinner" vertical>
|
|
51
|
+
加载中...
|
|
52
|
+
</VanLoading>
|
|
53
|
+
<iframe
|
|
54
|
+
v-if="iframeUrl"
|
|
55
|
+
v-show="!iframeLoading"
|
|
56
|
+
:src="iframeUrl"
|
|
57
|
+
width="100%"
|
|
58
|
+
height="100%"
|
|
59
|
+
frameborder="0"
|
|
60
|
+
@load="iframeLoading = false"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<style scoped>
|
|
66
|
+
.preview-doc-container {
|
|
67
|
+
height: 100%;
|
|
68
|
+
position: relative;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* 透明遮罩:阻止 iframe 在点击悬浮按钮时抢夺事件 */
|
|
72
|
+
.preview-doc-container::after {
|
|
73
|
+
content: '';
|
|
74
|
+
position: absolute;
|
|
75
|
+
inset: 0;
|
|
76
|
+
z-index: 99;
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.kk-loading {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 0;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 媒体预览子组件
|
|
4
|
+
* 处理 image / video / audio 三种类型,直接用原生 HTML 标签渲染
|
|
5
|
+
*/
|
|
6
|
+
import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
|
|
7
|
+
|
|
8
|
+
defineProps<{
|
|
9
|
+
kind: PreviewFileKind
|
|
10
|
+
src: string
|
|
11
|
+
}>()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div v-if="kind === 'image'" class="media-wrapper">
|
|
16
|
+
<img :src="src" alt="预览图片" class="media-image">
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div v-else-if="kind === 'video'" class="media-wrapper media-dark">
|
|
20
|
+
<video class="media-video" controls :src="src" />
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div v-else-if="kind === 'audio'" class="media-audio-wrapper">
|
|
24
|
+
<audio class="media-audio" controls :src="src" />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
.media-wrapper {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-height: 200px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.media-dark {
|
|
38
|
+
background: #000;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.media-image {
|
|
42
|
+
max-width: 100%;
|
|
43
|
+
max-height: 100%;
|
|
44
|
+
object-fit: contain;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.media-video {
|
|
48
|
+
width: 100%;
|
|
49
|
+
max-height: 100%;
|
|
50
|
+
border: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.media-audio-wrapper {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
padding: 32px 16px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.media-audio {
|
|
61
|
+
width: 100%;
|
|
62
|
+
max-width: 480px;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Office 文档预览子组件
|
|
4
|
+
* 使用 @vue-office 系列库渲染 pdf / docx / xlsx / xls / doc
|
|
5
|
+
* 渲染失败时 emit 'error',由父组件决定是否降级到 kk 预览
|
|
6
|
+
*/
|
|
7
|
+
import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
|
|
8
|
+
import { defineAsyncComponent } from 'vue'
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
kind: PreviewFileKind
|
|
12
|
+
src: string | ArrayBuffer
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
(e: 'error'): void
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
// 按需异步加载,避免影响首屏
|
|
20
|
+
const VueOfficePdf = defineAsyncComponent(() =>
|
|
21
|
+
import('@vue-office/pdf/lib/v3/vue-office-pdf.mjs').then(m => m.default),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const VueOfficeDocx = defineAsyncComponent(async () => {
|
|
25
|
+
await import('@vue-office/docx/lib/v3/index.css')
|
|
26
|
+
return import('@vue-office/docx/lib/v3/vue-office-docx.mjs').then(m => m.default)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const VueOfficeExcel = defineAsyncComponent(async () => {
|
|
30
|
+
await import('@vue-office/excel/lib/v3/index.css')
|
|
31
|
+
return import('@vue-office/excel/lib/v3/vue-office-excel.mjs').then(m => m.default)
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<VueOfficePdf
|
|
37
|
+
v-if="kind === 'pdf'"
|
|
38
|
+
:src="src"
|
|
39
|
+
class="office-viewer"
|
|
40
|
+
@error="emit('error')"
|
|
41
|
+
/>
|
|
42
|
+
<!-- docx / doc 共用 docx 渲染器;.doc 若解析失败会触发 error 降级到 kk -->
|
|
43
|
+
<VueOfficeDocx
|
|
44
|
+
v-else-if="kind === 'docx' || kind === 'doc'"
|
|
45
|
+
:src="src"
|
|
46
|
+
class="office-viewer"
|
|
47
|
+
@error="emit('error')"
|
|
48
|
+
/>
|
|
49
|
+
<!-- xlsx / xls 共用 excel 渲染器 -->
|
|
50
|
+
<VueOfficeExcel
|
|
51
|
+
v-else-if="kind === 'xlsx' || kind === 'xls'"
|
|
52
|
+
:src="src"
|
|
53
|
+
class="office-viewer"
|
|
54
|
+
@error="emit('error')"
|
|
55
|
+
/>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<style scoped>
|
|
59
|
+
.office-viewer {
|
|
60
|
+
width: 100%;
|
|
61
|
+
flex: 1;
|
|
62
|
+
min-height: 200px;
|
|
63
|
+
overflow: auto;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 纯文本预览子组件
|
|
4
|
+
* 直接渲染文本内容,保留空白和换行
|
|
5
|
+
*/
|
|
6
|
+
defineProps<{
|
|
7
|
+
content: string
|
|
8
|
+
}>()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<pre class="text-preview">{{ content }}</pre>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<style scoped>
|
|
16
|
+
.text-preview {
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 12px;
|
|
19
|
+
white-space: pre-wrap;
|
|
20
|
+
word-break: break-word;
|
|
21
|
+
font-size: 13px;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
color: #333;
|
|
24
|
+
overflow: auto;
|
|
25
|
+
flex: 1;
|
|
26
|
+
}
|
|
27
|
+
</style>
|