af-mobile-client-vue3 1.6.29 → 1.6.30
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/package.json +1 -1
- package/src/components/core/MessageNotification/MessageNotification.vue +1 -1
- package/src/components/data/XOlMap/index.vue +13 -5
- package/src/components/data/step/person-select.vue +168 -168
- package/src/stores/index.ts +15 -15
- package/src/stores/modules/captcha.ts +108 -108
- package/src/stores/modules/step.ts +88 -88
- package/src/views/SafeInspection/SecurityCertificate/photoSignature/slots/QinHuaSignature.vue +5 -7
- package/src/views/component/FilePreviewView/index.vue +31 -31
- package/src/views/component/StepView/index.vue +1 -1
- package/src/views/user/login/LoginForm.vue +574 -574
- package/src/views/userRecords/PaymentRecords.vue +2 -2
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ async function fetchMessages() {
|
|
|
36
36
|
userId: `${userInfo.id}:phone`, // |phone
|
|
37
37
|
typeList: '\'phone\'', // phone
|
|
38
38
|
pageNo: 1,
|
|
39
|
-
pageSize:
|
|
39
|
+
pageSize: 10,
|
|
40
40
|
}
|
|
41
41
|
const res = await post('/af-system/logic/getNotificationListByType', param)
|
|
42
42
|
messages.value = (res || []).map((item: any) => {
|
|
@@ -63,12 +63,12 @@ const preview = ref(false)
|
|
|
63
63
|
/** 地图实例 */
|
|
64
64
|
let map: Map | null = null
|
|
65
65
|
/** 当前底图类型 */
|
|
66
|
-
const currentMapType = ref<string>('
|
|
66
|
+
const currentMapType = ref<string>('gaode')
|
|
67
67
|
/** 控制图层面板显示状态 */
|
|
68
68
|
const showControls = ref<boolean>(false)
|
|
69
69
|
|
|
70
70
|
/** 图层选项配置 */
|
|
71
|
-
|
|
71
|
+
let layerOptions = [
|
|
72
72
|
{ text: '高德地图', value: 'gaode' },
|
|
73
73
|
{ text: '高德卫星', value: 'gaodeSatellite' },
|
|
74
74
|
{ text: '天地图', value: 'tianditu' },
|
|
@@ -199,7 +199,7 @@ function initializeLayers(tianDiTuKey = ''): void {
|
|
|
199
199
|
// 高德地图
|
|
200
200
|
baseMaps.gaode = new TileLayer({
|
|
201
201
|
source: new XYZ({
|
|
202
|
-
url: '
|
|
202
|
+
url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
|
|
203
203
|
crossOrigin: 'anonymous',
|
|
204
204
|
projection: 'EPSG:3857',
|
|
205
205
|
maxZoom: 18,
|
|
@@ -364,7 +364,7 @@ function init(params: InitParams = {}): Promise<void> {
|
|
|
364
364
|
const {
|
|
365
365
|
center = [116.404, 39.915],
|
|
366
366
|
zoom = 10,
|
|
367
|
-
maxZoom =
|
|
367
|
+
maxZoom = 21,
|
|
368
368
|
minZoom = 4,
|
|
369
369
|
isPreview = false,
|
|
370
370
|
} = params
|
|
@@ -375,6 +375,14 @@ function init(params: InitParams = {}): Promise<void> {
|
|
|
375
375
|
const tianDiTuKey = res.tianDiTuKey || 'c16876b28898637c0a1a68b3fa410504'
|
|
376
376
|
const amapKey = res.amapKey || '5ebabc4536d4b42e0dd1e20175cca8ab'
|
|
377
377
|
|
|
378
|
+
const initMapOptionConfig = res.mapOptionConfig
|
|
379
|
+
if (initMapOptionConfig.mapType) {
|
|
380
|
+
currentMapType.value = initMapOptionConfig.mapType
|
|
381
|
+
}
|
|
382
|
+
if (initMapOptionConfig.mapOption) {
|
|
383
|
+
layerOptions = initMapOptionConfig.mapOption
|
|
384
|
+
}
|
|
385
|
+
|
|
378
386
|
tiandityKey.value = tianDiTuKey
|
|
379
387
|
gaodeKey.value = amapKey
|
|
380
388
|
// 初始化所有底图图层
|
|
@@ -412,7 +420,7 @@ function init(params: InitParams = {}): Promise<void> {
|
|
|
412
420
|
if (map) {
|
|
413
421
|
map.updateSize()
|
|
414
422
|
// 确保默认图层正确显示
|
|
415
|
-
handleMapChange(
|
|
423
|
+
handleMapChange(currentMapType.value)
|
|
416
424
|
// 地图初始化完成后解析 Promise
|
|
417
425
|
resolve()
|
|
418
426
|
}
|
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
Field as VanField,
|
|
4
|
-
Picker as VanPicker,
|
|
5
|
-
Popup as VanPopup,
|
|
6
|
-
Search as VanSearch,
|
|
7
|
-
} from 'vant'
|
|
8
|
-
import { computed, inject, onMounted, ref } from 'vue'
|
|
9
|
-
|
|
10
|
-
const workflowHandleWrap: any = inject('workflowHandleWrap')
|
|
11
|
-
|
|
12
|
-
const showPicker = ref(false)
|
|
13
|
-
|
|
14
|
-
const showMultiplePicker = ref(false)
|
|
15
|
-
|
|
16
|
-
const branchChargePersons = ref({})
|
|
17
|
-
|
|
18
|
-
const isInit = ref(false)
|
|
19
|
-
|
|
20
|
-
// 用于 filterOption 的本地输入缓存(vant 不自带 filter)
|
|
21
|
-
const filterKeyword = ref('')
|
|
22
|
-
|
|
23
|
-
const customFieldName = {
|
|
24
|
-
text: 'label',
|
|
25
|
-
value: 'value',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const selectedNode = ref(undefined)
|
|
29
|
-
|
|
30
|
-
const selectedOptions = computed(() => {
|
|
31
|
-
if (!selectedNode.value) {
|
|
32
|
-
return []
|
|
33
|
-
}
|
|
34
|
-
return filterOptions(selectedNode.value.chargePersonOptions)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
onMounted(() => {
|
|
38
|
-
isInit.value = false
|
|
39
|
-
if (workflowHandleWrap.branchNodes.value) {
|
|
40
|
-
// 初始化初始值
|
|
41
|
-
for (const node of workflowHandleWrap.branchNodes.value) {
|
|
42
|
-
branchChargePersons.value[node.stepId] = {
|
|
43
|
-
handler: '',
|
|
44
|
-
handlerId: '',
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
isInit.value = true
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
function setBranchPersonValue(stepId, value, options, selectedOptions) {
|
|
52
|
-
if (!stepId)
|
|
53
|
-
return
|
|
54
|
-
if (!workflowHandleWrap.branchChargePersons.value) {
|
|
55
|
-
workflowHandleWrap.branchChargePersons.value = {}
|
|
56
|
-
}
|
|
57
|
-
if (!workflowHandleWrap.branchChargePersons.value[stepId]) {
|
|
58
|
-
workflowHandleWrap.branchChargePersons.value[stepId] = {}
|
|
59
|
-
}
|
|
60
|
-
if (workflowHandleWrap.branchChargePersons.value[stepId]) {
|
|
61
|
-
branchChargePersons.value[stepId] = {
|
|
62
|
-
handler: options.find(item => item.value === value)?.label,
|
|
63
|
-
handlerId: value,
|
|
64
|
-
}
|
|
65
|
-
Object.assign(workflowHandleWrap.branchChargePersons.value[stepId], branchChargePersons.value[stepId])
|
|
66
|
-
}
|
|
67
|
-
workflowHandleWrap.checkedNextStepPerson.value = selectedOptions[0].value
|
|
68
|
-
workflowHandleWrap.checkedNextStepPersonName.value = selectedOptions[0].label
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getBranchSelectionLabel() {
|
|
72
|
-
if (workflowHandleWrap.needMultipleBranchSelection.value) {
|
|
73
|
-
return '分支处理人'
|
|
74
|
-
}
|
|
75
|
-
else if (workflowHandleWrap.calculatedTargetNode.value) {
|
|
76
|
-
return `${workflowHandleWrap.getStepNameByStepId(workflowHandleWrap.calculatedTargetNode.value)}处理人`
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
return '处理人'
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// vant 没有原生 filter-option,因此需要本地筛选
|
|
84
|
-
function filterOptions(options) {
|
|
85
|
-
if (!filterKeyword.value)
|
|
86
|
-
return options
|
|
87
|
-
return options.filter(option => option.label.toLowerCase().includes(filterKeyword.value.toLowerCase()))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 搜索回调函数
|
|
91
|
-
function search(val: string) {
|
|
92
|
-
filterKeyword.value = val || ''
|
|
93
|
-
}
|
|
94
|
-
</script>
|
|
95
|
-
|
|
96
|
-
<template>
|
|
97
|
-
<div v-if="workflowHandleWrap.isNeedSelectPerson.value && isInit">
|
|
98
|
-
<!-- 多分支选择 -->
|
|
99
|
-
<template v-if="workflowHandleWrap.needMultipleBranchSelection.value && workflowHandleWrap.branchNodes.value.length > 0">
|
|
100
|
-
<template v-for="node in workflowHandleWrap.branchNodes.value" :key="node.stepId">
|
|
101
|
-
<VanField
|
|
102
|
-
v-model="branchChargePersons[node.stepId].handler"
|
|
103
|
-
:label="`${node.stepName}处理人`"
|
|
104
|
-
is-link
|
|
105
|
-
clickable
|
|
106
|
-
required
|
|
107
|
-
placeholder="请选择处理人"
|
|
108
|
-
@click="selectedNode = node; showMultiplePicker = true;"
|
|
109
|
-
/>
|
|
110
|
-
</template>
|
|
111
|
-
<VanPopup v-model:show="showMultiplePicker" position="bottom">
|
|
112
|
-
<!-- 搜索框 -->
|
|
113
|
-
<VanSearch
|
|
114
|
-
v-model="filterKeyword"
|
|
115
|
-
placeholder="搜索"
|
|
116
|
-
@clear="() => search('')"
|
|
117
|
-
@update:model-value="search"
|
|
118
|
-
/>
|
|
119
|
-
<VanPicker
|
|
120
|
-
:columns="selectedOptions"
|
|
121
|
-
:columns-field-names="customFieldName"
|
|
122
|
-
value-key="label"
|
|
123
|
-
@confirm="(val) => {
|
|
124
|
-
setBranchPersonValue(selectedNode.stepId, val.selectedValues[0], selectedNode.chargePersonOptions, val.selectedOptions)
|
|
125
|
-
showMultiplePicker = false;
|
|
126
|
-
}"
|
|
127
|
-
@cancel="showMultiplePicker = false;"
|
|
128
|
-
/>
|
|
129
|
-
</VanPopup>
|
|
130
|
-
</template>
|
|
131
|
-
|
|
132
|
-
<!-- 单分支选择 -->
|
|
133
|
-
<template v-else>
|
|
134
|
-
<VanField
|
|
135
|
-
v-model="workflowHandleWrap.checkedNextStepPersonName.value"
|
|
136
|
-
:label="getBranchSelectionLabel()"
|
|
137
|
-
label-width="auto"
|
|
138
|
-
is-link
|
|
139
|
-
clickable
|
|
140
|
-
required
|
|
141
|
-
placeholder="请选择处理人"
|
|
142
|
-
@click="showPicker = true"
|
|
143
|
-
/>
|
|
144
|
-
<VanPopup v-model:show="showPicker" position="bottom">
|
|
145
|
-
<!-- 搜索框 -->
|
|
146
|
-
<VanSearch
|
|
147
|
-
v-model="filterKeyword"
|
|
148
|
-
placeholder="搜索"
|
|
149
|
-
@clear="() => search('')"
|
|
150
|
-
@update:model-value="search"
|
|
151
|
-
/>
|
|
152
|
-
<VanPicker
|
|
153
|
-
:columns="filterOptions(workflowHandleWrap.nextStepPersonOptions.value)"
|
|
154
|
-
:columns-field-names="customFieldName"
|
|
155
|
-
@confirm="(val) => {
|
|
156
|
-
setBranchPersonValue(workflowHandleWrap.calculatedTargetNode.value, val.selectedValues[0], workflowHandleWrap.nextStepPersonOptions.value, val.selectedOptions)
|
|
157
|
-
showPicker = false;
|
|
158
|
-
}"
|
|
159
|
-
@cancel="showPicker = false;"
|
|
160
|
-
/>
|
|
161
|
-
</VanPopup>
|
|
162
|
-
</template>
|
|
163
|
-
</div>
|
|
164
|
-
</template>
|
|
165
|
-
|
|
166
|
-
<style scoped>
|
|
167
|
-
/* 保持你的样式或根据 vant 移动端自行优化 */
|
|
168
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Field as VanField,
|
|
4
|
+
Picker as VanPicker,
|
|
5
|
+
Popup as VanPopup,
|
|
6
|
+
Search as VanSearch,
|
|
7
|
+
} from 'vant'
|
|
8
|
+
import { computed, inject, onMounted, ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
const workflowHandleWrap: any = inject('workflowHandleWrap')
|
|
11
|
+
|
|
12
|
+
const showPicker = ref(false)
|
|
13
|
+
|
|
14
|
+
const showMultiplePicker = ref(false)
|
|
15
|
+
|
|
16
|
+
const branchChargePersons = ref({})
|
|
17
|
+
|
|
18
|
+
const isInit = ref(false)
|
|
19
|
+
|
|
20
|
+
// 用于 filterOption 的本地输入缓存(vant 不自带 filter)
|
|
21
|
+
const filterKeyword = ref('')
|
|
22
|
+
|
|
23
|
+
const customFieldName = {
|
|
24
|
+
text: 'label',
|
|
25
|
+
value: 'value',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const selectedNode = ref(undefined)
|
|
29
|
+
|
|
30
|
+
const selectedOptions = computed(() => {
|
|
31
|
+
if (!selectedNode.value) {
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
return filterOptions(selectedNode.value.chargePersonOptions)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
onMounted(() => {
|
|
38
|
+
isInit.value = false
|
|
39
|
+
if (workflowHandleWrap.branchNodes.value) {
|
|
40
|
+
// 初始化初始值
|
|
41
|
+
for (const node of workflowHandleWrap.branchNodes.value) {
|
|
42
|
+
branchChargePersons.value[node.stepId] = {
|
|
43
|
+
handler: '',
|
|
44
|
+
handlerId: '',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
isInit.value = true
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function setBranchPersonValue(stepId, value, options, selectedOptions) {
|
|
52
|
+
if (!stepId)
|
|
53
|
+
return
|
|
54
|
+
if (!workflowHandleWrap.branchChargePersons.value) {
|
|
55
|
+
workflowHandleWrap.branchChargePersons.value = {}
|
|
56
|
+
}
|
|
57
|
+
if (!workflowHandleWrap.branchChargePersons.value[stepId]) {
|
|
58
|
+
workflowHandleWrap.branchChargePersons.value[stepId] = {}
|
|
59
|
+
}
|
|
60
|
+
if (workflowHandleWrap.branchChargePersons.value[stepId]) {
|
|
61
|
+
branchChargePersons.value[stepId] = {
|
|
62
|
+
handler: options.find(item => item.value === value)?.label,
|
|
63
|
+
handlerId: value,
|
|
64
|
+
}
|
|
65
|
+
Object.assign(workflowHandleWrap.branchChargePersons.value[stepId], branchChargePersons.value[stepId])
|
|
66
|
+
}
|
|
67
|
+
workflowHandleWrap.checkedNextStepPerson.value = selectedOptions[0].value
|
|
68
|
+
workflowHandleWrap.checkedNextStepPersonName.value = selectedOptions[0].label
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getBranchSelectionLabel() {
|
|
72
|
+
if (workflowHandleWrap.needMultipleBranchSelection.value) {
|
|
73
|
+
return '分支处理人'
|
|
74
|
+
}
|
|
75
|
+
else if (workflowHandleWrap.calculatedTargetNode.value) {
|
|
76
|
+
return `${workflowHandleWrap.getStepNameByStepId(workflowHandleWrap.calculatedTargetNode.value)}处理人`
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return '处理人'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// vant 没有原生 filter-option,因此需要本地筛选
|
|
84
|
+
function filterOptions(options) {
|
|
85
|
+
if (!filterKeyword.value)
|
|
86
|
+
return options
|
|
87
|
+
return options.filter(option => option.label.toLowerCase().includes(filterKeyword.value.toLowerCase()))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 搜索回调函数
|
|
91
|
+
function search(val: string) {
|
|
92
|
+
filterKeyword.value = val || ''
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<template>
|
|
97
|
+
<div v-if="workflowHandleWrap.isNeedSelectPerson.value && isInit">
|
|
98
|
+
<!-- 多分支选择 -->
|
|
99
|
+
<template v-if="workflowHandleWrap.needMultipleBranchSelection.value && workflowHandleWrap.branchNodes.value.length > 0">
|
|
100
|
+
<template v-for="node in workflowHandleWrap.branchNodes.value" :key="node.stepId">
|
|
101
|
+
<VanField
|
|
102
|
+
v-model="branchChargePersons[node.stepId].handler"
|
|
103
|
+
:label="`${node.stepName}处理人`"
|
|
104
|
+
is-link
|
|
105
|
+
clickable
|
|
106
|
+
required
|
|
107
|
+
placeholder="请选择处理人"
|
|
108
|
+
@click="selectedNode = node; showMultiplePicker = true;"
|
|
109
|
+
/>
|
|
110
|
+
</template>
|
|
111
|
+
<VanPopup v-model:show="showMultiplePicker" position="bottom">
|
|
112
|
+
<!-- 搜索框 -->
|
|
113
|
+
<VanSearch
|
|
114
|
+
v-model="filterKeyword"
|
|
115
|
+
placeholder="搜索"
|
|
116
|
+
@clear="() => search('')"
|
|
117
|
+
@update:model-value="search"
|
|
118
|
+
/>
|
|
119
|
+
<VanPicker
|
|
120
|
+
:columns="selectedOptions"
|
|
121
|
+
:columns-field-names="customFieldName"
|
|
122
|
+
value-key="label"
|
|
123
|
+
@confirm="(val) => {
|
|
124
|
+
setBranchPersonValue(selectedNode.stepId, val.selectedValues[0], selectedNode.chargePersonOptions, val.selectedOptions)
|
|
125
|
+
showMultiplePicker = false;
|
|
126
|
+
}"
|
|
127
|
+
@cancel="showMultiplePicker = false;"
|
|
128
|
+
/>
|
|
129
|
+
</VanPopup>
|
|
130
|
+
</template>
|
|
131
|
+
|
|
132
|
+
<!-- 单分支选择 -->
|
|
133
|
+
<template v-else>
|
|
134
|
+
<VanField
|
|
135
|
+
v-model="workflowHandleWrap.checkedNextStepPersonName.value"
|
|
136
|
+
:label="getBranchSelectionLabel()"
|
|
137
|
+
label-width="auto"
|
|
138
|
+
is-link
|
|
139
|
+
clickable
|
|
140
|
+
required
|
|
141
|
+
placeholder="请选择处理人"
|
|
142
|
+
@click="showPicker = true"
|
|
143
|
+
/>
|
|
144
|
+
<VanPopup v-model:show="showPicker" position="bottom">
|
|
145
|
+
<!-- 搜索框 -->
|
|
146
|
+
<VanSearch
|
|
147
|
+
v-model="filterKeyword"
|
|
148
|
+
placeholder="搜索"
|
|
149
|
+
@clear="() => search('')"
|
|
150
|
+
@update:model-value="search"
|
|
151
|
+
/>
|
|
152
|
+
<VanPicker
|
|
153
|
+
:columns="filterOptions(workflowHandleWrap.nextStepPersonOptions.value)"
|
|
154
|
+
:columns-field-names="customFieldName"
|
|
155
|
+
@confirm="(val) => {
|
|
156
|
+
setBranchPersonValue(workflowHandleWrap.calculatedTargetNode.value, val.selectedValues[0], workflowHandleWrap.nextStepPersonOptions.value, val.selectedOptions)
|
|
157
|
+
showPicker = false;
|
|
158
|
+
}"
|
|
159
|
+
@cancel="showPicker = false;"
|
|
160
|
+
/>
|
|
161
|
+
</VanPopup>
|
|
162
|
+
</template>
|
|
163
|
+
</div>
|
|
164
|
+
</template>
|
|
165
|
+
|
|
166
|
+
<style scoped>
|
|
167
|
+
/* 保持你的样式或根据 vant 移动端自行优化 */
|
|
168
|
+
</style>
|
package/src/stores/index.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { createPinia } from 'pinia'
|
|
2
|
-
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
3
|
-
|
|
4
|
-
import useCaptchaStore from './modules/captcha'
|
|
5
|
-
import useHomeAppStore from './modules/homeApp'
|
|
6
|
-
import useRouteCacheStore from './modules/routeCache'
|
|
7
|
-
import useSettingStore from './modules/setting'
|
|
8
|
-
import useStepStore from './modules/step'
|
|
9
|
-
import useUserStore from './modules/user'
|
|
10
|
-
|
|
11
|
-
const pinia = createPinia()
|
|
12
|
-
pinia.use(piniaPluginPersistedstate)
|
|
13
|
-
|
|
14
|
-
export { useCaptchaStore, useHomeAppStore, useRouteCacheStore, useSettingStore, useStepStore, useUserStore }
|
|
15
|
-
export default pinia
|
|
1
|
+
import { createPinia } from 'pinia'
|
|
2
|
+
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
3
|
+
|
|
4
|
+
import useCaptchaStore from './modules/captcha'
|
|
5
|
+
import useHomeAppStore from './modules/homeApp'
|
|
6
|
+
import useRouteCacheStore from './modules/routeCache'
|
|
7
|
+
import useSettingStore from './modules/setting'
|
|
8
|
+
import useStepStore from './modules/step'
|
|
9
|
+
import useUserStore from './modules/user'
|
|
10
|
+
|
|
11
|
+
const pinia = createPinia()
|
|
12
|
+
pinia.use(piniaPluginPersistedstate)
|
|
13
|
+
|
|
14
|
+
export { useCaptchaStore, useHomeAppStore, useRouteCacheStore, useSettingStore, useStepStore, useUserStore }
|
|
15
|
+
export default pinia
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import { useSettingStore } from '@af-mobile-client-vue3/stores/modules/setting'
|
|
2
|
-
import { defineStore } from 'pinia'
|
|
3
|
-
import { ref } from 'vue'
|
|
4
|
-
|
|
5
|
-
// 验证码字符集
|
|
6
|
-
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
7
|
-
|
|
8
|
-
// 生成随机验证码
|
|
9
|
-
function generateCode(length = 4): string {
|
|
10
|
-
let code = ''
|
|
11
|
-
for (let i = 0; i < length; i++) {
|
|
12
|
-
code += CHARS.charAt(Math.floor(Math.random() * CHARS.length))
|
|
13
|
-
}
|
|
14
|
-
return code
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 用 canvas 生成验证码图片的 base64
|
|
18
|
-
function generateCaptchaImage(code: string): string {
|
|
19
|
-
try {
|
|
20
|
-
const canvas = document.createElement('canvas')
|
|
21
|
-
canvas.width = 80
|
|
22
|
-
canvas.height = 32
|
|
23
|
-
const ctx = canvas.getContext('2d')
|
|
24
|
-
if (!ctx)
|
|
25
|
-
return ''
|
|
26
|
-
|
|
27
|
-
// 随机浅色背景
|
|
28
|
-
const bgColors = ['#e8f4ff', '#f0f7ff', '#fff8e1', '#f3e5f5', '#e8f5e9']
|
|
29
|
-
ctx.fillStyle = bgColors[Math.floor(Math.random() * bgColors.length)]
|
|
30
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
31
|
-
|
|
32
|
-
// 随机文字颜色
|
|
33
|
-
const textColors = ['#1a73e8', '#e91e63', '#2e7d32', '#6a1b9a', '#1565c0', '#ad1457']
|
|
34
|
-
const textColor = textColors[Math.floor(Math.random() * textColors.length)]
|
|
35
|
-
|
|
36
|
-
// 文字
|
|
37
|
-
ctx.font = 'bold 22px Arial'
|
|
38
|
-
ctx.fillStyle = textColor
|
|
39
|
-
ctx.textAlign = 'center'
|
|
40
|
-
ctx.textBaseline = 'middle'
|
|
41
|
-
ctx.fillText(code, canvas.width / 2, canvas.height / 2)
|
|
42
|
-
|
|
43
|
-
return canvas.toDataURL('image/png')
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return ''
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const useCaptchaStore = defineStore('captcha', () => {
|
|
51
|
-
const show = ref(false)
|
|
52
|
-
const isLoading = ref(false)
|
|
53
|
-
const captchaImgBase64 = ref('')
|
|
54
|
-
const captchaUUID = ref('')
|
|
55
|
-
const captchaCode = ref('')
|
|
56
|
-
|
|
57
|
-
async function fetchCaptcha() {
|
|
58
|
-
if (isLoading.value)
|
|
59
|
-
return
|
|
60
|
-
isLoading.value = true
|
|
61
|
-
try {
|
|
62
|
-
// 未配置 captchaEnable 或显式为 true 时才显示验证码
|
|
63
|
-
const setting = useSettingStore().getSetting()
|
|
64
|
-
if (!setting?.captchaEnable) {
|
|
65
|
-
show.value = false
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 本地生成验证码
|
|
70
|
-
const code = generateCode()
|
|
71
|
-
captchaCode.value = code
|
|
72
|
-
captchaUUID.value = `local_${Date.now()}`
|
|
73
|
-
captchaImgBase64.value = generateCaptchaImage(code)
|
|
74
|
-
show.value = true
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
console.error('生成验证码失败', e)
|
|
78
|
-
show.value = false
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
isLoading.value = false
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 验证验证码
|
|
86
|
-
function verifyCaptcha(inputCode: string): boolean {
|
|
87
|
-
return captchaCode.value.toLowerCase() === inputCode.toLowerCase()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function resetCaptcha() {
|
|
91
|
-
captchaUUID.value = ''
|
|
92
|
-
captchaImgBase64.value = ''
|
|
93
|
-
captchaCode.value = ''
|
|
94
|
-
show.value = false
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
show,
|
|
99
|
-
isLoading,
|
|
100
|
-
captchaImgBase64,
|
|
101
|
-
captchaUUID,
|
|
102
|
-
fetchCaptcha,
|
|
103
|
-
verifyCaptcha,
|
|
104
|
-
resetCaptcha,
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
export default useCaptchaStore
|
|
1
|
+
import { useSettingStore } from '@af-mobile-client-vue3/stores/modules/setting'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
// 验证码字符集
|
|
6
|
+
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
7
|
+
|
|
8
|
+
// 生成随机验证码
|
|
9
|
+
function generateCode(length = 4): string {
|
|
10
|
+
let code = ''
|
|
11
|
+
for (let i = 0; i < length; i++) {
|
|
12
|
+
code += CHARS.charAt(Math.floor(Math.random() * CHARS.length))
|
|
13
|
+
}
|
|
14
|
+
return code
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 用 canvas 生成验证码图片的 base64
|
|
18
|
+
function generateCaptchaImage(code: string): string {
|
|
19
|
+
try {
|
|
20
|
+
const canvas = document.createElement('canvas')
|
|
21
|
+
canvas.width = 80
|
|
22
|
+
canvas.height = 32
|
|
23
|
+
const ctx = canvas.getContext('2d')
|
|
24
|
+
if (!ctx)
|
|
25
|
+
return ''
|
|
26
|
+
|
|
27
|
+
// 随机浅色背景
|
|
28
|
+
const bgColors = ['#e8f4ff', '#f0f7ff', '#fff8e1', '#f3e5f5', '#e8f5e9']
|
|
29
|
+
ctx.fillStyle = bgColors[Math.floor(Math.random() * bgColors.length)]
|
|
30
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
31
|
+
|
|
32
|
+
// 随机文字颜色
|
|
33
|
+
const textColors = ['#1a73e8', '#e91e63', '#2e7d32', '#6a1b9a', '#1565c0', '#ad1457']
|
|
34
|
+
const textColor = textColors[Math.floor(Math.random() * textColors.length)]
|
|
35
|
+
|
|
36
|
+
// 文字
|
|
37
|
+
ctx.font = 'bold 22px Arial'
|
|
38
|
+
ctx.fillStyle = textColor
|
|
39
|
+
ctx.textAlign = 'center'
|
|
40
|
+
ctx.textBaseline = 'middle'
|
|
41
|
+
ctx.fillText(code, canvas.width / 2, canvas.height / 2)
|
|
42
|
+
|
|
43
|
+
return canvas.toDataURL('image/png')
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return ''
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const useCaptchaStore = defineStore('captcha', () => {
|
|
51
|
+
const show = ref(false)
|
|
52
|
+
const isLoading = ref(false)
|
|
53
|
+
const captchaImgBase64 = ref('')
|
|
54
|
+
const captchaUUID = ref('')
|
|
55
|
+
const captchaCode = ref('')
|
|
56
|
+
|
|
57
|
+
async function fetchCaptcha() {
|
|
58
|
+
if (isLoading.value)
|
|
59
|
+
return
|
|
60
|
+
isLoading.value = true
|
|
61
|
+
try {
|
|
62
|
+
// 未配置 captchaEnable 或显式为 true 时才显示验证码
|
|
63
|
+
const setting = useSettingStore().getSetting()
|
|
64
|
+
if (!setting?.captchaEnable) {
|
|
65
|
+
show.value = false
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 本地生成验证码
|
|
70
|
+
const code = generateCode()
|
|
71
|
+
captchaCode.value = code
|
|
72
|
+
captchaUUID.value = `local_${Date.now()}`
|
|
73
|
+
captchaImgBase64.value = generateCaptchaImage(code)
|
|
74
|
+
show.value = true
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.error('生成验证码失败', e)
|
|
78
|
+
show.value = false
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
isLoading.value = false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 验证验证码
|
|
86
|
+
function verifyCaptcha(inputCode: string): boolean {
|
|
87
|
+
return captchaCode.value.toLowerCase() === inputCode.toLowerCase()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resetCaptcha() {
|
|
91
|
+
captchaUUID.value = ''
|
|
92
|
+
captchaImgBase64.value = ''
|
|
93
|
+
captchaCode.value = ''
|
|
94
|
+
show.value = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
show,
|
|
99
|
+
isLoading,
|
|
100
|
+
captchaImgBase64,
|
|
101
|
+
captchaUUID,
|
|
102
|
+
fetchCaptcha,
|
|
103
|
+
verifyCaptcha,
|
|
104
|
+
resetCaptcha,
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
export default useCaptchaStore
|