af-mobile-client-vue3 1.0.54
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/.editorconfig +38 -0
- package/.env +7 -0
- package/.env.development +4 -0
- package/.env.envoiceShow +7 -0
- package/.env.production +7 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.vscode/extensions.json +7 -0
- package/.vscode/settings.json +61 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/af-example-mobile-vue-web.iml +9 -0
- package/build/vite/index.ts +91 -0
- package/build/vite/vconsole.ts +44 -0
- package/eslint.config.js +7 -0
- package/index.html +17 -0
- package/mock/data.ts +20 -0
- package/mock/index.ts +5 -0
- package/mock/modules/prose.mock.ts +16 -0
- package/mock/modules/user.mock.ts +152 -0
- package/netlify.toml +12 -0
- package/package.json +107 -0
- package/public/favicon-dark.svg +4 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +4 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/safari-pinned-tab.svg +32 -0
- package/scripts/verifyCommit.js +19 -0
- package/src/App.vue +43 -0
- package/src/api/mock/index.ts +30 -0
- package/src/api/user/index.ts +40 -0
- package/src/assets/common/default-user-profile.png +0 -0
- package/src/assets/img/apps/apply-web.png +0 -0
- package/src/assets/img/apps/example-web.png +0 -0
- package/src/assets/img/apps/iot-web.png +0 -0
- package/src/assets/img/apps/linepatrol-web.png +0 -0
- package/src/assets/img/apps/monitor-web.png +0 -0
- package/src/assets/img/apps/oa-web.png +0 -0
- package/src/assets/img/apps/revenue-web.png +0 -0
- package/src/assets/img/apps/safe-check-web.png +0 -0
- package/src/assets/img/component/logo.png +0 -0
- package/src/assets/img/home/banner1.png +0 -0
- package/src/assets/img/home/banner2.png +0 -0
- package/src/assets/img/home/banner3.png +0 -0
- package/src/assets/img/home/banner4.png +0 -0
- package/src/assets/img/home/notice/icon.png +0 -0
- package/src/assets/img/user/login/background-shadow-1.svg +20 -0
- package/src/assets/img/user/login/logo-background.svg +20 -0
- package/src/assets/img/user/login/logo.png +0 -0
- package/src/assets/img/user/my/exit-login.png +0 -0
- package/src/assets/img/user/my/setting-arrow.png +0 -0
- package/src/assets/img/user/my/setting.png +0 -0
- package/src/bootstrap.ts +32 -0
- package/src/components/core/App/MicroAppView.vue +59 -0
- package/src/components/core/BeautifulLoading/index.vue +47 -0
- package/src/components/core/NavBar/index.vue +12 -0
- package/src/components/core/SvgIcon/index.vue +61 -0
- package/src/components/core/Tabbar/index.vue +38 -0
- package/src/components/core/Uploader/index.vue +104 -0
- package/src/components/core/XMultiSelect/index.vue +196 -0
- package/src/components/core/XSelect/index.vue +130 -0
- package/src/components/data/XBadge/index.vue +85 -0
- package/src/components/data/XCellDetail/index.vue +106 -0
- package/src/components/data/XCellList/index.vue +358 -0
- package/src/components/data/XCellListFilter/index.vue +392 -0
- package/src/components/data/XForm/index.vue +127 -0
- package/src/components/data/XFormItem/index.vue +472 -0
- package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -0
- package/src/components/data/XReportForm/index.vue +1058 -0
- package/src/components/layout/NormalDataLayout/index.vue +70 -0
- package/src/components/layout/TabBarLayout/index.vue +40 -0
- package/src/components.d.ts +53 -0
- package/src/enums/requestEnum.ts +25 -0
- package/src/env.d.ts +16 -0
- package/src/font-style/PingFangSC-Regular.woff2 +0 -0
- package/src/font-style/font.css +4 -0
- package/src/hooks/useCommon.ts +9 -0
- package/src/hooks/useLogin.ts +97 -0
- package/src/icons/svg/bird.svg +1 -0
- package/src/icons/svg/check-in.svg +33 -0
- package/src/icons/svg/dark.svg +5 -0
- package/src/icons/svg/github.svg +5 -0
- package/src/icons/svg/light.svg +5 -0
- package/src/icons/svg/link.svg +5 -0
- package/src/icons/svg/loadError.svg +1 -0
- package/src/icons/svg/notFound.svg +1 -0
- package/src/icons/svgo.yml +22 -0
- package/src/layout/PageLayout.vue +51 -0
- package/src/layout/SingleLayout.vue +35 -0
- package/src/locales/en-US.json +25 -0
- package/src/locales/zh-CN.json +25 -0
- package/src/main.ts +48 -0
- package/src/plugins/AppData.ts +38 -0
- package/src/plugins/GetLoginInfoService.ts +10 -0
- package/src/plugins/index.ts +11 -0
- package/src/router/README.md +8 -0
- package/src/router/guards.ts +60 -0
- package/src/router/index.ts +60 -0
- package/src/router/invoiceRoutes.ts +33 -0
- package/src/router/routes.ts +84 -0
- package/src/services/api/Login.ts +6 -0
- package/src/services/api/common.ts +98 -0
- package/src/services/api/index.ts +7 -0
- package/src/services/api/manage.ts +8 -0
- package/src/services/restTools.ts +37 -0
- package/src/settings.ts +1 -0
- package/src/stores/index.ts +7 -0
- package/src/stores/modules/cachedView.ts +31 -0
- package/src/stores/modules/counter.ts +19 -0
- package/src/stores/modules/routeTransitionName.ts +26 -0
- package/src/stores/modules/setting.ts +28 -0
- package/src/stores/modules/user.ts +180 -0
- package/src/stores/mutation-type.ts +7 -0
- package/src/styles/app.less +67 -0
- package/src/styles/login.less +81 -0
- package/src/typing.ts +3 -0
- package/src/utils/Storage.ts +124 -0
- package/src/utils/authority-utils.ts +87 -0
- package/src/utils/common.ts +41 -0
- package/src/utils/crypto.ts +39 -0
- package/src/utils/dataUtil.ts +42 -0
- package/src/utils/dictUtil.ts +51 -0
- package/src/utils/http/index.ts +158 -0
- package/src/utils/i18n.ts +41 -0
- package/src/utils/indexedDB.ts +180 -0
- package/src/utils/local-storage.ts +9 -0
- package/src/utils/mobileUtil.ts +26 -0
- package/src/utils/progress.ts +19 -0
- package/src/utils/routerUtil.ts +271 -0
- package/src/utils/set-page-title.ts +7 -0
- package/src/utils/validate.ts +6 -0
- package/src/views/chat/index.vue +153 -0
- package/src/views/common/LoadError.vue +64 -0
- package/src/views/common/NotFound.vue +68 -0
- package/src/views/component/EvaluateRecordView/index.vue +40 -0
- package/src/views/component/XCellDetailView/index.vue +216 -0
- package/src/views/component/XCellListView/index.vue +36 -0
- package/src/views/component/XFormView/index.vue +478 -0
- package/src/views/component/XReportFormIframeView/index.vue +45 -0
- package/src/views/component/XReportFormView/index.vue +295 -0
- package/src/views/component/index.vue +111 -0
- package/src/views/component/menu.vue +117 -0
- package/src/views/component/notice.vue +46 -0
- package/src/views/component/topNav.vue +36 -0
- package/src/views/invoiceShow/index.vue +62 -0
- package/src/views/user/login/ForgetPasswordForm.vue +93 -0
- package/src/views/user/login/LoginForm.vue +145 -0
- package/src/views/user/login/LoginTitle.vue +68 -0
- package/src/views/user/login/LoginWave.vue +109 -0
- package/src/views/user/login/index.vue +22 -0
- package/src/views/user/my/index.vue +230 -0
- package/src/vue-router.d.ts +9 -0
- package/tsconfig.json +43 -0
- package/uno.config.ts +32 -0
- package/vite.config.ts +110 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import SvgIcon from './components/core/SvgIcon/index.vue'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="main">
|
|
7
|
+
<div class="bird-container">
|
|
8
|
+
<SvgIcon name="bird" class="bird" />
|
|
9
|
+
</div>
|
|
10
|
+
<div class="loading-text">
|
|
11
|
+
加载中,请稍候...
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style scoped>
|
|
17
|
+
.main {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
align-items: center;
|
|
22
|
+
height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.bird-container {
|
|
26
|
+
width: 60px;
|
|
27
|
+
height: 60px;
|
|
28
|
+
animation: fade 3s ease-out infinite;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.bird {
|
|
32
|
+
width: 60px;
|
|
33
|
+
height: 60px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.loading-text {
|
|
37
|
+
margin-top: 20px;
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
color: #333;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes fade {
|
|
43
|
+
0% { opacity: 1; }
|
|
44
|
+
50% { opacity: 0.3; }
|
|
45
|
+
100% { opacity: 1; }
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { NavBar as VanNavBar } from 'vant/es'
|
|
3
|
+
|
|
4
|
+
// back
|
|
5
|
+
const onClickLeft = () => history.back()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<VanNavBar placeholder left-arrow fixed @click-left="onClickLeft" />
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<style scoped></style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { isExternal } from '@af-mobile-client-vue3/utils/validate'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
name: string
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
10
|
+
name: '',
|
|
11
|
+
className: '',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const isExternalIcon = computed(() => isExternal(props.name))
|
|
15
|
+
const iconName = computed(() => `#icon-${props.name}`)
|
|
16
|
+
const svgClass = computed(() => {
|
|
17
|
+
if (props.className)
|
|
18
|
+
return `svg-icon ${props.className}`
|
|
19
|
+
else
|
|
20
|
+
return 'svg-icon'
|
|
21
|
+
})
|
|
22
|
+
// 外链 icon
|
|
23
|
+
const styleExternalIcon = computed(() => {
|
|
24
|
+
return {
|
|
25
|
+
'mask': `url(${props.name}) no-repeat 50% 50%`,
|
|
26
|
+
'-webkit-mask': `url(${props.name}) no-repeat 50% 50%`,
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<div
|
|
33
|
+
v-if="isExternalIcon"
|
|
34
|
+
:style="styleExternalIcon"
|
|
35
|
+
class="svg-external-icon svg-icon"
|
|
36
|
+
v-bind="$attrs"
|
|
37
|
+
/>
|
|
38
|
+
<svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
|
|
39
|
+
<use :xlink:href="iconName" />
|
|
40
|
+
</svg>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<style scoped>
|
|
44
|
+
.svg-icon {
|
|
45
|
+
width: 1em;
|
|
46
|
+
height: 1em;
|
|
47
|
+
vertical-align: -0.15em;
|
|
48
|
+
fill: currentColor;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.svg-external-icon {
|
|
53
|
+
background-color: currentColor;
|
|
54
|
+
mask-size: cover !important;
|
|
55
|
+
display: inline-block;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.svg-icon-active-blue {
|
|
59
|
+
color: #1989FA
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import SvgIcon from '@af-mobile-client-vue3/components/core/SvgIcon/index.vue'
|
|
4
|
+
import { Icon as VanIcon, Tabbar as VanTabbar, TabbarItem as VanTabbarItem } from 'vant'
|
|
5
|
+
import 'vant/lib/tabbar-item/index.css'
|
|
6
|
+
import 'vant/lib/tabbar/index.css'
|
|
7
|
+
|
|
8
|
+
const Props = defineProps<{
|
|
9
|
+
tabbarData: [
|
|
10
|
+
{
|
|
11
|
+
icon: string
|
|
12
|
+
title: string
|
|
13
|
+
to: {
|
|
14
|
+
name: string
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
}>()
|
|
19
|
+
const active = ref(0)
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<VanTabbar v-model="active" :placeholder="true" :route="true" fixed>
|
|
24
|
+
<VanTabbarItem
|
|
25
|
+
v-for="(item, index) in Props.tabbarData"
|
|
26
|
+
:key="index"
|
|
27
|
+
:to="item.to"
|
|
28
|
+
>
|
|
29
|
+
<template v-if="item.icon.startsWith('svg:')" #icon="props">
|
|
30
|
+
<SvgIcon :name="item.icon.split(':')[1]" :class-name="props.active ? 'svg-icon-active-blue' : ``" />
|
|
31
|
+
</template>
|
|
32
|
+
<template v-else #icon="props">
|
|
33
|
+
<VanIcon :name="item.icon" />
|
|
34
|
+
</template>
|
|
35
|
+
{{ item.title }}
|
|
36
|
+
</VanTabbarItem>
|
|
37
|
+
</VanTabbar>
|
|
38
|
+
</template>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Uploader as vanUploader,
|
|
4
|
+
} from 'vant'
|
|
5
|
+
import { ref } from 'vue'
|
|
6
|
+
import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
imageList: Array<any>,
|
|
10
|
+
outerIndex: { default: undefined },
|
|
11
|
+
authority: { default: 'user' },
|
|
12
|
+
uploadMode: { default: 'server' },
|
|
13
|
+
})
|
|
14
|
+
const emit = defineEmits(['updateFileList'])
|
|
15
|
+
|
|
16
|
+
const imageList = ref([])
|
|
17
|
+
|
|
18
|
+
// 上传图片
|
|
19
|
+
function fileUpload(file: any): void {
|
|
20
|
+
const formData = new FormData()
|
|
21
|
+
|
|
22
|
+
formData.append('avatar', file.file)
|
|
23
|
+
formData.append('resUploadMode', props.uploadMode)
|
|
24
|
+
formData.append('pathKey', 'Default')
|
|
25
|
+
|
|
26
|
+
formData.append('formType', 'image')
|
|
27
|
+
formData.append('useType', 'Default')
|
|
28
|
+
formData.append('resUploadStock', '1')
|
|
29
|
+
formData.append('filename', file.file.name)
|
|
30
|
+
formData.append('filesize', (file.file.size / 1024 / 1024).toFixed(4))
|
|
31
|
+
formData.append('f_operator', '')
|
|
32
|
+
|
|
33
|
+
imageList.value[imageList.value.length - 1].message = '上传中...'
|
|
34
|
+
imageList.value[imageList.value.length - 1].status = 'uploading'
|
|
35
|
+
imageList.value[imageList.value.length - 1]!.name = file.file.name
|
|
36
|
+
|
|
37
|
+
upload(formData, 'af-system', { 'Content-Type': 'multipart/form-data' }).then((res: any) => {
|
|
38
|
+
if (res.success || res.id) {
|
|
39
|
+
imageList.value[imageList.value.length - 1].uid = res.data.id
|
|
40
|
+
imageList.value[imageList.value.length - 1].id = res.data.id
|
|
41
|
+
delete imageList.value[imageList.value.length - 1].message
|
|
42
|
+
imageList.value[imageList.value.length - 1].status = 'done'
|
|
43
|
+
imageList.value[imageList.value.length - 1].url = res.data.f_downloadpath
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
imageList.value[imageList.value.length - 1].status = 'failed'
|
|
47
|
+
imageList.value[imageList.value.length - 1].message = '上传失败'
|
|
48
|
+
}
|
|
49
|
+
if (props.outerIndex !== undefined)
|
|
50
|
+
emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
|
|
51
|
+
else
|
|
52
|
+
emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 删除图片
|
|
57
|
+
function deleteFileFunction(file: any) {
|
|
58
|
+
if (file.id) {
|
|
59
|
+
deleteFile({ id: file.id, f_state: '删除' }).then((res: any) => {
|
|
60
|
+
if (res.id) {
|
|
61
|
+
let target = -1
|
|
62
|
+
for (let i = 0; i < imageList.value.length; i++) {
|
|
63
|
+
if (imageList.value[i].id === Number.parseInt(res.id)) {
|
|
64
|
+
target = i
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
imageList.value.splice(target, 1)
|
|
69
|
+
if (props.outerIndex !== undefined)
|
|
70
|
+
emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
|
|
71
|
+
else
|
|
72
|
+
emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ------------------------- 初始化 -------------------------
|
|
79
|
+
initComponent()
|
|
80
|
+
|
|
81
|
+
function initComponent() {
|
|
82
|
+
if (props.imageList.length > 0)
|
|
83
|
+
imageList.value = JSON.parse(JSON.stringify(props.imageList))
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<van-uploader
|
|
89
|
+
v-if="props.authority === 'user'"
|
|
90
|
+
v-model="imageList"
|
|
91
|
+
:after-read="fileUpload"
|
|
92
|
+
:show-upload="false"
|
|
93
|
+
:deletable="false"
|
|
94
|
+
/>
|
|
95
|
+
<van-uploader
|
|
96
|
+
v-else-if="props.authority === 'admin'"
|
|
97
|
+
v-model="imageList"
|
|
98
|
+
:after-read="fileUpload"
|
|
99
|
+
:before-delete="deleteFileFunction"
|
|
100
|
+
/>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<style scoped lang="less">
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Field as VanField,
|
|
4
|
+
Picker as VanPicker,
|
|
5
|
+
Popup as VanPopup,
|
|
6
|
+
Cell as VanCell,
|
|
7
|
+
CellGroup as VanCellGroup,
|
|
8
|
+
Checkbox as VanCheckbox,
|
|
9
|
+
CheckboxGroup as VanCheckboxGroup,
|
|
10
|
+
} from 'vant'
|
|
11
|
+
import {ref,reactive,watch,computed} from 'vue'
|
|
12
|
+
const props = defineProps({
|
|
13
|
+
columns: {
|
|
14
|
+
type: Array,
|
|
15
|
+
default: function () {
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
selectValue: {
|
|
20
|
+
type: Array,
|
|
21
|
+
default: function () {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
option: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: function () {
|
|
28
|
+
return { label: 'label', value: 'value' }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
// 是否支持搜索
|
|
32
|
+
isSearch: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
default: true
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
let emits = defineEmits(['input','confirm','change','cancel'])
|
|
38
|
+
let {columns,selectValue,option,isSearch} = props
|
|
39
|
+
let show = ref(false)
|
|
40
|
+
let searchVal = ref('')
|
|
41
|
+
let columnsData = reactive(JSON.parse(JSON.stringify(columns)))
|
|
42
|
+
let checkboxValue = reactive(JSON.parse(JSON.stringify(selectValue)))
|
|
43
|
+
let checkedAll = ref(false)
|
|
44
|
+
let resultValue = reactive(JSON.parse(JSON.stringify(selectValue)))
|
|
45
|
+
let checkboxGroup = ref()
|
|
46
|
+
let checkboxes = ref()
|
|
47
|
+
function search (val) {
|
|
48
|
+
if (val) {
|
|
49
|
+
columnsData = columnsData.filter(item => {
|
|
50
|
+
return item[option.label].indexOf(val) > -1
|
|
51
|
+
})
|
|
52
|
+
} else {
|
|
53
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getData (val) {
|
|
57
|
+
const res = columnsData.filter(item => {
|
|
58
|
+
return val.indexOf(item[option.value]) > -1
|
|
59
|
+
})
|
|
60
|
+
return res
|
|
61
|
+
}
|
|
62
|
+
function onConfirm () {
|
|
63
|
+
resultValue = checkboxValue
|
|
64
|
+
show.value = !show.value
|
|
65
|
+
emits('confirm', resultValue, getData(resultValue))
|
|
66
|
+
}
|
|
67
|
+
function change (val) {
|
|
68
|
+
emits('change', val, getData(resultValue))
|
|
69
|
+
}
|
|
70
|
+
function cancel () {
|
|
71
|
+
show.value = !show.value
|
|
72
|
+
emits('cancel', resultValue)
|
|
73
|
+
}
|
|
74
|
+
const toggle = (index) => {
|
|
75
|
+
// 假设 checkboxValue 是一个对象,我们通过 index 来切换对应 checkbox 的值
|
|
76
|
+
checkboxValue.value[index] = !checkboxValue.value[index];
|
|
77
|
+
}
|
|
78
|
+
const toggleAll = () => {
|
|
79
|
+
// 遍历 checkboxValue 并设置所有 checkbox 的值为 checkedAll 的值
|
|
80
|
+
Object.keys(checkboxValue.value).forEach((key) => {
|
|
81
|
+
checkboxValue.value[key] = checkedAll.value;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function showPopu (disabled) {
|
|
85
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
86
|
+
checkboxValue = JSON.parse(JSON.stringify(selectValue))
|
|
87
|
+
resultValue = JSON.parse(JSON.stringify(selectValue))
|
|
88
|
+
if (disabled !== undefined && disabled !== false) {
|
|
89
|
+
return false
|
|
90
|
+
} else {
|
|
91
|
+
show.value = !show.value
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
watch(()=> selectValue,(newVal, oldVal)=>{
|
|
95
|
+
resultValue = newVal
|
|
96
|
+
})
|
|
97
|
+
watch(()=> resultValue,(newVal, oldVal)=>{
|
|
98
|
+
searchVal.value = ''
|
|
99
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
100
|
+
emits('input', newVal)
|
|
101
|
+
})
|
|
102
|
+
watch(()=> columnsData,(newVal, oldVal)=>{
|
|
103
|
+
if (newVal.length && newVal.length === checkboxValue.length) {
|
|
104
|
+
checkedAll.value = true
|
|
105
|
+
} else {
|
|
106
|
+
checkedAll.value = false
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
watch(()=> checkboxValue,(newVal, oldVal)=>{
|
|
110
|
+
if (newVal.length && newVal.length === columnsData.length) {
|
|
111
|
+
checkedAll.value = true
|
|
112
|
+
} else {
|
|
113
|
+
checkedAll.value = false
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
watch(checkedAll, (newVal) => {
|
|
117
|
+
toggleAll();
|
|
118
|
+
})
|
|
119
|
+
let resultLabel = computed({
|
|
120
|
+
get () {
|
|
121
|
+
const res = columns.filter(item => {
|
|
122
|
+
return resultValue.indexOf(item[this.option.value]) > -1
|
|
123
|
+
})
|
|
124
|
+
const resLabel = res.map(item => {
|
|
125
|
+
return item[this.option.label]
|
|
126
|
+
})
|
|
127
|
+
return resLabel.join(',')
|
|
128
|
+
},
|
|
129
|
+
set () {
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<div class="dh-field">
|
|
137
|
+
<div class="van-hairline--bottom">
|
|
138
|
+
<van-field
|
|
139
|
+
v-model="resultLabel"
|
|
140
|
+
v-bind="$attrs"
|
|
141
|
+
readonly
|
|
142
|
+
:is-link="$attrs.disabled === undefined"
|
|
143
|
+
error-message-align='right'
|
|
144
|
+
input-align="right"
|
|
145
|
+
class="dh-cell"
|
|
146
|
+
@click="showPopu($attrs.disabled)"
|
|
147
|
+
/>
|
|
148
|
+
<van-popup v-model="show" position="bottom" class="" >
|
|
149
|
+
<div class="van-picker__toolbar">
|
|
150
|
+
<button type="button" class="van-picker__cancel" @click="cancel">取消</button>
|
|
151
|
+
<div class="van-ellipsis van-picker__title">{{$attrs.label}}</div>
|
|
152
|
+
<button type="button" class="van-picker__confirm" @click="onConfirm">确认</button>
|
|
153
|
+
</div>
|
|
154
|
+
<div style="max-height:264px; overflow-y:auto;">
|
|
155
|
+
<van-field v-if="isSearch" v-model="searchVal" input-align="left" placeholder="搜索" @input="search"/>
|
|
156
|
+
<van-cell title="全选">
|
|
157
|
+
<template #right-icon>
|
|
158
|
+
<van-checkbox v-model="checkedAll" name="all" @click="toggleAll"/>
|
|
159
|
+
</template>
|
|
160
|
+
</van-cell>
|
|
161
|
+
<van-checkbox-group ref="checkboxGroup" v-model="checkboxValue" @change="change">
|
|
162
|
+
<van-cell-group>
|
|
163
|
+
<van-cell
|
|
164
|
+
v-for="(item, index) in columnsData"
|
|
165
|
+
:key="item[option.value]"
|
|
166
|
+
:title="item[option.label]"
|
|
167
|
+
clickable
|
|
168
|
+
@click="toggle(index)"
|
|
169
|
+
>
|
|
170
|
+
<template #right-icon>
|
|
171
|
+
<van-checkbox ref="checkboxes" :name="item[option.value]" />
|
|
172
|
+
</template>
|
|
173
|
+
</van-cell>
|
|
174
|
+
</van-cell-group>
|
|
175
|
+
</van-checkbox-group>
|
|
176
|
+
</div>
|
|
177
|
+
</van-popup>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
181
|
+
|
|
182
|
+
<style lang="scss" scoped>
|
|
183
|
+
.dh-field {
|
|
184
|
+
padding: 0;
|
|
185
|
+
background:#fff;
|
|
186
|
+
.dh-cell.van-cell {
|
|
187
|
+
padding: 10px 0;
|
|
188
|
+
}
|
|
189
|
+
.dh-cell.van-cell--required::before {
|
|
190
|
+
left: -8px;
|
|
191
|
+
}
|
|
192
|
+
.van-popup {
|
|
193
|
+
border-radius: 20px 20px 0 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Field as VanField,
|
|
4
|
+
Picker as VanPicker,
|
|
5
|
+
Popup as VanPopup,
|
|
6
|
+
} from 'vant'
|
|
7
|
+
import {watch,computed,ref,reactive} from 'vue'
|
|
8
|
+
const emits = defineEmits(['confirm','change','cancel','input'])
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
columns: {
|
|
11
|
+
type: Array,
|
|
12
|
+
default: function () {
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
selectValue: {
|
|
17
|
+
type: [String, Number],
|
|
18
|
+
default: ''
|
|
19
|
+
},
|
|
20
|
+
option: {
|
|
21
|
+
type: Object,
|
|
22
|
+
default: function () {
|
|
23
|
+
return { label: 'label', value: 'value' }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
isSearch: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
default: false
|
|
29
|
+
},
|
|
30
|
+
offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
let {columns,selectValue,option,isSearch,offOption} = props
|
|
36
|
+
let show = ref(false)
|
|
37
|
+
let searchVal = ref('')
|
|
38
|
+
let resultValue = ref('')
|
|
39
|
+
let columnsData = reactive([])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
let resultLabel = computed({
|
|
43
|
+
get () {
|
|
44
|
+
const res = columns.filter(item => {
|
|
45
|
+
const data = offOption ? item : item[option.value]
|
|
46
|
+
return data === resultValue.value
|
|
47
|
+
})
|
|
48
|
+
let label = ''
|
|
49
|
+
if (res.length) {
|
|
50
|
+
label = offOption ? res[0] : res[0][option.label]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return label
|
|
54
|
+
},
|
|
55
|
+
set () {
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
function search (val) {
|
|
60
|
+
if (val) {
|
|
61
|
+
columnsData = columnsData.filter(item => {
|
|
62
|
+
const data = offOption ? item : item[option.label]
|
|
63
|
+
|
|
64
|
+
return data.indexOf(val) > -1
|
|
65
|
+
})
|
|
66
|
+
} else {
|
|
67
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function onConfirm (value, index) {
|
|
71
|
+
const data = offOption ? value : value[option.value]
|
|
72
|
+
|
|
73
|
+
resultValue.value = data
|
|
74
|
+
show.value = !show.value
|
|
75
|
+
emits('confirm', value, index, resultValue.value)
|
|
76
|
+
}
|
|
77
|
+
function change (val, index) {
|
|
78
|
+
emits('change', val, index, resultValue.value)
|
|
79
|
+
}
|
|
80
|
+
function cancel (val, index) {
|
|
81
|
+
show.value = !show.value
|
|
82
|
+
emits('cancel', val, index, resultValue.value)
|
|
83
|
+
}
|
|
84
|
+
function showPopu (disabled) {
|
|
85
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
86
|
+
resultValue.value = selectValue + ''
|
|
87
|
+
if (disabled !== undefined && disabled !== false) {
|
|
88
|
+
return false
|
|
89
|
+
} else {
|
|
90
|
+
show.value = !show.value
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
watch(()=> selectValue,(newVal, oldVal)=>{
|
|
95
|
+
resultValue.value = newVal + ''
|
|
96
|
+
})
|
|
97
|
+
watch(()=> resultValue, (newVal, oldVal)=>{
|
|
98
|
+
searchVal.value = ''
|
|
99
|
+
columnsData = JSON.parse(JSON.stringify(columns))
|
|
100
|
+
emits('input', newVal)
|
|
101
|
+
})
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<van-field
|
|
106
|
+
v-model="resultLabel"
|
|
107
|
+
v-bind="$attrs"
|
|
108
|
+
readonly
|
|
109
|
+
:is-link="true"
|
|
110
|
+
error-message-align='right'
|
|
111
|
+
@click="showPopu($attrs.disabled)"
|
|
112
|
+
/>
|
|
113
|
+
<van-popup v-model="show" position="bottom">
|
|
114
|
+
<van-field v-if="isSearch" v-model="searchVal" input-align="left" placeholder="搜索" @input="search"/>
|
|
115
|
+
<van-picker
|
|
116
|
+
v-bind="$attrs"
|
|
117
|
+
:columns="columnsData"
|
|
118
|
+
show-toolbar
|
|
119
|
+
@cancel="cancel"
|
|
120
|
+
@confirm="onConfirm"
|
|
121
|
+
@change="change"
|
|
122
|
+
:value-key="option.label"
|
|
123
|
+
/>
|
|
124
|
+
</van-popup>
|
|
125
|
+
</template>
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
<style lang="scss" scoped>
|
|
129
|
+
|
|
130
|
+
</style>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { getDictItemByValue } from '@af-mobile-client-vue3/utils/dictUtil'
|
|
4
|
+
|
|
5
|
+
const { dictName, dictValue } = withDefaults(defineProps<{
|
|
6
|
+
dictName?: string
|
|
7
|
+
dictValue: any
|
|
8
|
+
isColor?: boolean
|
|
9
|
+
}>(), {
|
|
10
|
+
dictName: '',
|
|
11
|
+
isColor: true,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// 字面值
|
|
15
|
+
const label = ref('')
|
|
16
|
+
|
|
17
|
+
// 样式类名
|
|
18
|
+
const className = ref('')
|
|
19
|
+
|
|
20
|
+
initComponent()
|
|
21
|
+
|
|
22
|
+
// 组件初始化
|
|
23
|
+
function initComponent() {
|
|
24
|
+
if (!dictValue) {
|
|
25
|
+
label.value = '-'
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
if (!dictName || dictName === '') {
|
|
29
|
+
label.value = dictValue.toString()
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
getDictItemByValue(dictName, dictValue, (result) => {
|
|
33
|
+
if (result == null) {
|
|
34
|
+
label.value = dictValue.toString()
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
label.value = result.label
|
|
38
|
+
className.value = `badge-${result.status}`
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<span :class="className !== '' ? className : undefined">{{ label }}</span>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<style scoped lang="less">
|
|
49
|
+
.badge-blue {
|
|
50
|
+
color: #1890ff;
|
|
51
|
+
}
|
|
52
|
+
.badge-green {
|
|
53
|
+
color: #52c41a;
|
|
54
|
+
}
|
|
55
|
+
.badge-orange {
|
|
56
|
+
color: #fa8c16;
|
|
57
|
+
}
|
|
58
|
+
.badge-cyan {
|
|
59
|
+
color: #13c2c2;
|
|
60
|
+
}
|
|
61
|
+
.badge-yellow {
|
|
62
|
+
color: #fadb14;
|
|
63
|
+
}
|
|
64
|
+
.badge-red {
|
|
65
|
+
color: #f5222d;
|
|
66
|
+
}
|
|
67
|
+
.badge-pink {
|
|
68
|
+
color: #eb2f96;
|
|
69
|
+
}
|
|
70
|
+
.badge-purple {
|
|
71
|
+
color: #722ed1;
|
|
72
|
+
}
|
|
73
|
+
.badge-geekblue {
|
|
74
|
+
color: #2f54eb;
|
|
75
|
+
}
|
|
76
|
+
.badge-volcano {
|
|
77
|
+
color: #fa541c;
|
|
78
|
+
}
|
|
79
|
+
.badge-gold {
|
|
80
|
+
color: #faad14;
|
|
81
|
+
}
|
|
82
|
+
.badge-lime {
|
|
83
|
+
color: #a0d911;
|
|
84
|
+
}
|
|
85
|
+
</style>
|