wui-components-v2 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/api/core/handlers.ts +106 -0
- package/api/core/instance.ts +60 -0
- package/api/core/middleware.ts +92 -0
- package/api/index.ts +15 -0
- package/composables/types/theme.ts +63 -0
- package/composables/useLocale.ts +37 -0
- package/composables/useManualTheme.ts +175 -0
- package/composables/useTheme.ts +73 -0
- package/index.ts +25 -0
- package/package.json +14 -0
- package/store/manualThemeStore.ts +141 -0
- package/store/themeStore.ts +74 -0
- package/test.vue +9 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: weisheng
|
|
3
|
+
* @Date: 2025-04-17 15:58:11
|
|
4
|
+
* @LastEditTime: 2025-06-15 21:47:22
|
|
5
|
+
* @LastEditors: weisheng
|
|
6
|
+
* @Description: Alova response and error handlers
|
|
7
|
+
* @FilePath: /wot-demo/src/api/core/handlers.ts
|
|
8
|
+
*/
|
|
9
|
+
import type { Method } from 'alova'
|
|
10
|
+
import router from '@/router'
|
|
11
|
+
|
|
12
|
+
// Custom error class for API errors
|
|
13
|
+
export class ApiError extends Error {
|
|
14
|
+
code: number
|
|
15
|
+
data?: any
|
|
16
|
+
|
|
17
|
+
constructor(message: string, code: number, data?: any) {
|
|
18
|
+
super(message)
|
|
19
|
+
this.name = 'ApiError'
|
|
20
|
+
this.code = code
|
|
21
|
+
this.data = data
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Define a type for the expected API response structure
|
|
26
|
+
interface ApiResponse {
|
|
27
|
+
code: number
|
|
28
|
+
msg?: string
|
|
29
|
+
data?: any
|
|
30
|
+
success?: boolean
|
|
31
|
+
total?: number
|
|
32
|
+
more?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 处理成功的响应
|
|
36
|
+
export async function handleAlovaResponse(
|
|
37
|
+
response: UniApp.RequestSuccessCallbackResult | UniApp.UploadFileSuccessCallbackResult | UniApp.DownloadSuccessData,
|
|
38
|
+
) {
|
|
39
|
+
const globalToast = useGlobalToast()
|
|
40
|
+
// Extract status code and data from UniApp response
|
|
41
|
+
const { statusCode, data } = response as UniNamespace.RequestSuccessCallbackResult
|
|
42
|
+
|
|
43
|
+
// 处理401/403错误(如果不是在handleAlovaResponse中处理的)
|
|
44
|
+
if ((statusCode === 401 || statusCode === 403)) {
|
|
45
|
+
// 如果是未授权错误,清除用户信息并跳转到登录页
|
|
46
|
+
globalToast.error({ msg: '登录已过期,请重新登录!', duration: 500 })
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
clearTimeout(timer)
|
|
49
|
+
router.replaceAll({ name: 'login' })
|
|
50
|
+
}, 500)
|
|
51
|
+
|
|
52
|
+
throw new ApiError('登录已过期,请重新登录!', statusCode, data)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle HTTP error status codes
|
|
56
|
+
if (statusCode >= 400) {
|
|
57
|
+
globalToast.error(`请求失败,状态为: ${statusCode}`)
|
|
58
|
+
throw new ApiError(`请求失败,状态为: ${statusCode}`, statusCode, data)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The data is already parsed by UniApp adapter
|
|
62
|
+
const json = data as ApiResponse
|
|
63
|
+
// Log response in development
|
|
64
|
+
if (import.meta.env.MODE === 'development') {
|
|
65
|
+
console.log('[Alova Response]', json)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Return data for successful responses
|
|
69
|
+
return json
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 处理失败的响应
|
|
73
|
+
export function handleAlovaError(error: any, method: Method) {
|
|
74
|
+
const globalToast = useGlobalToast()
|
|
75
|
+
// Log error in development
|
|
76
|
+
if (import.meta.env.MODE === 'development') {
|
|
77
|
+
console.error('[Alova Error]', error, method)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 处理401/403错误(如果不是在handleAlovaResponse中处理的)
|
|
81
|
+
if (error instanceof ApiError && (error.code === 401 || error.code === 403)) {
|
|
82
|
+
// 如果是未授权错误,清除用户信息并跳转到登录页
|
|
83
|
+
globalToast.error({ msg: '登录已过期,请重新登录!', duration: 500 })
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
clearTimeout(timer)
|
|
86
|
+
router.replaceAll({ name: 'login' })
|
|
87
|
+
}, 500)
|
|
88
|
+
throw new ApiError('登录已过期,请重新登录!', error.code, error.data)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle different types of errors
|
|
92
|
+
if (error.name === 'NetworkError') {
|
|
93
|
+
globalToast.error('网络错误,请检查您的网络连接')
|
|
94
|
+
}
|
|
95
|
+
else if (error.name === 'TimeoutError') {
|
|
96
|
+
globalToast.error('请求超时,请重试')
|
|
97
|
+
}
|
|
98
|
+
else if (error instanceof ApiError) {
|
|
99
|
+
globalToast.error(error.message || '请求失败')
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
globalToast.error('发生意外错误')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw error
|
|
106
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: weisheng
|
|
3
|
+
* @Date: 2025-04-10 18:02:00
|
|
4
|
+
* @LastEditTime: 2025-06-15 22:41:52
|
|
5
|
+
* @LastEditors: weisheng
|
|
6
|
+
* @Description: Alova instance configuration
|
|
7
|
+
* @FilePath: /wot-demo/src/api/core/instance.ts
|
|
8
|
+
*/
|
|
9
|
+
import { createAlova } from 'alova'
|
|
10
|
+
import vueHook from 'alova/vue'
|
|
11
|
+
import AdapterUniapp from '@alova/adapter-uniapp'
|
|
12
|
+
import { handleAlovaError, handleAlovaResponse } from './handlers'
|
|
13
|
+
|
|
14
|
+
export const alovaInstance = createAlova({
|
|
15
|
+
baseURL: import.meta.env.VITE_API_BASE_URL || 'https://petstore3.swagger.io/api/v3',
|
|
16
|
+
...AdapterUniapp(),
|
|
17
|
+
statesHook: vueHook,
|
|
18
|
+
beforeRequest: (method) => {
|
|
19
|
+
// 为POST/PUT/PATCH请求添加内容类型
|
|
20
|
+
if (['POST', 'PUT', 'PATCH'].includes(method.type)) {
|
|
21
|
+
method.config.headers['Content-Type'] = 'application/json'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 添加时间戳以防止缓存GET请求
|
|
25
|
+
if (method.type === 'GET' && CommonUtil.isObj(method.config.params)) {
|
|
26
|
+
method.config.params._t = Date.now()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 开发中的日志请求
|
|
30
|
+
if (import.meta.env.MODE === 'development') {
|
|
31
|
+
console.log(`[Alova Request] ${method.type} ${method.url}`, method.data || method.config.params)
|
|
32
|
+
console.log(`[API Base URL] ${import.meta.env.VITE_API_BASE_URL}`)
|
|
33
|
+
console.log(`[Environment] ${import.meta.env.VITE_ENV_NAME}`)
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// 响应处理程序
|
|
38
|
+
responded: {
|
|
39
|
+
// 成功处理程序
|
|
40
|
+
onSuccess: handleAlovaResponse,
|
|
41
|
+
|
|
42
|
+
// 错误处理程序
|
|
43
|
+
onError: handleAlovaError,
|
|
44
|
+
|
|
45
|
+
// 完整处理程序-成功或错误后运行
|
|
46
|
+
onComplete: async () => {
|
|
47
|
+
// Any cleanup or logging can be done here
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// 我们将在钩子中使用中间件
|
|
52
|
+
// createAlova选项中不直接支持中间件
|
|
53
|
+
|
|
54
|
+
// 默认请求超时(10秒)
|
|
55
|
+
timeout: 60000,
|
|
56
|
+
// 设置为null即可全局关闭全部请求缓存
|
|
57
|
+
cacheFor: null,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export default alovaInstance
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 延迟加载中间件
|
|
3
|
+
* 延迟显示加载状态,防止快速请求导致的闪烁
|
|
4
|
+
* @param delay 显示加载状态前的延迟时间(毫秒)
|
|
5
|
+
* @returns Alova 中间件
|
|
6
|
+
*/
|
|
7
|
+
export function createDelayLoadingMiddleware(delay = 300) {
|
|
8
|
+
return async (context: any, next: any) => {
|
|
9
|
+
context.controlLoading()
|
|
10
|
+
|
|
11
|
+
const { loading } = context.proxyStates
|
|
12
|
+
|
|
13
|
+
const timer = setTimeout(() => {
|
|
14
|
+
loading.v = true
|
|
15
|
+
}, delay)
|
|
16
|
+
|
|
17
|
+
await next()
|
|
18
|
+
|
|
19
|
+
loading.v = false
|
|
20
|
+
clearTimeout(timer)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 全局加载中间件
|
|
26
|
+
* 为所有请求显示全局加载指示器,支持延迟显示
|
|
27
|
+
*
|
|
28
|
+
* 使用示例:
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // 1. 基本用法
|
|
31
|
+
* const { send: submit } = useRequest(method, {
|
|
32
|
+
* middleware: createGlobalLoadingMiddleware()
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // 2. 自定义延迟时间和加载文本
|
|
36
|
+
* const { send: submit } = useRequest(method, {
|
|
37
|
+
* middleware: createGlobalLoadingMiddleware({
|
|
38
|
+
* delay: 500, // 延迟 500ms 显示加载指示器,防止闪烁
|
|
39
|
+
* loadingText: '正在提交...', // 自定义加载文本
|
|
40
|
+
* })
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param options 加载选项
|
|
45
|
+
* @param options.delay 显示加载指示器前的延迟时间(毫秒),默认 300ms
|
|
46
|
+
* @param options.loadingText 加载指示器显示的文本,默认为 'Loading...'
|
|
47
|
+
* @returns Alova 中间件
|
|
48
|
+
*/
|
|
49
|
+
export function createGlobalLoadingMiddleware(options: {
|
|
50
|
+
delay?: number
|
|
51
|
+
loadingText?: string
|
|
52
|
+
} = {}) {
|
|
53
|
+
const {
|
|
54
|
+
delay = 0,
|
|
55
|
+
loadingText = 'Loading...',
|
|
56
|
+
} = options
|
|
57
|
+
|
|
58
|
+
return async (ctx: any, next: any) => {
|
|
59
|
+
// 自行控制loading
|
|
60
|
+
ctx.controlLoading()
|
|
61
|
+
|
|
62
|
+
const globalLoading = useGlobalLoading()
|
|
63
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
64
|
+
|
|
65
|
+
// 如果delay为0或未设置,直接显示loading
|
|
66
|
+
if (delay <= 0) {
|
|
67
|
+
globalLoading.loading(loadingText)
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// 延迟特定时间显示全局loading
|
|
71
|
+
timer = setTimeout(() => {
|
|
72
|
+
globalLoading.loading(loadingText)
|
|
73
|
+
}, delay)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await next()
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
// 清除定时器并关闭loading
|
|
81
|
+
if (timer) {
|
|
82
|
+
clearTimeout(timer)
|
|
83
|
+
}
|
|
84
|
+
globalLoading.close()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 导出延迟加载中间件作为默认中间件
|
|
90
|
+
export const defaultMiddleware = createDelayLoadingMiddleware()
|
|
91
|
+
|
|
92
|
+
export default defaultMiddleware
|
package/api/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// 导入核心alova实例
|
|
2
|
+
import alovaInstance from './core/instance'
|
|
3
|
+
|
|
4
|
+
// 如果需要,导出alova实例以供直接使用
|
|
5
|
+
export { alovaInstance }
|
|
6
|
+
|
|
7
|
+
// 为特定API配置方法选项
|
|
8
|
+
export const $$userConfigMap = withConfigType({})
|
|
9
|
+
|
|
10
|
+
// 创建全局Apis对象
|
|
11
|
+
const Apis = createApis(alovaInstance, $$userConfigMap)
|
|
12
|
+
|
|
13
|
+
// 导出默认导出和命名导出以进行自动导入
|
|
14
|
+
export default Apis
|
|
15
|
+
export { Apis }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ConfigProviderThemeVars } from 'wot-design-uni'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 主题色选项接口
|
|
5
|
+
*/
|
|
6
|
+
export interface ThemeColorOption {
|
|
7
|
+
name: string
|
|
8
|
+
value: string
|
|
9
|
+
primary: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 语言选项接口
|
|
14
|
+
*/
|
|
15
|
+
export interface LocaleOption {
|
|
16
|
+
name: string
|
|
17
|
+
value: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 主题类型
|
|
22
|
+
*/
|
|
23
|
+
export type ThemeMode = 'light' | 'dark'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 主题状态接口
|
|
27
|
+
*/
|
|
28
|
+
export interface ThemeState {
|
|
29
|
+
theme: ThemeMode
|
|
30
|
+
Locale: LocaleOption
|
|
31
|
+
followSystem: boolean
|
|
32
|
+
hasUserSet: boolean
|
|
33
|
+
currentThemeColor: ThemeColorOption
|
|
34
|
+
themeVars: ConfigProviderThemeVars
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 系统主题状态接口(简化版)
|
|
39
|
+
*/
|
|
40
|
+
export interface SystemThemeState {
|
|
41
|
+
theme: ThemeMode
|
|
42
|
+
themeVars: ConfigProviderThemeVars
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 预定义的主题色选项
|
|
47
|
+
*/
|
|
48
|
+
export const themeColorOptions: ThemeColorOption[] = [
|
|
49
|
+
{ name: '默认蓝', value: 'blue', primary: '#4D7FFF' },
|
|
50
|
+
{ name: '活力橙', value: 'orange', primary: '#FF7D00' },
|
|
51
|
+
{ name: '薄荷绿', value: 'green', primary: '#07C160' },
|
|
52
|
+
{ name: '樱花粉', value: 'pink', primary: '#FF69B4' },
|
|
53
|
+
{ name: '紫罗兰', value: 'purple', primary: '#8A2BE2' },
|
|
54
|
+
{ name: '朱砂红', value: 'red', primary: '#FF4757' },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 预定义的语言选项
|
|
59
|
+
*/
|
|
60
|
+
export const LocaleOptions: LocaleOption[] = [
|
|
61
|
+
{ name: '简体中文', value: 'zh-CN' },
|
|
62
|
+
{ name: 'English', value: 'en-US' },
|
|
63
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Locale } from 'wot-design-uni'
|
|
2
|
+
// 引入英文语言包
|
|
3
|
+
import enUS from 'wot-design-uni/locale/lang/en-US'
|
|
4
|
+
// 引入中文语言包
|
|
5
|
+
import zhCN from 'wot-design-uni/locale/lang/zh-CN'
|
|
6
|
+
import { useManualThemeStore } from '../store/manualThemeStore'
|
|
7
|
+
/**
|
|
8
|
+
* 简化版系统语言切换组合式API
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export function useLocale() {
|
|
12
|
+
const store = useManualThemeStore()
|
|
13
|
+
// 系统语言切换
|
|
14
|
+
function changeSystemLocale() {
|
|
15
|
+
console.log('系统语言切换为:', store.Locale.value)
|
|
16
|
+
switch (store.Locale.value) {
|
|
17
|
+
case 'en-US':
|
|
18
|
+
Locale.use('en-US', enUS)
|
|
19
|
+
break
|
|
20
|
+
default:
|
|
21
|
+
Locale.use('zh-CN', zhCN)
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 组件挂载前初始化系统语言
|
|
27
|
+
onBeforeMount(() => {
|
|
28
|
+
changeSystemLocale()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
// 状态(只读)
|
|
33
|
+
locale: computed(() => store.Locale),
|
|
34
|
+
// 方法
|
|
35
|
+
changeSystemLocale,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useManualThemeStore } from '../store/manualThemeStore'
|
|
2
|
+
import { useLocale } from './useLocale'
|
|
3
|
+
import type { LocaleOption, ThemeColorOption, ThemeMode } from './types/theme'
|
|
4
|
+
|
|
5
|
+
import { LocaleOptions, themeColorOptions } from './types/theme'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 完整版主题管理组合式API
|
|
9
|
+
*
|
|
10
|
+
* 功能特性:
|
|
11
|
+
* - 支持手动切换暗黑模式
|
|
12
|
+
* - 支持主题色选择
|
|
13
|
+
* - 支持跟随系统主题
|
|
14
|
+
* - 自动同步导航栏颜色
|
|
15
|
+
* - 持久化用户设置
|
|
16
|
+
*
|
|
17
|
+
* 适用场景:
|
|
18
|
+
* - 需要用户手动控制主题的应用
|
|
19
|
+
* - 需要主题色自定义的应用
|
|
20
|
+
* - 需要完整主题管理功能的复杂应用
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```vue
|
|
24
|
+
* <script setup>
|
|
25
|
+
* import { useManualTheme } from '@/composables/useManualTheme'
|
|
26
|
+
*
|
|
27
|
+
* const {
|
|
28
|
+
* theme,
|
|
29
|
+
* isDark,
|
|
30
|
+
* toggleTheme,
|
|
31
|
+
* openThemeColorPicker,
|
|
32
|
+
* currentThemeColor,
|
|
33
|
+
* themeVars
|
|
34
|
+
* } = useManualTheme()
|
|
35
|
+
* </script>
|
|
36
|
+
*
|
|
37
|
+
* <template>
|
|
38
|
+
* <wd-config-provider :theme-vars="themeVars">
|
|
39
|
+
* <view :class="{ 'dark-mode': isDark }">
|
|
40
|
+
* <wd-button @click="toggleTheme">切换主题</wd-button>
|
|
41
|
+
* <wd-button @click="openThemeColorPicker">选择主题色</wd-button>
|
|
42
|
+
* </view>
|
|
43
|
+
* </wd-config-provider>
|
|
44
|
+
* </template>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function useManualTheme() {
|
|
48
|
+
const store = useManualThemeStore()
|
|
49
|
+
const showThemeColorSheet = ref(false)
|
|
50
|
+
const showLanguageSheet = ref(false)
|
|
51
|
+
const language = useLocale()
|
|
52
|
+
/**
|
|
53
|
+
* 切换暗黑模式
|
|
54
|
+
* @param mode 指定主题模式,不传则自动切换
|
|
55
|
+
*/
|
|
56
|
+
function toggleTheme(mode?: ThemeMode) {
|
|
57
|
+
store.toggleTheme(mode)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 打开主题色选择器
|
|
62
|
+
*/
|
|
63
|
+
function openThemeColorPicker() {
|
|
64
|
+
showThemeColorSheet.value = true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 关闭主题色选择器
|
|
69
|
+
*/
|
|
70
|
+
function closeThemeColorPicker() {
|
|
71
|
+
showThemeColorSheet.value = false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 选择主题色
|
|
76
|
+
* @param option 主题色选项
|
|
77
|
+
*/
|
|
78
|
+
function selectThemeColor(option: ThemeColorOption) {
|
|
79
|
+
store.setCurrentThemeColor(option)
|
|
80
|
+
closeThemeColorPicker()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 选择语言
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
function selectLanguage(option: LocaleOption) {
|
|
88
|
+
store.setLocale(option)
|
|
89
|
+
closeLanguagePicker()
|
|
90
|
+
language.changeSystemLocale()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 打开语言选择器
|
|
95
|
+
*/
|
|
96
|
+
function openLanguagePicker() {
|
|
97
|
+
showLanguageSheet.value = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 关闭语言选择器
|
|
102
|
+
*/
|
|
103
|
+
function closeLanguagePicker() {
|
|
104
|
+
showLanguageSheet.value = false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 初始化主题
|
|
109
|
+
*/
|
|
110
|
+
function initTheme() {
|
|
111
|
+
store.initTheme()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 组件挂载前初始化主题
|
|
115
|
+
onBeforeMount(() => {
|
|
116
|
+
initTheme()
|
|
117
|
+
|
|
118
|
+
// 监听系统主题变化
|
|
119
|
+
if (typeof uni !== 'undefined' && uni.onThemeChange) {
|
|
120
|
+
uni.onThemeChange((res) => {
|
|
121
|
+
if (store.followSystem) {
|
|
122
|
+
toggleTheme(res.theme as ThemeMode)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// 页面显示时更新导航栏颜色,确保每次切换页面时导航栏颜色都是正确的
|
|
129
|
+
onShow(() => {
|
|
130
|
+
store.setNavigationBarColor()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 组件卸载时清理监听
|
|
134
|
+
onUnmounted(() => {
|
|
135
|
+
if (typeof uni !== 'undefined' && uni.offThemeChange) {
|
|
136
|
+
uni.offThemeChange((res) => {
|
|
137
|
+
if (store.followSystem) {
|
|
138
|
+
toggleTheme(res.theme as ThemeMode)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
// 状态
|
|
146
|
+
theme: computed(() => store.theme),
|
|
147
|
+
primary: computed(() => store.themeVars.colorTheme),
|
|
148
|
+
isDark: computed(() => store.isDark),
|
|
149
|
+
followSystem: computed(() => store.followSystem),
|
|
150
|
+
hasUserSet: computed(() => store.hasUserSet),
|
|
151
|
+
currentThemeColor: computed(() => store.currentThemeColor),
|
|
152
|
+
currentLocale: computed(() => store.Locale),
|
|
153
|
+
themeVars: computed(() => store.themeVars),
|
|
154
|
+
showThemeColorSheet,
|
|
155
|
+
showLanguageSheet,
|
|
156
|
+
// 常量
|
|
157
|
+
themeColorOptions,
|
|
158
|
+
LocaleOptions,
|
|
159
|
+
|
|
160
|
+
// 方法
|
|
161
|
+
initTheme,
|
|
162
|
+
toggleTheme,
|
|
163
|
+
setFollowSystem: store.setFollowSystem,
|
|
164
|
+
openThemeColorPicker,
|
|
165
|
+
closeThemeColorPicker,
|
|
166
|
+
selectThemeColor,
|
|
167
|
+
openLanguagePicker,
|
|
168
|
+
closeLanguagePicker,
|
|
169
|
+
selectLanguage,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 导出类型和常量供外部使用
|
|
174
|
+
export type { ThemeColorOption, ThemeMode, LocaleOption }
|
|
175
|
+
export { themeColorOptions }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ThemeMode } from '@/composables/types/theme'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 简化版系统主题管理组合式API
|
|
5
|
+
*
|
|
6
|
+
* 功能特性:
|
|
7
|
+
* - 仅跟随系统主题变化
|
|
8
|
+
* - 自动响应系统主题切换
|
|
9
|
+
* - 导航栏颜色通过 theme.json 自动处理
|
|
10
|
+
* - 轻量级,无额外功能
|
|
11
|
+
*
|
|
12
|
+
* 适用场景:
|
|
13
|
+
* - 只需要系统主题适应的简单应用
|
|
14
|
+
* - 不需要用户手动控制主题的应用
|
|
15
|
+
* - 追求轻量级主题管理的应用
|
|
16
|
+
*
|
|
17
|
+
* 注意事项:
|
|
18
|
+
* - 不支持手动切换主题
|
|
19
|
+
* - 不支持主题色自定义
|
|
20
|
+
* - 导航栏颜色依赖 theme.json 配置
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```vue
|
|
24
|
+
* <script setup>
|
|
25
|
+
* import { useTheme } from '@/composables/useTheme'
|
|
26
|
+
*
|
|
27
|
+
* const { theme, isDark, themeVars } = useTheme()
|
|
28
|
+
* </script>
|
|
29
|
+
*
|
|
30
|
+
* <template>
|
|
31
|
+
* <wd-config-provider :theme-vars="themeVars">
|
|
32
|
+
* <view :class="{ 'dark-mode': isDark }">
|
|
33
|
+
* <text>当前主题: {{ theme }}</text>
|
|
34
|
+
* </view>
|
|
35
|
+
* </wd-config-provider>
|
|
36
|
+
* </template>
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function useTheme() {
|
|
40
|
+
const store = useThemeStore()
|
|
41
|
+
|
|
42
|
+
// 组件挂载前初始化系统主题
|
|
43
|
+
onBeforeMount(() => {
|
|
44
|
+
store.initSystemTheme()
|
|
45
|
+
// 监听系统主题变化
|
|
46
|
+
if (typeof uni !== 'undefined' && uni.onThemeChange) {
|
|
47
|
+
uni.onThemeChange((res) => {
|
|
48
|
+
// 系统主题变化时自动更新,导航栏颜色由 theme.json 自动处理
|
|
49
|
+
store.setTheme(res.theme as ThemeMode)
|
|
50
|
+
console.log('系统主题已切换至:', res.theme)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 组件卸载时清理监听
|
|
56
|
+
onUnmounted(() => {
|
|
57
|
+
if (typeof uni !== 'undefined' && uni.offThemeChange) {
|
|
58
|
+
uni.offThemeChange((res) => {
|
|
59
|
+
store.setTheme(res.theme as ThemeMode)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
// 状态(只读)
|
|
66
|
+
theme: computed(() => store.theme),
|
|
67
|
+
isDark: computed(() => store.isDark),
|
|
68
|
+
themeVars: computed(() => store.themeVars),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 导出类型供外部使用
|
|
73
|
+
export type { ThemeMode }
|
package/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
// import SystemSettings from './components/system-settings/system-settings.vue'
|
|
3
|
+
// import { useLocale } from './composables/useLocale'
|
|
4
|
+
// import { useManualTheme } from './composables/useManualTheme'
|
|
5
|
+
// import request from './api'
|
|
6
|
+
|
|
7
|
+
import test from './test.vue'
|
|
8
|
+
|
|
9
|
+
const coms: any[] = [
|
|
10
|
+
test,
|
|
11
|
+
]
|
|
12
|
+
// 批量组件注册
|
|
13
|
+
function install(Vue: App) {
|
|
14
|
+
coms.forEach((com) => {
|
|
15
|
+
Vue.component(com.name, com)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// export const coreFunctions = {
|
|
20
|
+
// useLocale,
|
|
21
|
+
// useManualTheme,
|
|
22
|
+
// request,
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
export default install // 这个方法以后再使用的时候可以被vue.use调用
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wui-components-v2",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "wui 组件库",
|
|
5
|
+
"author": "wgxshh",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import type { LocaleOption, ThemeColorOption, ThemeMode, ThemeState } from '../composables/types/theme'
|
|
3
|
+
import { LocaleOptions, themeColorOptions } from '../composables/types/theme'
|
|
4
|
+
/**
|
|
5
|
+
* 完整版主题状态管理
|
|
6
|
+
* 支持手动切换主题、主题色选择、跟随系统主题等完整功能
|
|
7
|
+
*/
|
|
8
|
+
export const useManualThemeStore = defineStore('manualTheme', {
|
|
9
|
+
state: (): ThemeState => ({
|
|
10
|
+
theme: 'light',
|
|
11
|
+
followSystem: true, // 是否跟随系统主题
|
|
12
|
+
hasUserSet: false, // 用户是否手动设置过主题
|
|
13
|
+
currentThemeColor: themeColorOptions[0],
|
|
14
|
+
Locale: LocaleOptions[0],
|
|
15
|
+
themeVars: {
|
|
16
|
+
darkBackground: '#0f0f0f',
|
|
17
|
+
darkBackground2: '#1a1a1a',
|
|
18
|
+
darkBackground3: '#242424',
|
|
19
|
+
darkBackground4: '#2f2f2f',
|
|
20
|
+
darkBackground5: '#3d3d3d',
|
|
21
|
+
darkBackground6: '#4a4a4a',
|
|
22
|
+
darkBackground7: '#606060',
|
|
23
|
+
darkColor: '#ffffff',
|
|
24
|
+
darkColor2: '#e0e0e0',
|
|
25
|
+
darkColor3: '#a0a0a0',
|
|
26
|
+
colorTheme: themeColorOptions[0].primary,
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
getters: {
|
|
31
|
+
isDark: state => state.theme === 'dark',
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
actions: {
|
|
35
|
+
/**
|
|
36
|
+
* 手动切换主题
|
|
37
|
+
* @param mode 指定主题模式,不传则自动切换
|
|
38
|
+
*/
|
|
39
|
+
toggleTheme(mode?: ThemeMode) {
|
|
40
|
+
this.theme = mode || (this.theme === 'light' ? 'dark' : 'light')
|
|
41
|
+
this.hasUserSet = true // 标记用户已手动设置
|
|
42
|
+
this.followSystem = false // 不再跟随系统
|
|
43
|
+
this.setNavigationBarColor()
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 设置是否跟随系统主题
|
|
48
|
+
* @param follow 是否跟随系统
|
|
49
|
+
*/
|
|
50
|
+
setFollowSystem(follow: boolean) {
|
|
51
|
+
this.followSystem = follow
|
|
52
|
+
if (follow) {
|
|
53
|
+
this.hasUserSet = false
|
|
54
|
+
this.initTheme() // 重新获取系统主题
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 设置导航栏颜色
|
|
60
|
+
*/
|
|
61
|
+
setNavigationBarColor() {
|
|
62
|
+
uni.setNavigationBarColor({
|
|
63
|
+
frontColor: this.theme === 'light' ? '#000000' : '#ffffff',
|
|
64
|
+
backgroundColor: this.theme === 'light' ? '#ffffff' : '#000000',
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 设置主题色
|
|
70
|
+
* @param color 主题色选项
|
|
71
|
+
*/
|
|
72
|
+
setCurrentThemeColor(color: ThemeColorOption) {
|
|
73
|
+
this.currentThemeColor = color
|
|
74
|
+
this.themeVars.colorTheme = color.primary
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 设置语言
|
|
79
|
+
*/
|
|
80
|
+
setLocale(locale: LocaleOption) {
|
|
81
|
+
this.Locale = locale
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* 获取系统主题
|
|
85
|
+
* @returns 系统主题模式
|
|
86
|
+
*/
|
|
87
|
+
getSystemTheme(): ThemeMode {
|
|
88
|
+
try {
|
|
89
|
+
// #ifdef MP-WEIXIN
|
|
90
|
+
// 微信小程序使用 getAppBaseInfo
|
|
91
|
+
const appBaseInfo = uni.getAppBaseInfo()
|
|
92
|
+
if (appBaseInfo && appBaseInfo.theme) {
|
|
93
|
+
return appBaseInfo.theme as ThemeMode
|
|
94
|
+
}
|
|
95
|
+
// #endif
|
|
96
|
+
|
|
97
|
+
// #ifndef MP-WEIXIN
|
|
98
|
+
// 其他平台使用 getSystemInfoSync
|
|
99
|
+
const systemInfo = uni.getSystemInfoSync()
|
|
100
|
+
if (systemInfo && systemInfo.theme) {
|
|
101
|
+
return systemInfo.theme as ThemeMode
|
|
102
|
+
}
|
|
103
|
+
// #endif
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.warn('获取系统主题失败:', error)
|
|
107
|
+
}
|
|
108
|
+
return 'light' // 默认返回 light
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 初始化主题
|
|
113
|
+
*/
|
|
114
|
+
initTheme() {
|
|
115
|
+
// 如果用户已手动设置且不跟随系统,保持当前主题
|
|
116
|
+
if (this.hasUserSet && !this.followSystem) {
|
|
117
|
+
console.log('使用用户设置的主题:', this.theme)
|
|
118
|
+
this.setNavigationBarColor()
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 获取系统主题
|
|
123
|
+
const systemTheme = this.getSystemTheme()
|
|
124
|
+
|
|
125
|
+
// 如果是首次启动或跟随系统,使用系统主题
|
|
126
|
+
if (!this.hasUserSet || this.followSystem) {
|
|
127
|
+
this.theme = systemTheme
|
|
128
|
+
if (!this.hasUserSet) {
|
|
129
|
+
this.followSystem = true
|
|
130
|
+
console.log('首次启动,使用系统主题:', this.theme)
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log('跟随系统主题:', this.theme)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.setNavigationBarColor()
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
},
|
|
141
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import type { SystemThemeState, ThemeMode } from '../composables/types/theme'
|
|
3
|
+
import { themeColorOptions } from '../composables/types/theme'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 简化版系统主题状态管理
|
|
7
|
+
* 仅支持跟随系统主题,不提供手动切换功能
|
|
8
|
+
* 导航栏颜色通过 theme.json 自动处理
|
|
9
|
+
*/
|
|
10
|
+
export const useThemeStore = defineStore('theme', {
|
|
11
|
+
state: (): SystemThemeState => ({
|
|
12
|
+
theme: 'light',
|
|
13
|
+
themeVars: {
|
|
14
|
+
darkBackground: '#0f0f0f',
|
|
15
|
+
darkBackground2: '#1a1a1a',
|
|
16
|
+
darkBackground3: '#242424',
|
|
17
|
+
darkBackground4: '#2f2f2f',
|
|
18
|
+
darkBackground5: '#3d3d3d',
|
|
19
|
+
darkBackground6: '#4a4a4a',
|
|
20
|
+
darkBackground7: '#606060',
|
|
21
|
+
darkColor: '#ffffff',
|
|
22
|
+
darkColor2: '#e0e0e0',
|
|
23
|
+
darkColor3: '#a0a0a0',
|
|
24
|
+
colorTheme: themeColorOptions[0].primary,
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
getters: {
|
|
29
|
+
isDark: state => state.theme === 'dark',
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
actions: {
|
|
33
|
+
/**
|
|
34
|
+
* 获取系统主题
|
|
35
|
+
* @returns 系统主题模式
|
|
36
|
+
*/
|
|
37
|
+
getSystemTheme(): ThemeMode {
|
|
38
|
+
// #ifdef MP-WEIXIN
|
|
39
|
+
// 微信小程序使用 getAppBaseInfo
|
|
40
|
+
const appBaseInfo = uni.getAppBaseInfo()
|
|
41
|
+
if (appBaseInfo && appBaseInfo.theme) {
|
|
42
|
+
return appBaseInfo.theme as ThemeMode
|
|
43
|
+
}
|
|
44
|
+
// #endif
|
|
45
|
+
|
|
46
|
+
// #ifndef MP-WEIXIN
|
|
47
|
+
// 其他平台使用 getSystemInfoSync
|
|
48
|
+
const systemInfo = uni.getSystemInfoSync()
|
|
49
|
+
if (systemInfo && systemInfo.theme) {
|
|
50
|
+
return systemInfo.theme as ThemeMode
|
|
51
|
+
}
|
|
52
|
+
// #endif
|
|
53
|
+
|
|
54
|
+
return 'light' // 默认返回 light
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 设置主题(仅内部使用)
|
|
59
|
+
* @param theme 主题模式
|
|
60
|
+
*/
|
|
61
|
+
setTheme(theme: ThemeMode) {
|
|
62
|
+
this.theme = theme
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 初始化系统主题
|
|
67
|
+
*/
|
|
68
|
+
initSystemTheme() {
|
|
69
|
+
const systemTheme = this.getSystemTheme()
|
|
70
|
+
this.theme = systemTheme
|
|
71
|
+
console.log('初始化系统主题:', this.theme)
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
})
|