@gindow/vant-go 1.0.0
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/README.md +157 -0
- package/dist/resolver.cjs +1 -0
- package/dist/resolver.d.ts +1 -0
- package/dist/resolver.mjs +16 -0
- package/dist/style.css +2 -0
- package/dist/vant-go.cjs +1 -0
- package/dist/vant-go.d.ts +1 -0
- package/dist/vant-go.mjs +1059 -0
- package/package.json +57 -0
- package/src/assets/avatar.png +0 -0
- package/src/components/VaxAssetPreview.vue +38 -0
- package/src/components/VaxAvatar.vue +16 -0
- package/src/components/VaxButton.vue +21 -0
- package/src/components/VaxCaptcha.vue +60 -0
- package/src/components/VaxCell.vue +35 -0
- package/src/components/VaxCellGroup.vue +50 -0
- package/src/components/VaxEmpty.vue +18 -0
- package/src/components/VaxField.vue +69 -0
- package/src/components/VaxIcon.vue +31 -0
- package/src/components/VaxLoading.vue +32 -0
- package/src/components/VaxNavBar.vue +28 -0
- package/src/components/VaxPage.vue +9 -0
- package/src/components/VaxSelect.vue +78 -0
- package/src/components/VaxTabbar.vue +49 -0
- package/src/components/VaxTabs.vue +66 -0
- package/src/env.d.ts +7 -0
- package/src/hooks/useCaptcha.ts +27 -0
- package/src/hooks/useMessage.ts +19 -0
- package/src/index.ts +73 -0
- package/src/libs/auto-imports.d.ts +84 -0
- package/src/libs/components.d.ts +51 -0
- package/src/locale/en-US.ts +24 -0
- package/src/locale/index.ts +65 -0
- package/src/locale/zh-CN.ts +24 -0
- package/src/resolver.ts +26 -0
- package/src/style.css +46 -0
- package/src/types/index.ts +15 -0
- package/src/utils/validate.ts +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gindow/vant-go",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "基于 Vant 的移动端扩展组件库",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/vant-go.cjs",
|
|
8
|
+
"types": "./dist/vant-go.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/vant-go.d.ts",
|
|
12
|
+
"import": "./dist/vant-go.mjs",
|
|
13
|
+
"require": "./dist/vant-go.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./resolver": {
|
|
16
|
+
"types": "./dist/resolver.d.ts",
|
|
17
|
+
"import": "./dist/resolver.mjs",
|
|
18
|
+
"require": "./dist/resolver.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./style.css": "./dist/style.css",
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": [
|
|
30
|
+
"**/*.css"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "vue-tsc --noEmit && vite build"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@gindow/vue": "^1.0.3",
|
|
37
|
+
"@iconify/vue": "^5.0.0",
|
|
38
|
+
"vant": "^4.9.24",
|
|
39
|
+
"vue": "^3.5.0",
|
|
40
|
+
"vue-router": "^4.0.0 || ^5.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@iconify/vue": "^5.0.1",
|
|
44
|
+
"@types/node": "^22.20.0",
|
|
45
|
+
"@vitejs/plugin-vue": "^6.0.7",
|
|
46
|
+
"@vue/tsconfig": "^0.9.1",
|
|
47
|
+
"typescript": "^6.0.3",
|
|
48
|
+
"unplugin-auto-import": "^21.0.0",
|
|
49
|
+
"unplugin-vue-components": "^32.1.0",
|
|
50
|
+
"vant": "^4.9.24",
|
|
51
|
+
"vite": "^8.1.0",
|
|
52
|
+
"vite-plugin-dts": "^4.5.4",
|
|
53
|
+
"vue": "^3.5.38",
|
|
54
|
+
"vue-router": "^5.1.0",
|
|
55
|
+
"vue-tsc": "^3.3.5"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-image-preview v-model:show="show" :images="assetUrls" :start-position="initialIndex" @change="onChange">
|
|
3
|
+
<template #image="{ src }">
|
|
4
|
+
<van-image fit="contain" :src="src" class="w-full">
|
|
5
|
+
<template #error>
|
|
6
|
+
<div class="flex flex-col justify-center items-center">
|
|
7
|
+
<vax-icon icon="file-question" :size="40" color="#093" />
|
|
8
|
+
<div>{{ currentAsset?.title }}</div>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
</van-image>
|
|
12
|
+
</template>
|
|
13
|
+
</van-image-preview>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import type { PropType } from 'vue'
|
|
18
|
+
import { computed, ref } from 'vue'
|
|
19
|
+
import type { IAsset } from '../types'
|
|
20
|
+
|
|
21
|
+
const show = defineModel('show', { type: Boolean, default: false })
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
asset: { type: Object as PropType<IAsset>, default: () => ({}) },
|
|
25
|
+
assets: { type: Array as PropType<IAsset[]>, default: () => [] },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const assetUrls = computed(() => props.assets.map(item => item.shrink ?? item?.url))
|
|
29
|
+
|
|
30
|
+
const initialIndex = computed(() => props.assets.findIndex(item => item.id === props.asset?.id) ?? 0)
|
|
31
|
+
const index = ref(initialIndex.value === -1 ? 0 : initialIndex.value)
|
|
32
|
+
const currentAsset = computed(() => props.assets[index.value])
|
|
33
|
+
|
|
34
|
+
const onChange = (value: number) => index.value = value
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style scoped>
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-image :src :round :width="pixel" :height="pixel" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import avatar from '../assets/avatar.png'
|
|
7
|
+
|
|
8
|
+
const { size, src } = defineProps({
|
|
9
|
+
src: { type: String, default: avatar },
|
|
10
|
+
size: { type: [String, Number], default: 'normal' },
|
|
11
|
+
round: { type: Boolean, default: true },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const sizes = { small: 24, normal: 48, large: 72 }
|
|
15
|
+
const pixel = computed(() => typeof size === 'number' ? size : (sizes[size as keyof typeof sizes] || sizes.normal))
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-button v-bind="$attrs">
|
|
3
|
+
<span class="flex-center gap-2">
|
|
4
|
+
<vax-icon v-if="icon" :icon :size :color :strokeWidth />
|
|
5
|
+
<slot />
|
|
6
|
+
</span>
|
|
7
|
+
</van-button>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
icon: { type: String, default: '' },
|
|
13
|
+
iconSize: { type: Number, default: 14 },
|
|
14
|
+
iconColor: { type: String, default: '' },
|
|
15
|
+
iconStrokeWidth: { type: Number },
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const size = computed(() => props.iconSize)
|
|
19
|
+
const color = computed(() => props.iconColor)
|
|
20
|
+
const strokeWidth = computed(() => props.iconStrokeWidth)
|
|
21
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-field type="tel" v-model="phone" name="phone" :label="label ? t('auth.phone') : ''" :placeholder="t('auth.enterPhone')" center required clearable :rules="[{ required: true }]">
|
|
3
|
+
<template #button>
|
|
4
|
+
<van-button class="captcha" type="primary" :disabled="!!waiting" @click.stop="getCaptcha">{{ waiting > 0 ? waiting : t('auth.getOTP') }}</van-button>
|
|
5
|
+
</template>
|
|
6
|
+
</van-field>
|
|
7
|
+
<van-field type="number" v-model="captcha" :label="label ? t('auth.otp') : ''" :placeholder="t('auth.enterOTP')" center required clearable :rules="[{ required: true }]" />
|
|
8
|
+
<van-action-sheet v-model:show="show" :actions :cancel-text="t('cancel')" @select="select" />
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { useLocale } from '../locale'
|
|
13
|
+
import { Validate } from '../utils/validate'
|
|
14
|
+
import { useCaptcha } from '../hooks/useCaptcha'
|
|
15
|
+
import { useMessage } from '../hooks/useMessage'
|
|
16
|
+
|
|
17
|
+
const { method, whatsApp, onSend } = defineProps({
|
|
18
|
+
method: { type: String, default: '' },
|
|
19
|
+
label: { type: Boolean, default: false }, // 是否显示 Label
|
|
20
|
+
whatsApp: { type: Boolean, default: false }, // 是否使用 WhatsApp
|
|
21
|
+
onSend: { type: Function as PropType<(data: any) => Promise<void>>, required: true }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const phone = defineModel('phone', { type: String, required: true })
|
|
25
|
+
const captcha = defineModel('captcha', { type: String, required: true })
|
|
26
|
+
|
|
27
|
+
const { t } = useLocale()
|
|
28
|
+
const { success, error } = useMessage()
|
|
29
|
+
const { waiting, send } = useCaptcha(onSend)
|
|
30
|
+
|
|
31
|
+
const channel = ref('sms')
|
|
32
|
+
const sent = ref(false)
|
|
33
|
+
const show = ref(false)
|
|
34
|
+
const actions = computed(() => [
|
|
35
|
+
{ name: t('auth.viaWhatsApp'), value: 'whatsapp' },
|
|
36
|
+
{ name: t('auth.viaPhone'), value: 'sms' },
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
function sendOTP() {
|
|
40
|
+
return send({ method, phone: phone.value, channel: channel.value }).then(() => {
|
|
41
|
+
captcha.value = ''
|
|
42
|
+
sent.value = true
|
|
43
|
+
success(t('message.otpSent'))
|
|
44
|
+
}).catch(({ message }: { message: string }) => error(message))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 获取验证码
|
|
48
|
+
function getCaptcha() {
|
|
49
|
+
if (!Validate.phone(phone.value)) return error(t('message.phoneInvalid'))
|
|
50
|
+
if (whatsApp) show.value = true
|
|
51
|
+
else sendOTP()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 选择通道
|
|
55
|
+
function select({ value }: { value: string }) {
|
|
56
|
+
channel.value = value
|
|
57
|
+
show.value = false
|
|
58
|
+
sendOTP()
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-cell center :title :value :label :to :titleClass :valueClass :labelClass :isLink @click="onClick">
|
|
3
|
+
<template v-if="$slots.icon || icon" #icon>
|
|
4
|
+
<slot name="icon">
|
|
5
|
+
<vax-icon v-if="icon" :icon
|
|
6
|
+
:size="iconSize" :color="iconColor"
|
|
7
|
+
:background="iconBackground" :stroke-width="iconStrokeWidth" />
|
|
8
|
+
</slot>
|
|
9
|
+
</template>
|
|
10
|
+
<template v-if="$slots.title" #title><slot name="title" /></template>
|
|
11
|
+
<template v-if="$slots.label" #label><slot name="label" /></template>
|
|
12
|
+
<template v-if="$slots.value" #value><slot name="value" /></template>
|
|
13
|
+
</van-cell>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
icon: { type: String },
|
|
19
|
+
iconSize: { type: Number },
|
|
20
|
+
iconColor: { type: String },
|
|
21
|
+
iconBackground: { type: String },
|
|
22
|
+
iconStrokeWidth: { type: Number },
|
|
23
|
+
title: { type: String },
|
|
24
|
+
label: { type: String },
|
|
25
|
+
value: { type: [String, Number] },
|
|
26
|
+
titleClass: { type: String },
|
|
27
|
+
valueClass: { type: String },
|
|
28
|
+
labelClass: { type: String },
|
|
29
|
+
isLink: { type: Boolean },
|
|
30
|
+
to: { type: String },
|
|
31
|
+
onClick: { type: Function as PropType<() => void> },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const isLink = computed(() => props.isLink || !! props.to)
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-cell-group class="vax-card menu" :class="{ compact }" :title>
|
|
3
|
+
<template v-if="$slots.title" #title><slot name="title" /></template>
|
|
4
|
+
<slot />
|
|
5
|
+
<vax-cell v-for="(item, key) in items" :key v-bind="item"
|
|
6
|
+
:is-link="item.isLink ?? isLink"
|
|
7
|
+
:title-class="item.titleClass ?? titleClass"
|
|
8
|
+
:value-class="item.valueClass ?? valueClass"
|
|
9
|
+
:label-class="item.labelClass ?? labelClass"
|
|
10
|
+
:icon-size="item.iconSize ?? iconSize"
|
|
11
|
+
:icon-color="item.iconColor ?? iconColor"
|
|
12
|
+
:icon-background="item.iconBackground ?? iconBackground"
|
|
13
|
+
:icon-stroke-width="item.iconStrokeWidth ?? iconStrokeWidth" />
|
|
14
|
+
</van-cell-group>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import vaxCell from './VaxCell.vue'
|
|
19
|
+
|
|
20
|
+
interface IVaxCardItem {
|
|
21
|
+
icon?: string
|
|
22
|
+
iconSize?: number
|
|
23
|
+
iconColor?: string
|
|
24
|
+
iconBackground?: string
|
|
25
|
+
iconStrokeWidth?: number
|
|
26
|
+
title: string
|
|
27
|
+
value?: string | number
|
|
28
|
+
label?: string
|
|
29
|
+
to?: string
|
|
30
|
+
titleClass?: string
|
|
31
|
+
valueClass?: string
|
|
32
|
+
labelClass?: string
|
|
33
|
+
isLink?: boolean
|
|
34
|
+
onClick?: () => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
defineProps({
|
|
38
|
+
title: { type: String, default: '' },
|
|
39
|
+
iconSize: { type: Number },
|
|
40
|
+
iconColor: { type: String },
|
|
41
|
+
iconBackground: { type: String },
|
|
42
|
+
iconStrokeWidth: { type: Number },
|
|
43
|
+
items: { type: Array as PropType<IVaxCardItem[]>, default: () => [] },
|
|
44
|
+
titleClass: { type: String },
|
|
45
|
+
valueClass: { type: String },
|
|
46
|
+
labelClass: { type: String },
|
|
47
|
+
isLink: { type: Boolean },
|
|
48
|
+
compact: { type: Boolean, default: false }
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-empty :image :imageSize :description>
|
|
3
|
+
<slot />
|
|
4
|
+
</van-empty>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { useLocale } from '../locale'
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
image: { type: String, default: 'default' },
|
|
12
|
+
imageSize: { type: Number },
|
|
13
|
+
description: { type: String },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const { t } = useLocale()
|
|
17
|
+
const description = computed(() => props.description ?? t('empty.noData'))
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-cell :title="label" :value center is-link @click="edit">
|
|
3
|
+
<template #icon>
|
|
4
|
+
<vax-icon :icon class="mr-2" />
|
|
5
|
+
</template>
|
|
6
|
+
</van-cell>
|
|
7
|
+
<!-- Text -->
|
|
8
|
+
<van-dialog v-model:show="show" :title="label" show-cancel-button @confirm="confirm" @cancel="cancel">
|
|
9
|
+
<van-field v-model="field" :type="(type as FieldType)" />
|
|
10
|
+
</van-dialog>
|
|
11
|
+
<!-- Date -->
|
|
12
|
+
<van-popup v-model:show="showData" position="bottom">
|
|
13
|
+
<van-date-picker :model-value="dates" :title="label" @confirm="dateConfirm" @cancel="showData = false" />
|
|
14
|
+
</van-popup>
|
|
15
|
+
<!-- Select -->
|
|
16
|
+
<van-popup v-model:show="showSelect" position="bottom">
|
|
17
|
+
<van-picker :columns :title="label" @confirm="pickerConfirm" @cancel="showSelect = false" />
|
|
18
|
+
</van-popup>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import type { FieldType } from 'vant'
|
|
23
|
+
|
|
24
|
+
const { readonly, type, columns } = defineProps({
|
|
25
|
+
icon: { type: String },
|
|
26
|
+
label: { type: String },
|
|
27
|
+
type: { type: String, default: 'text' },
|
|
28
|
+
readonly: { type: Boolean, default: false },
|
|
29
|
+
columns: { type: Array as PropType<{ text: string; value: string }[]>, default: () => [] }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits(['update:modelValue'])
|
|
33
|
+
|
|
34
|
+
const model = defineModel<string | number>()
|
|
35
|
+
const value = computed(() => {
|
|
36
|
+
if (type === 'select') return columns.find(item => item.value === model.value)?.text || ''
|
|
37
|
+
else return model.value
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Text
|
|
41
|
+
const show = ref(false)
|
|
42
|
+
const field = ref(model.value)
|
|
43
|
+
const cancel = () => field.value = model.value
|
|
44
|
+
const confirm = () => model.value = field.value
|
|
45
|
+
const edit = () => {
|
|
46
|
+
if (readonly) return
|
|
47
|
+
else if (type === 'date') showData.value = true
|
|
48
|
+
else if (type === 'select') showSelect.value = true
|
|
49
|
+
else show.value = true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Data
|
|
53
|
+
const showData = ref(false)
|
|
54
|
+
const dates = computed(() => {
|
|
55
|
+
const date = new Date()
|
|
56
|
+
return [ String(date.getFullYear()), String(date.getMonth() + 1), String(date.getDate()) ]
|
|
57
|
+
})
|
|
58
|
+
const dateConfirm = ({ selectedValues }: { selectedValues: string[] }) => {
|
|
59
|
+
model.value = selectedValues.join('-')
|
|
60
|
+
showData.value = false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Picker
|
|
64
|
+
const showSelect = ref(false)
|
|
65
|
+
const pickerConfirm = ({ selectedValues }: { selectedValues: string[] }) => {
|
|
66
|
+
model.value = selectedValues[0]
|
|
67
|
+
showSelect.value = false
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span v-if="icon" class="i-icon inline-flex items-center gap-1" :style="{ background, color, padding }" :class="{ 'rounded-full': round }">
|
|
3
|
+
<Icon :icon="name" :width="size" :height="size" :aria-hidden="null" />
|
|
4
|
+
<slot />
|
|
5
|
+
</span>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { Icon } from '@iconify/vue'
|
|
10
|
+
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
vendor: { type: String, default: 'icon-park-outline' },
|
|
13
|
+
icon: { type: String, default: '' },
|
|
14
|
+
size: { type: Number, default: 16 },
|
|
15
|
+
strokeWidth: { type: Number, default: 3 },
|
|
16
|
+
color: { type: String },
|
|
17
|
+
background: { type: String },
|
|
18
|
+
padding: { type: String },
|
|
19
|
+
round: { type: Boolean },
|
|
20
|
+
disabled: { type: Boolean },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const name = computed(() => props.icon.indexOf(':') !== -1 ? props.icon : `${props.vendor}:${props.icon}`)
|
|
24
|
+
const color = computed(() => props.disabled ? 'var(--van-text-color-3)' : props.color || (props.background ? '#FFF' : undefined))
|
|
25
|
+
const round = props.round || (props.background ? true : false)
|
|
26
|
+
const padding = props.padding ? parseInt(props.padding) + 'px' : (props.background ? '8px' : '0')
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style scoped>
|
|
30
|
+
:deep(g), :deep(path) { stroke-width: v-bind(strokeWidth) }
|
|
31
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body" :disabled="!fullscreen">
|
|
3
|
+
<div class="vax-loading flex-center p-4" :class="{ 'vax-loading--fullscreen': fullscreen }">
|
|
4
|
+
<van-loading :type :size :color :vertical>
|
|
5
|
+
<slot>{{ text }}</slot>
|
|
6
|
+
</van-loading>
|
|
7
|
+
</div>
|
|
8
|
+
</Teleport>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import type { LoadingType } from 'vant'
|
|
13
|
+
|
|
14
|
+
defineProps({
|
|
15
|
+
type: { type: String as PropType<LoadingType>, default: 'circular' },
|
|
16
|
+
size: { type: Number, default: 48 },
|
|
17
|
+
color: { type: String, default: '#ccc' },
|
|
18
|
+
vertical: { type: Boolean, default: false },
|
|
19
|
+
text: { type: String, default: '' },
|
|
20
|
+
fullscreen: { type: Boolean, default: false },
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.vax-loading--fullscreen {
|
|
26
|
+
position: fixed;
|
|
27
|
+
inset: 0;
|
|
28
|
+
z-index: 2000;
|
|
29
|
+
padding: 0;
|
|
30
|
+
background: rgba(0, 0, 0, 0.45);
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-nav-bar :leftArrow :leftText @click-left="onClickLeft">
|
|
3
|
+
<template v-if="$slots.right" #right>
|
|
4
|
+
<slot name="right"></slot>
|
|
5
|
+
</template>
|
|
6
|
+
</van-nav-bar>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
back: { type: Boolean, default: false },
|
|
12
|
+
leftArrow: { type: Boolean, default: false },
|
|
13
|
+
leftText: { type: String, default: '' },
|
|
14
|
+
clickLeft: { type: Function, default: () => {} },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const textAlign = computed(() => props.leftText ? 'center' : 'left')
|
|
18
|
+
const leftArrow = computed(() => props.leftArrow || props.back)
|
|
19
|
+
const paddingLeft = computed(() => props.leftText || leftArrow.value ? '0' : '16px')
|
|
20
|
+
|
|
21
|
+
const router = useRouter()
|
|
22
|
+
const onClickLeft = () => props.back ? router.back() : props.clickLeft()
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.van-nav-bar :deep(.van-nav-bar__content > div) { position: static; }
|
|
27
|
+
.van-nav-bar :deep(.van-nav-bar__title) { flex: 1; max-width: 100%; text-align: v-bind(textAlign); padding-left: v-bind(paddingLeft); }
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-field v-bind="$attrs" :model-value="value" colon readonly is-link @click="onShow">
|
|
3
|
+
<template #input>
|
|
4
|
+
<span v-if="!multiple">{{ model }}</span>
|
|
5
|
+
<van-space v-else>
|
|
6
|
+
<van-tag v-for="item in (model as string[])" plain type="primary">{{ item }}</van-tag>
|
|
7
|
+
</van-space>
|
|
8
|
+
</template>
|
|
9
|
+
</van-field>
|
|
10
|
+
<van-popup v-model:show="show" destroy-on-close position="bottom" round>
|
|
11
|
+
<!-- 单选 -->
|
|
12
|
+
<van-picker v-if="!multiple" v-model="pickerValue" :columns="columns" @confirm="onConfirm" @cancel="show = false" />
|
|
13
|
+
<!-- 多选 -->
|
|
14
|
+
<template v-else>
|
|
15
|
+
<div class="van-picker__toolbar">
|
|
16
|
+
<van-button class="border-none! bg-white!" @click="show = false">{{ t('cancel') }}</van-button>
|
|
17
|
+
<van-button class="border-none! bg-white!" plain type="primary" @click="confirm">{{ t('confirm') }}</van-button>
|
|
18
|
+
</div>
|
|
19
|
+
<van-checkbox-group v-model="checkboxValue" direction="vertical" class="h-[264px] overflow-auto">
|
|
20
|
+
<van-cell-group>
|
|
21
|
+
<van-cell
|
|
22
|
+
v-for="(item, index) in options"
|
|
23
|
+
clickable
|
|
24
|
+
:title="item"
|
|
25
|
+
@click="toggle(index)">
|
|
26
|
+
<template #right-icon>
|
|
27
|
+
<van-checkbox :ref="(el) => checkboxRefs[index] = el" :name="item" @click.stop />
|
|
28
|
+
</template>
|
|
29
|
+
</van-cell>
|
|
30
|
+
</van-cell-group>
|
|
31
|
+
</van-checkbox-group>
|
|
32
|
+
</template>
|
|
33
|
+
</van-popup>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type { PropType } from 'vue'
|
|
38
|
+
import type { CheckboxInstance } from 'vant'
|
|
39
|
+
import { computed, ref } from 'vue'
|
|
40
|
+
import { useLocale } from '../locale'
|
|
41
|
+
|
|
42
|
+
const model = defineModel({ type: [String, Number, Array] as PropType<string | number | number[] | string[]> })
|
|
43
|
+
const value = computed(() => multiple ? Array.isArray(model.value) ? model.value.join(',') : '' : model.value as string | number)
|
|
44
|
+
|
|
45
|
+
const { multiple, options } = defineProps({
|
|
46
|
+
multiple: { type: Boolean, default: false },
|
|
47
|
+
options: { type: Array as PropType<string[]>, default: () => [] },
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const { t } = useLocale()
|
|
51
|
+
|
|
52
|
+
const show = ref(false)
|
|
53
|
+
const onShow = () => {
|
|
54
|
+
if (multiple) {
|
|
55
|
+
checkboxValue.value = (model.value as string[]) ?? []
|
|
56
|
+
} else {
|
|
57
|
+
pickerValue.value = model.value ? [model.value] as string[] : []
|
|
58
|
+
}
|
|
59
|
+
show.value = true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 单选
|
|
63
|
+
const pickerValue = ref<string[] | number[]>([])
|
|
64
|
+
const columns = computed(() => options?.map((value) => ({ text: value, value })) || [])
|
|
65
|
+
const onConfirm = ({ selectedValues }: { selectedValues: string[] }) => {
|
|
66
|
+
show.value = false
|
|
67
|
+
model.value = selectedValues[0]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 多选
|
|
71
|
+
const checkboxValue = ref<string[] | number[]>([])
|
|
72
|
+
const checkboxRefs = ref<(CheckboxInstance | any)[]>([])
|
|
73
|
+
const toggle = (index: number) => checkboxRefs.value[index]?.toggle()
|
|
74
|
+
const confirm = () => {
|
|
75
|
+
show.value = false
|
|
76
|
+
model.value = checkboxValue.value
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<van-tabbar v-model="active" placeholder safe-area-inset-bottom>
|
|
3
|
+
<van-tabbar-item v-for="{ to, name, icon, title, flashed, dot } in data" replace :to :name :dot>
|
|
4
|
+
<template v-if="!flashed" #icon>
|
|
5
|
+
<vax-icon :icon :size />
|
|
6
|
+
</template>
|
|
7
|
+
<div v-if="!flash || flashed" :class="{ flashed }" class="mt-1.5 font-bold">{{ title }}</div>
|
|
8
|
+
</van-tabbar-item>
|
|
9
|
+
</van-tabbar>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { Tabbar as vanTabbar, TabbarItem as vanTabbarItem } from 'vant'
|
|
14
|
+
import VaxIcon from './VaxIcon.vue'
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
menu: { type: Array<{ title?: string, icon: string, to: string, dot?: boolean }>, required: true, default: () => ([]) },
|
|
18
|
+
flash: { type: Boolean, default: false },
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const data = computed(() => props.menu.map(item => ({
|
|
22
|
+
title: item.title,
|
|
23
|
+
to: item.to,
|
|
24
|
+
name: item.to,
|
|
25
|
+
icon: item.icon,
|
|
26
|
+
flashed: props.flash && item.title && active.value === item.to,
|
|
27
|
+
dot: item.dot,
|
|
28
|
+
})))
|
|
29
|
+
|
|
30
|
+
// 当前路由
|
|
31
|
+
const route = useRoute()
|
|
32
|
+
const routePath = computed(() => {
|
|
33
|
+
let routePath = ''
|
|
34
|
+
props.menu.forEach(({ to }) => {
|
|
35
|
+
if ((route.path + '/').indexOf(to) !== -1 && to.length > routePath.length) routePath = to
|
|
36
|
+
})
|
|
37
|
+
return routePath
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const active = ref(routePath.value)
|
|
41
|
+
const size = computed(() => props.flash ? 22 : 20)
|
|
42
|
+
|
|
43
|
+
watchEffect(() => active.value = routePath.value)
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style scoped>
|
|
47
|
+
.van-tabbar .van-tabbar-item { --van-tabbar-item-icon-margin-bottom: 0 }
|
|
48
|
+
.van-tabbar .flashed { margin-top: 0; font-size: 14px; }
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vax-tabs" :class="{ 'vax-tabs--sticky': sticky }">
|
|
3
|
+
<button v-for="{ name, label, badge } in tabs" :key="name" type="button" class="vax-tab" :class="{ active: name === active }" @click="onPick(name)">
|
|
4
|
+
<van-badge :content="badge" class="badge">{{ label }}</van-badge>
|
|
5
|
+
</button>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
export interface IVaxTab {
|
|
11
|
+
name: string
|
|
12
|
+
label: string
|
|
13
|
+
badge?: number | string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { tabs } = defineProps({
|
|
17
|
+
tabs: { type: Array as () => IVaxTab[], required: true },
|
|
18
|
+
sticky: { type: Boolean, default: false },
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const active = defineModel<string>('active', { required: true })
|
|
22
|
+
|
|
23
|
+
function onPick(name: string) {
|
|
24
|
+
if (active.value !== name) active.value = name
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
.vax-tabs {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 4px;
|
|
32
|
+
margin: 12px 16px;
|
|
33
|
+
padding: 4px;
|
|
34
|
+
background: var(--van-gray-2);
|
|
35
|
+
border-radius: 10px;
|
|
36
|
+
overflow-x: auto;
|
|
37
|
+
scrollbar-width: none;
|
|
38
|
+
}
|
|
39
|
+
.vax-tabs::-webkit-scrollbar { display: none; }
|
|
40
|
+
.vax-tabs--sticky {
|
|
41
|
+
position: sticky;
|
|
42
|
+
top: 0;
|
|
43
|
+
z-index: 10;
|
|
44
|
+
background: var(--van-gray-2);
|
|
45
|
+
}
|
|
46
|
+
.vax-tab {
|
|
47
|
+
position: relative;
|
|
48
|
+
flex: 1;
|
|
49
|
+
min-width: max-content;
|
|
50
|
+
padding: 8px 14px;
|
|
51
|
+
border: 0;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
background: transparent;
|
|
54
|
+
font-size: 13px;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
line-height: 1.25;
|
|
57
|
+
color: var(--van-text-color-2);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
.vax-tab:active { transform: scale(0.98); }
|
|
63
|
+
.vax-tab.active { background: var(--van-background-2); color: var(--van-text-color); font-weight: 600; }
|
|
64
|
+
.vax-tab .badge { --van-badge-background: var(--van-primary-color); }
|
|
65
|
+
.vax-tab .badge :deep(.van-badge--top-right) { right: -8px; }
|
|
66
|
+
</style>
|