提交 8cd921f3 作者: 方治民

refactor: 重构采用 hook 模式开发 Map Widget

上级 368c2cb2
...@@ -157,6 +157,7 @@ ...@@ -157,6 +157,7 @@
"unocss": "^0.54.3", "unocss": "^0.54.3",
"unocss-preset-weapp": "^0.54.0", "unocss-preset-weapp": "^0.54.0",
"unplugin-auto-import": "^0.16.6", "unplugin-auto-import": "^0.16.6",
"unplugin-transform-class": "^0.5.1",
"unplugin-vue-components": "^0.25.1", "unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9", "vite": "^4.4.9",
"vue-eslint-parser": "^9.3.1" "vue-eslint-parser": "^9.3.1"
......
...@@ -271,6 +271,9 @@ devDependencies: ...@@ -271,6 +271,9 @@ devDependencies:
unplugin-auto-import: unplugin-auto-import:
specifier: ^0.16.6 specifier: ^0.16.6
version: 0.16.6(@vueuse/core@10.3.0) version: 0.16.6(@vueuse/core@10.3.0)
unplugin-transform-class:
specifier: ^0.5.1
version: 0.5.1
unplugin-vue-components: unplugin-vue-components:
specifier: ^0.25.1 specifier: ^0.25.1
version: 0.25.1(vue@3.2.47) version: 0.25.1(vue@3.2.47)
......
...@@ -282,7 +282,7 @@ export const defaultStyle: mapboxgl.Style = { ...@@ -282,7 +282,7 @@ export const defaultStyle: mapboxgl.Style = {
'text-max-width': 8, 'text-max-width': 8,
}, },
paint: { paint: {
'text-color': '#000', 'text-color': '#333',
'text-halo-color': '#fff', 'text-halo-color': '#fff',
'text-halo-width': 2, 'text-halo-width': 2,
'text-halo-blur': 1, 'text-halo-blur': 1,
...@@ -309,7 +309,7 @@ export const defaultStyle: mapboxgl.Style = { ...@@ -309,7 +309,7 @@ export const defaultStyle: mapboxgl.Style = {
'text-max-width': 8, 'text-max-width': 8,
}, },
paint: { paint: {
'text-color': '#000', 'text-color': '#333',
'text-halo-color': '#fff', 'text-halo-color': '#fff',
'text-halo-width': 2, 'text-halo-width': 2,
'text-halo-blur': 1, 'text-halo-blur': 1,
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
container: this.id, container: this.id,
style: this.config?.style, style: this.config?.style,
options: this.config?.options, options: this.config?.options,
attribution: this.config?.attribution,
} }
}, },
methods: { methods: {
...@@ -56,10 +57,6 @@ ...@@ -56,10 +57,6 @@
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
page {
height: 100%;
}
.wrap { .wrap {
display: flex; display: flex;
width: 100%; width: 100%;
......
...@@ -32,6 +32,7 @@ export default { ...@@ -32,6 +32,7 @@ export default {
}, },
options?.options, options?.options,
), ),
attributionControl: false,
}) })
// 绑定作用域 // 绑定作用域
...@@ -62,6 +63,17 @@ export default { ...@@ -62,6 +63,17 @@ export default {
center: map.getCenter(), center: map.getCenter(),
}) })
// 添加地图数据来源描述
if (options.attribution) {
map.addControl(
new mapboxgl.AttributionControl({
compact: true,
customAttribution: options.attribution?.text,
}),
options.attribution?.align || 'bottom-right',
)
}
// 加载地图控件 // 加载地图控件
loadMapControl(mapboxgl, map, options?.control) loadMapControl(mapboxgl, map, options?.control)
}) })
......
export type * from './src/types'
export * from './src/hook'
export { default as BottomBarWidget } from './src/index.vue'
import type { BottomBarInstance, BottomBarProps } from './types'
import { isProdMode } from '/@/utils/env'
/**
* 底部交互/展示小部件响应式 Hook
* @param props 组件参数
* @returns 组件响应式数据
*/
export function useBottomBarWidget<T extends BottomBarInstance>(props: BottomBarProps): [(instance: T) => void, T] {
const instanceRef = ref<Nullable<T>>(null)
const height = computed(() => instanceRef?.value?.height)
function register(instance: T) {
if (isProdMode()) {
if (instance === unref(instanceRef)) {
return
}
onUnmounted(() => {
instanceRef.value = null
})
}
instanceRef.value = instance
props && instance.setProps(props)
}
function getInstance(): T {
const instance = unref(instanceRef)
if (!instance) {
console.warn('BottomBar 小部件还未加载完成')
}
return instance as T
}
return [
register,
{
height,
setProps: (props: BottomBarProps) => {
getInstance()?.setProps(props)
},
toggleShow: (show?: boolean) => {
getInstance()?.toggleShow(show)
},
toggleExpand: (expand?: boolean) => {
getInstance()?.toggleExpand(expand)
},
} as T,
]
}
<!-- 底部 Bar 组件 -->
<script setup lang="ts">
const props = defineProps({
// 是否显示
show: {
type: Boolean,
default: true,
},
// 是否展开
expand: {
type: Boolean,
default: false,
},
// 高度
height: {
type: Number,
default: 100,
},
// 最高高度
maxHeight: {
type: Number,
default: 200,
},
})
const emits = defineEmits(['register'])
const iHeight = computed(() => `${props.height}rpx`)
const iMaxHeight = computed(() => `${props.maxHeight}rpx`)
const data = reactive({
show: props.show,
expand: props.expand,
})
function toggleShow(show?: boolean) {
data.show = show ?? !data.show
}
function toggleExpand(expand?: boolean) {
data.expand = expand ?? !data.expand
}
function setProps(value: Partial<typeof props>) {
Object.assign(data, value)
}
emits('register', {
setProps,
toggleShow,
toggleExpand,
height: computed(() => `${(data.expand ? props.maxHeight : props.height) + 60}rpx`),
})
</script>
<template>
<view class="wrap bottom-bar" :class="{ expand: data.expand }" v-show="data.show">
<view class="action" @tap="toggleExpand()">
<Icon icon="solar-double-alt-arrow-up-line-duotone" size="60" class="icon" />
</view>
<!-- 自定义内容 -->
<view class="content">
<slot></slot>
</view>
<!-- 底部安全区 -->
<fui-safe-area />
</view>
</template>
<style lang="scss" scoped>
@mixin animate() {
transition: all 0.35s;
}
.wrap {
@include animate();
position: absolute;
left: 30rpx;
bottom: 30rpx;
z-index: 99;
background-color: white;
}
.bottom-bar {
@include animate();
position: fixed;
bottom: 0;
left: 0;
padding: 30rpx;
width: calc(100% - 60rpx);
height: v-bind(iHeight);
max-height: v-bind(iMaxHeight);
box-shadow: 0 0 15rpx rgba(0, 0, 0, 0.1);
&.expand {
height: v-bind(iMaxHeight);
.icon {
transform: rotateX(180deg) !important;
}
}
.action {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.icon {
@include animate();
position: relative;
top: -20rpx;
transform: rotateX(0);
transform-style: preserve-3d;
}
}
.content {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: calc(100% - 60rpx - 20rpx);
}
}
</style>
export interface BottomBarProps {
// 是否显示
show?: boolean
// 是否展开
expand?: boolean
// 高度 rpx
height?: number
// 最大高度 rpx
maxHeight?: number
}
export interface BottomBarInstance {
setProps: (props: BottomBarProps) => void
toggleShow: (show?: boolean) => void
toggleExpand: (expand?: boolean) => void
height?: ComputedRef<string>
}
export type * from './src/types'
export * from './src/hook'
export { default as LegendWidget } from './src/index.vue'
import type { LegendInstance, LegendProps, Option } from './types'
import { isProdMode } from '/@/utils/env'
/**
* 图例组件响应式 Hook
* @param props 组件参数
* @returns 组件响应式数据
*/
export function useLegendWidget<T extends LegendInstance>(props: LegendProps): [(instance: T) => void, T] {
const instanceRef = ref<Nullable<T>>(null)
function register(instance: T) {
if (isProdMode()) {
if (instance === unref(instanceRef)) {
return
}
onUnmounted(() => {
instanceRef.value = null
})
}
instanceRef.value = instance
props && instance?.setProps(props)
}
function getInstance(): T {
const instance = unref(instanceRef)
if (!instance) {
console.warn('图例小部件还未加载完成')
}
return instance as T
}
return [
register,
{
setProps: (props: LegendProps) => {
getInstance()?.setProps(props)
},
setTitle: (title: string) => {
getInstance()?.setTitle(title)
},
setOption: (option: Option) => {
getInstance()?.setOption(option)
},
toggleShow: (show?: boolean) => {
getInstance()?.toggleShow(show)
},
toggleExpand: (expand?: boolean) => {
getInstance()?.toggleExpand(expand)
},
} as T,
]
}
<!-- 图例组件 --> <!-- 图例组件 -->
<script setup lang="ts"> <script setup lang="ts">
//
import { isArray } from 'lodash-es' import { isArray } from 'lodash-es'
import { createReusableTemplate } from '@vueuse/core' import { createReusableTemplate } from '@vueuse/core'
import type { Option } from './types'
interface OptionItem {
// 颜色
color?: string
// 图标
icon?: string
// 标签
label?: string
// 多子项
items?: OptionItem[]
}
interface Option {
// 选项
items: OptionItem[] | OptionItem[][]
// label 样式
labelStyle?: Recordable
// 色块样式
blockStyle?: Recordable
}
const props = defineProps({ const props = defineProps({
// 显示 // 显示
...@@ -47,34 +28,45 @@ ...@@ -47,34 +28,45 @@
}, },
}) })
const emits = defineEmits(['register'])
// 定义复用渲染组件
const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ item: Recordable; sub?: boolean }>()
const data = reactive({ const data = reactive({
// 显示
show: props.show, show: props.show,
// 展开
expand: props.expand, expand: props.expand,
// 标题
title: props.title, title: props.title,
// JSON 数据
option: props.option, option: props.option,
}) })
watch( function setTitle(title: string) {
() => props.show, data.title = title
(show) => (data.show = show), }
)
function setOption(option: Option) {
data.option = option
}
watch( function toggleShow(show?: boolean) {
() => props.title, data.show = show ?? !data.show
(title) => (data.title = title), }
)
watch( function toggleExpand(expand?: boolean) {
() => props.option, data.expand = expand ?? !data.expand
(option) => (data.option = option), }
{ deep: true },
)
const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ item: Recordable; sub?: boolean }>() function setProps(value: Partial<typeof props>) {
Object.assign(data, value)
}
emits('register', {
setProps,
setTitle,
setOption,
toggleShow,
toggleExpand,
})
</script> </script>
<template> <template>
...@@ -90,26 +82,7 @@ ...@@ -90,26 +82,7 @@
<!-- 标题 --> <!-- 标题 -->
<view v-if="data.title" class="title">{{ data.title }}</view> <view v-if="data.title" class="title">{{ data.title }}</view>
<!-- JSON --> <!-- 定义复用渲染组件 -->
<view class="option">
<template v-if="isArray(data.option.items?.[0])">
<view v-for="(item, index) in data.option.items" :key="index" class="item-wrap">
<view v-for="(subItem, subIndex) in item" :key="subIndex" class="items">
<ReuseTemplate :item="subItem" />
</view>
</view>
</template>
<template v-else>
<view class="item-wrap">
<view v-for="(item, index) in data.option.items" :key="index" class="items">
<ReuseTemplate :item="item" />
</view>
</view>
</template>
</view>
<!-- 复用渲染组件 -->
<DefineTemplate v-slot="{ item, sub }"> <DefineTemplate v-slot="{ item, sub }">
<view class="item"> <view class="item">
<!-- 图标 --> <!-- 图标 -->
...@@ -137,12 +110,43 @@ ...@@ -137,12 +110,43 @@
</view> </view>
</view> </view>
</DefineTemplate> </DefineTemplate>
<!-- JSON -->
<view class="option">
<template v-if="isArray(data.option.items?.[0])">
<view v-for="(item, index) in data.option.items" :key="index" class="item-wrap">
<view v-for="(subItem, subIndex) in item" :key="subIndex" class="items">
<ReuseTemplate :item="subItem" />
</view>
</view>
</template>
<template v-else>
<view class="item-wrap">
<view v-for="(item, index) in data.option.items" :key="index" class="items">
<ReuseTemplate :item="item" />
</view>
</view>
</template>
</view>
</view> </view>
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
// @mixin animate() {
transition: all 0.35s;
}
.wrap {
@include animate();
position: absolute;
left: 30rpx;
bottom: 30rpx;
z-index: 99;
}
.legend { .legend {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -158,6 +162,7 @@ ...@@ -158,6 +162,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 26rpx; font-size: 26rpx;
font-weight: bold;
color: #1890ff; color: #1890ff;
.text { .text {
......
export interface OptionItem {
// 颜色
color?: string
// 图标
icon?: string
// 标签
label?: string
// 多子项
items?: OptionItem[]
}
export interface Option {
// 选项
items: OptionItem[] | OptionItem[][]
// label 样式
labelStyle?: Recordable
// 色块样式
blockStyle?: Recordable
}
export interface LegendProps {
// 是否显示
show?: boolean
// 是否展开
expand?: boolean
// 标题
title: string
// 选项
option: Option
}
export interface LegendInstance {
setProps: (props: LegendProps) => void
setTitle: (title: string) => void
setOption: (option: Option) => void
toggleShow: (show?: boolean) => void
toggleExpand: (expand?: boolean) => void
}
export type * from './src/types'
export * from './src/hook'
export { default as SwitchControlWidget } from './src/index.vue'
import type { SwitchControlInstance, SwitchControlProps } from './types'
import { isProdMode } from '/@/utils/env'
/**
* 前后切换组件响应式 Hook
* @param props 组件参数
* @returns 组件响应式数据
*/
export function useSwitchControlWidget<T extends SwitchControlInstance>(
props: SwitchControlProps,
): [(instance: T) => void, T] {
const instanceRef = ref<Nullable<T>>(null)
function register(instance: T) {
if (isProdMode()) {
if (instance === unref(instanceRef)) {
return
}
onUnmounted(() => {
instanceRef.value = null
})
}
instanceRef.value = instance
props && instance?.setProps(props)
}
function getInstance(): T {
const instance = unref(instanceRef)
if (!instance) {
console.warn('前后切换小部件还未加载完成')
}
return instance as T
}
return [
register,
{
setProps: (props: SwitchControlProps) => {
getInstance()?.setProps(props)
},
toggleShow: (show?: boolean) => {
getInstance()?.toggleShow(show)
},
prev: () => {
getInstance()?.prev()
},
next: () => {
getInstance()?.next()
},
} as T,
]
}
<!-- 前后切换组件 -->
<script setup lang="ts">
const props = defineProps({
// 是否显示
show: {
type: Boolean,
default: true,
},
})
const emits = defineEmits(['register', 'prev', 'next'])
const data = reactive({
show: props.show,
// 往前
prev: () => {},
// 往后
next: () => {},
})
function toggleShow(show?: boolean) {
data.show = show ?? !data.show
}
function setProps(value: Partial<typeof props>) {
Object.assign(data, value)
}
function onPrev() {
data.prev?.()
emits('prev')
}
function onNext() {
data.next?.()
emits('next')
}
emits('register', {
prev: onPrev,
next: onNext,
setProps,
toggleShow,
})
</script>
<template>
<view class="wrap switch-control">
<view class="prev" @tap="onPrev">
<Icon class="icon" icon="ic-baseline-arrow-back-ios" size="50" color="white" />
</view>
<view class="next" @tap="onNext">
<Icon class="icon" icon="ic-baseline-arrow-forward-ios" size="50" color="white" />
</view>
</view>
</template>
<style lang="scss" scoped>
.switch-control {
> * {
width: 70rpx;
height: 120rpx;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: calc(50% - 100rpx);
z-index: 99;
}
.prev {
left: 0;
border-radius: 0 10rpx 10rpx 0;
.icon {
position: relative;
left: 12rpx;
}
}
.next {
right: 0;
border-radius: 10rpx 0 0 10rpx;
}
}
</style>
export interface SwitchControlProps {
// 是否显示
show?: boolean
// 上一个
prev?: () => void
// 下一个
next?: () => void
}
export interface SwitchControlInstance {
prev: () => void
next: () => void
setProps: (props: SwitchControlProps) => void
toggleShow: (show?: boolean) => void
}
...@@ -77,7 +77,9 @@ ...@@ -77,7 +77,9 @@
"titleNView": { "titleNView": {
"buttons": [ "buttons": [
{ {
"type": "share" "type": "share",
"color": "white",
"width": "50px"
} }
] ]
} }
......
<!-- 底部 Bar 组件 -->
<script setup lang="ts">
//
</script>
<template>
<view class="wrap bottom-bar">
<!-- -->
</view>
</template>
<style lang="scss" scoped>
//
</style>
<!-- 前后切换组件 -->
<script setup lang="ts">
//
</script>
<template>
<view class="wrap switch-control">
<!-- -->
</view>
</template>
<style lang="scss" scoped>
//
</style>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// 单色块图例配置 // 单色块图例配置
export const defaultLegendConfig = { export const defaultLegendConfig = {
expand: true, expand: false,
title: '单位: mm', title: '单位: mm',
option: { option: {
blockStyle: { blockStyle: {
......
<!-- 页面组件 --> <!-- 页面组件 -->
<script setup lang="ts"> <script setup lang="ts">
// 组件
import Legend from './components/Legend.vue'
import { defaultLegendConfig } from './config' import { defaultLegendConfig } from './config'
import { useShare } from '@/hooks/page/useShare' import { useShare } from '@/hooks/page/useShare'
import { LegendWidget, useLegendWidget } from '@/components/Map/Mapbox/widgets/Legend'
import { BottomBarWidget, useBottomBarWidget } from '@/components/Map/Mapbox/widgets/BottomBar'
import { SwitchControlWidget, useSwitchControlWidget } from '@/components/Map/Mapbox/widgets/SwitchControl'
useShare() useShare()
const { expand, title, option } = defaultLegendConfig const config = {
// 测试地图数据来源标注展示
attribution: {
text: '湖南省气象台',
// align: 'bottom-left',
},
}
// 前后切换小部件
const [registerSwitchControlWidget] = useSwitchControlWidget({
show: true,
prev: () => console.log('prev'),
next: () => console.log('next'),
})
// 图例小部件
const [registerLegendWidget] = useLegendWidget({
show: true,
expand: true,
title: defaultLegendConfig.title,
option: defaultLegendConfig.option,
})
// 底部 Bar 小部件
const [registerBottomBarWidget, { height }] = useBottomBarWidget({
show: true,
expand: true,
height: 100,
maxHeight: 200,
})
</script> </script>
<template> <template>
<view class="bg-white h-100vh"> <view class="bg-white h-100vh rain">
<!-- 地图组件 --> <!-- 地图组件 -->
<Mapbox style="height: 100%" /> <Mapbox :config="config" />
<!-- 交互组件集 --> <!-- 交互组件集 -->
<view class="interaction"> <view class="widgets">
<!-- --> <!-- -->
<!-- Legend 组件 --> <!-- 前后切换小部件 -->
<Legend :expand="expand" :title="title" :option="option" /> <SwitchControlWidget @register="registerSwitchControlWidget" />
<!-- 图例小部件 -->
<LegendWidget @register="registerLegendWidget" />
<!-- 底部 Bar 小部件 -->
<BottomBarWidget @register="registerBottomBarWidget">
<!-- 内容 Slot -->
<view class="c-coolGray">底部交互控件/展示内容</view>
</BottomBarWidget>
</view> </view>
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
// .widgets {
.interaction {
:deep(.legend) { :deep(.legend) {
position: absolute; bottom: calc(30rpx + v-bind(height));
left: 30rpx;
bottom: 30rpx; // 如果底部使用 bottom-left attribution 则需要加上 80rpx,默认显示在右侧,不会冲突
z-index: 99; // bottom: calc(30rpx + 80rpx + v-bind(height));
}
}
</style>
<style>
/* 外部套一层防止全局污染 */
.rain {
:deep(.mapboxgl-ctrl-bottom-right) {
bottom: calc(30rpx + 60rpx + v-bind(height));
}
:deep(.mapboxgl-ctrl-bottom-left) {
bottom: calc(30rpx + 60rpx + v-bind(height));
} }
} }
</style> </style>
...@@ -106,4 +106,28 @@ ...@@ -106,4 +106,28 @@
/* stylelint-disable-next-line function-no-unknown */ /* stylelint-disable-next-line function-no-unknown */
padding-bottom: constant(safe-area-inset-bottom); padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
transition: all 0.35s;
}
.mapboxgl-ctrl-attrib-inner {
display: inline-block !important;
vertical-align: middle;
line-height: 18px;
font-size: 12px;
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
color: rgba(0, 0, 0, 0.75);
text-decoration: none;
}
.mapboxgl-ctrl-attrib.mapboxgl-compact {
padding-right: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.mapboxgl-ctrl-attrib-button {
position: initial;
} }
...@@ -7,11 +7,15 @@ export {} ...@@ -7,11 +7,15 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
BottomBar: typeof import('./../src/components/Map/Mapbox/components/BottomBar.vue')['default']
BottomBarWidget: typeof import('./../src/components/Map/Mapbox/widgets/BottomBarWidget.vue')['default']
CacheImage: typeof import('./../src/components/CacheImage/index.vue')['default'] CacheImage: typeof import('./../src/components/CacheImage/index.vue')['default']
CustomPicker: typeof import('./../src/components/CustomPicker/index.vue')['default'] CustomPicker: typeof import('./../src/components/CustomPicker/index.vue')['default']
Empty: typeof import('./../src/components/Empty/index.vue')['default'] Empty: typeof import('./../src/components/Empty/index.vue')['default']
FDragItem: typeof import('./../src/components/FirstUI/fui-drag/f-drag-item.vue')['default'] FDragItem: typeof import('./../src/components/FirstUI/fui-drag/f-drag-item.vue')['default']
FIndexListItem: typeof import('./../src/components/FirstUI/fui-index-list/f-index-list-item.vue')['default'] FIndexListItem: typeof import('./../src/components/FirstUI/fui-index-list/f-index-list-item.vue')['default']
FloatFillter: typeof import('./../src/components/Map/Mapbox/components/FloatFillter.vue')['default']
FloatFillterWidget: typeof import('./../src/components/Map/Mapbox/widgets/FloatFillterWidget.vue')['default']
FuiActionsheet: typeof import('./../src/components/FirstUI/fui-actionsheet/fui-actionsheet.vue')['default'] FuiActionsheet: typeof import('./../src/components/FirstUI/fui-actionsheet/fui-actionsheet.vue')['default']
FuiAlert: typeof import('./../src/components/FirstUI/fui-alert/fui-alert.vue')['default'] FuiAlert: typeof import('./../src/components/FirstUI/fui-alert/fui-alert.vue')['default']
FuiAnimation: typeof import('./../src/components/FirstUI/fui-animation/fui-animation.vue')['default'] FuiAnimation: typeof import('./../src/components/FirstUI/fui-animation/fui-animation.vue')['default']
...@@ -136,11 +140,20 @@ declare module 'vue' { ...@@ -136,11 +140,20 @@ declare module 'vue' {
Grid: typeof import('./../src/components/Layout/Grid.vue')['default'] Grid: typeof import('./../src/components/Layout/Grid.vue')['default']
Header: typeof import('./../src/components/Layout/Header.vue')['default'] Header: typeof import('./../src/components/Layout/Header.vue')['default']
Icon: typeof import('./../src/components/Icon/index.vue')['default'] Icon: typeof import('./../src/components/Icon/index.vue')['default']
LeftBar: typeof import('./../src/components/Map/Mapbox/components/LeftBar.vue')['default']
LeftBarWidget: typeof import('./../src/components/Map/Mapbox/widgets/LeftBarWidget.vue')['default']
Legend: typeof import('./../src/components/Map/Mapbox/components/Legend.vue')['default']
LegendWidget: typeof import('./../src/components/Map/Mapbox/components/LegendWidget/index.vue')['default']
Mapbox: typeof import('./../src/components/Map/Mapbox/index.vue')['default'] Mapbox: typeof import('./../src/components/Map/Mapbox/index.vue')['default']
MenuHeader: typeof import('./../src/components/Layout/MenuHeader.vue')['default'] MenuHeader: typeof import('./../src/components/Layout/MenuHeader.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Src: typeof import('./../src/components/Map/Mapbox/widgets/BottomBar/src/index.vue')['default']
SwitchControl: typeof import('./../src/components/Map/Mapbox/components/SwitchControl.vue')['default']
SwitchControlWidget: typeof import('./../src/components/Map/Mapbox/widgets/SwitchControlWidget.vue')['default']
ThumbnailPreview: typeof import('./../src/components/ThumbnailPreview/index.vue')['default'] ThumbnailPreview: typeof import('./../src/components/ThumbnailPreview/index.vue')['default']
TopBar: typeof import('./../src/components/Map/Mapbox/components/TopBar.vue')['default']
TopBarWidget: typeof import('./../src/components/Map/Mapbox/widgets/TopBarWidget.vue')['default']
View: typeof import('./../src/components/Layout/View.vue')['default'] View: typeof import('./../src/components/Layout/View.vue')['default']
} }
} }
...@@ -2,6 +2,8 @@ import type { AxiosRequestConfig } from 'axios' ...@@ -2,6 +2,8 @@ import type { AxiosRequestConfig } from 'axios'
declare global { declare global {
type Recordable<T = any> = Record<string, T> type Recordable<T = any> = Record<string, T>
type Nullable<T> = T | null
type NonNullable<T> = T extends null | undefined ? never : T
interface ImportMetaEnv extends ViteEnv { interface ImportMetaEnv extends ViteEnv {
__: unknown __: unknown
......
...@@ -8,7 +8,19 @@ const { presetWeappAttributify, transformerAttributify } = extractorAttributify( ...@@ -8,7 +8,19 @@ const { presetWeappAttributify, transformerAttributify } = extractorAttributify(
attributes: [...defaultAttributes, 'icon'], attributes: [...defaultAttributes, 'icon'],
}) })
const ICONS = [
'ic-baseline-keyboard-arrow-up',
'ic-baseline-keyboard-arrow-down',
'ic-baseline-keyboard-arrow-left',
'ic-baseline-keyboard-arrow-right',
'ic-baseline-arrow-back-ios',
'ic-baseline-arrow-forward-ios',
'solar-double-alt-arrow-up-line-duotone',
'solar-double-alt-arrow-down-line-duotone',
]
export default defineConfig({ export default defineConfig({
safelist: ICONS.map((icon) => `icon-${icon.replace(/:/g, '-')}`),
shortcuts: [ shortcuts: [
{ {
bg: 'bg-[#F4F5F7]', bg: 'bg-[#F4F5F7]',
...@@ -23,9 +35,10 @@ export default defineConfig({ ...@@ -23,9 +35,10 @@ export default defineConfig({
presetWeappAttributify(), presetWeappAttributify(),
// icon // icon
presetIcons({ presetIcons({
prefix: 'icon-', prefix: ['i-', 'icon-'],
extraProperties: { extraProperties: {
display: 'inline-flex', display: 'inline-flex',
'vertical-align': 'middle',
}, },
}), }),
], ],
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论