style: 代码格式化与统一样式变量
This commit is contained in:
@@ -80,7 +80,7 @@ const AuthAPI = {
|
||||
*/
|
||||
sendSmsLoginCode(mobile: string): Promise<void> {
|
||||
const mobileSafe = encodeURIComponent(mobile);
|
||||
return request<void>({
|
||||
return request({
|
||||
url: `${AUTH_BASE_URL}/sms/code?mobile=${mobileSafe}`,
|
||||
method: "POST",
|
||||
});
|
||||
@@ -153,7 +153,7 @@ const AuthAPI = {
|
||||
* 登出
|
||||
*/
|
||||
logout() {
|
||||
return request<void>({
|
||||
return request({
|
||||
url: `${AUTH_BASE_URL}/logout`,
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
@@ -211,8 +211,6 @@ function handleNodeAction(node: FlatNode) {
|
||||
emit("action", node.raw);
|
||||
}
|
||||
|
||||
// ===== 多选相关 =====
|
||||
|
||||
/** 判断节点是否选中(多选模式) */
|
||||
function isChecked(value: string) {
|
||||
return internalCheckedKeys.value.has(value);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed } from "vue"
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
/**
|
||||
* 加载状态管理
|
||||
@@ -26,63 +26,63 @@ import { ref, computed } from "vue"
|
||||
|
||||
export interface UseLoadingOptions {
|
||||
/** 是否使用全局单例,默认 true */
|
||||
singleton?: boolean
|
||||
singleton?: boolean;
|
||||
/** 初始加载状态,默认 false */
|
||||
initial?: boolean
|
||||
initial?: boolean;
|
||||
/** 加载提示消息 */
|
||||
message?: string
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface UseLoadingReturn {
|
||||
/** 是否处于加载状态 */
|
||||
isLoading: ReturnType<typeof computed<boolean>>
|
||||
isLoading: ReturnType<typeof computed<boolean>>;
|
||||
/** 加载计数(支持并发) */
|
||||
loadingCount: ReturnType<typeof ref<number>>
|
||||
loadingCount: ReturnType<typeof ref<number>>;
|
||||
/** 当前加载消息 */
|
||||
message: ReturnType<typeof ref<string>>
|
||||
message: ReturnType<typeof ref<string>>;
|
||||
/** 开始加载 */
|
||||
start: (msg?: string) => void
|
||||
start: (msg?: string) => void;
|
||||
/** 结束加载 */
|
||||
stop: () => void
|
||||
stop: () => void;
|
||||
/** 包装异步函数,自动管理加载状态 */
|
||||
wrap: <T>(promise: Promise<T>) => Promise<T>
|
||||
wrap: <T>(promise: Promise<T>) => Promise<T>;
|
||||
/** 设置加载消息 */
|
||||
setMessage: (msg: string) => void
|
||||
setMessage: (msg: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加载状态实例
|
||||
*/
|
||||
function createLoadingState(options: UseLoadingOptions = {}) {
|
||||
const { initial = false, message: initialMessage = "" } = options
|
||||
const { message: initialMessage = "" } = options;
|
||||
|
||||
const loadingCount = ref(0)
|
||||
const message = ref(initialMessage)
|
||||
const loadingCount = ref(0);
|
||||
const message = ref(initialMessage);
|
||||
|
||||
const isLoading = computed(() => loadingCount.value > 0)
|
||||
const isLoading = computed(() => loadingCount.value > 0);
|
||||
|
||||
/**
|
||||
* 开始加载
|
||||
* @param msg 加载提示消息
|
||||
*/
|
||||
const start = (msg?: string): void => {
|
||||
loadingCount.value++
|
||||
loadingCount.value++;
|
||||
if (msg) {
|
||||
message.value = msg
|
||||
message.value = msg;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 结束加载
|
||||
*/
|
||||
const stop = (): void => {
|
||||
if (loadingCount.value > 0) {
|
||||
loadingCount.value--
|
||||
loadingCount.value--;
|
||||
}
|
||||
if (loadingCount.value === 0) {
|
||||
message.value = ""
|
||||
message.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 包装异步函数,自动管理加载状态
|
||||
@@ -90,20 +90,20 @@ function createLoadingState(options: UseLoadingOptions = {}) {
|
||||
*/
|
||||
const wrap = async <T>(promise: Promise<T>): Promise<T> => {
|
||||
try {
|
||||
start()
|
||||
return await promise
|
||||
start();
|
||||
return await promise;
|
||||
} finally {
|
||||
stop()
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置加载消息
|
||||
* @param msg 消息内容
|
||||
*/
|
||||
const setMessage = (msg: string): void => {
|
||||
message.value = msg
|
||||
}
|
||||
message.value = msg;
|
||||
};
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
@@ -113,11 +113,11 @@ function createLoadingState(options: UseLoadingOptions = {}) {
|
||||
stop,
|
||||
wrap,
|
||||
setMessage,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 全局单例实例 */
|
||||
let loadingInstance: UseLoadingReturn | null = null
|
||||
let loadingInstance: UseLoadingReturn | null = null;
|
||||
|
||||
/**
|
||||
* 加载状态管理 Hook
|
||||
@@ -128,14 +128,14 @@ let loadingInstance: UseLoadingReturn | null = null
|
||||
* @param options 配置选项
|
||||
*/
|
||||
export function useLoading(options?: UseLoadingOptions): UseLoadingReturn {
|
||||
const { singleton = true } = options ?? {}
|
||||
const { singleton = true } = options ?? {};
|
||||
|
||||
if (singleton) {
|
||||
if (!loadingInstance) {
|
||||
loadingInstance = createLoadingState(options)
|
||||
loadingInstance = createLoadingState(options);
|
||||
}
|
||||
return loadingInstance
|
||||
return loadingInstance;
|
||||
}
|
||||
|
||||
return createLoadingState(options)
|
||||
return createLoadingState(options);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export interface UseNavbarReturn {
|
||||
* @param options 配置选项
|
||||
*/
|
||||
export function useNavbar(options: UseNavbarOptions = {}): UseNavbarReturn {
|
||||
const { navBarHeight = 44, hasTabbar = false } = options;
|
||||
const { navBarHeight = 44 } = options;
|
||||
|
||||
// 状态栏高度
|
||||
const statusBarHeight = ref(0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed, shallowRef, type Ref, type ComputedRef } from "vue"
|
||||
import { ref, computed, shallowRef, type Ref, type ComputedRef } from "vue";
|
||||
|
||||
/**
|
||||
* 请求状态管理
|
||||
@@ -24,36 +24,36 @@ import { ref, computed, shallowRef, type Ref, type ComputedRef } from "vue"
|
||||
|
||||
export interface UseRequestOptions<T> {
|
||||
/** 是否立即执行,默认 false */
|
||||
immediate?: boolean
|
||||
immediate?: boolean;
|
||||
/** 初始数据 */
|
||||
initialData?: T | null
|
||||
initialData?: T | null;
|
||||
/** 请求前的回调 */
|
||||
onBefore?: () => void
|
||||
onBefore?: () => void;
|
||||
/** 请求成功的回调 */
|
||||
onSuccess?: (data: T) => void
|
||||
onSuccess?: (data: T) => void;
|
||||
/** 请求失败的回调 */
|
||||
onError?: (error: Error) => void
|
||||
onError?: (error: Error) => void;
|
||||
/** 请求完成的回调(无论成功失败) */
|
||||
onFinally?: () => void
|
||||
onFinally?: () => void;
|
||||
/** 是否重置数据在重新请求时,默认 false */
|
||||
resetOnExecute?: boolean
|
||||
resetOnExecute?: boolean;
|
||||
}
|
||||
|
||||
export interface UseRequestReturn<T> {
|
||||
/** 响应数据 */
|
||||
data: Ref<T | null>
|
||||
data: Ref<T | null>;
|
||||
/** 是否正在加载 */
|
||||
loading: ComputedRef<boolean>
|
||||
loading: ComputedRef<boolean>;
|
||||
/** 错误信息 */
|
||||
error: Ref<Error | null>
|
||||
error: Ref<Error | null>;
|
||||
/** 是否已请求过(用于区分初始状态和请求后状态) */
|
||||
hasExecuted: Ref<boolean>
|
||||
hasExecuted: Ref<boolean>;
|
||||
/** 执行请求 */
|
||||
execute: () => Promise<T | null>
|
||||
execute: () => Promise<T | null>;
|
||||
/** 刷新数据(重新执行请求) */
|
||||
refresh: () => Promise<T | null>
|
||||
refresh: () => Promise<T | null>;
|
||||
/** 重置状态 */
|
||||
reset: () => void
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,15 +74,15 @@ export function useRequest<T>(
|
||||
onError,
|
||||
onFinally,
|
||||
resetOnExecute = false,
|
||||
} = options
|
||||
} = options;
|
||||
|
||||
// 使用 shallowRef 避免深层响应式,提升性能
|
||||
const data = shallowRef<T | null>(initialData) as Ref<T | null>
|
||||
const error = ref<Error | null>(null)
|
||||
const loadingCount = ref(0)
|
||||
const hasExecuted = ref(false)
|
||||
const data = shallowRef<T | null>(initialData) as Ref<T | null>;
|
||||
const error = ref<Error | null>(null);
|
||||
const loadingCount = ref(0);
|
||||
const hasExecuted = ref(false);
|
||||
|
||||
const loading = computed(() => loadingCount.value > 0)
|
||||
const loading = computed(() => loadingCount.value > 0);
|
||||
|
||||
/**
|
||||
* 执行请求
|
||||
@@ -90,51 +90,51 @@ export function useRequest<T>(
|
||||
const execute = async (): Promise<T | null> => {
|
||||
// 重置状态
|
||||
if (resetOnExecute) {
|
||||
data.value = initialData
|
||||
error.value = null
|
||||
data.value = initialData;
|
||||
error.value = null;
|
||||
}
|
||||
|
||||
loadingCount.value++
|
||||
hasExecuted.value = true
|
||||
loadingCount.value++;
|
||||
hasExecuted.value = true;
|
||||
|
||||
try {
|
||||
onBefore?.()
|
||||
const result = await requestFn()
|
||||
data.value = result
|
||||
error.value = null
|
||||
onSuccess?.(result)
|
||||
return result
|
||||
onBefore?.();
|
||||
const result = await requestFn();
|
||||
data.value = result;
|
||||
error.value = null;
|
||||
onSuccess?.(result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
const err = e instanceof Error ? e : new Error(String(e))
|
||||
error.value = err
|
||||
onError?.(err)
|
||||
return null
|
||||
const err = e instanceof Error ? e : new Error(String(e));
|
||||
error.value = err;
|
||||
onError?.(err);
|
||||
return null;
|
||||
} finally {
|
||||
loadingCount.value--
|
||||
onFinally?.()
|
||||
loadingCount.value--;
|
||||
onFinally?.();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
*/
|
||||
const refresh = async (): Promise<T | null> => {
|
||||
return execute()
|
||||
}
|
||||
return execute();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
const reset = (): void => {
|
||||
data.value = initialData
|
||||
error.value = null
|
||||
loadingCount.value = 0
|
||||
hasExecuted.value = false
|
||||
}
|
||||
data.value = initialData;
|
||||
error.value = null;
|
||||
loadingCount.value = 0;
|
||||
hasExecuted.value = false;
|
||||
};
|
||||
|
||||
// 立即执行
|
||||
if (immediate) {
|
||||
execute()
|
||||
execute();
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -145,7 +145,7 @@ export function useRequest<T>(
|
||||
execute,
|
||||
refresh,
|
||||
reset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,42 +167,42 @@ export function useRequest<T>(
|
||||
*/
|
||||
|
||||
export interface PaginationParams {
|
||||
pageNum: number
|
||||
pageSize: number
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface UsePaginationOptions<T> {
|
||||
/** 每页条数,默认 10 */
|
||||
pageSize?: number
|
||||
pageSize?: number;
|
||||
/** 初始数据 */
|
||||
initialData?: T[]
|
||||
initialData?: T[];
|
||||
/** 请求成功的回调 */
|
||||
onSuccess?: (list: T[], total: number) => void
|
||||
onSuccess?: (list: T[], total: number) => void;
|
||||
/** 请求失败的回调 */
|
||||
onError?: (error: Error) => void
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
export interface UsePaginationReturn<T> {
|
||||
/** 数据列表 */
|
||||
list: Ref<T[]>
|
||||
list: Ref<T[]>;
|
||||
/** 是否正在加载 */
|
||||
loading: ComputedRef<boolean>
|
||||
loading: ComputedRef<boolean>;
|
||||
/** 错误信息 */
|
||||
error: Ref<Error | null>
|
||||
error: Ref<Error | null>;
|
||||
/** 是否还有更多数据 */
|
||||
hasMore: ComputedRef<boolean>
|
||||
hasMore: ComputedRef<boolean>;
|
||||
/** 当前页码 */
|
||||
pageNum: Ref<number>
|
||||
pageNum: Ref<number>;
|
||||
/** 总条数 */
|
||||
total: Ref<number>
|
||||
total: Ref<number>;
|
||||
/** 是否为空 */
|
||||
isEmpty: ComputedRef<boolean>
|
||||
isEmpty: ComputedRef<boolean>;
|
||||
/** 加载更多 */
|
||||
loadMore: () => Promise<T[]>
|
||||
loadMore: () => Promise<T[]>;
|
||||
/** 刷新(从第一页开始) */
|
||||
refresh: () => Promise<T[]>
|
||||
refresh: () => Promise<T[]>;
|
||||
/** 重置 */
|
||||
reset: () => void
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,17 +215,17 @@ export function usePagination<T>(
|
||||
requestFn: (params: PaginationParams) => Promise<PageResult<T[]>>,
|
||||
options: UsePaginationOptions<T> = {}
|
||||
): UsePaginationReturn<T> {
|
||||
const { pageSize = 10, initialData = [], onSuccess, onError } = options
|
||||
const { pageSize = 10, initialData = [], onSuccess, onError } = options;
|
||||
|
||||
const list = ref<T[]>(initialData) as Ref<T[]>
|
||||
const loadingCount = ref(0)
|
||||
const error = ref<Error | null>(null)
|
||||
const pageNum = ref(1)
|
||||
const total = ref(0)
|
||||
const list = ref<T[]>(initialData) as Ref<T[]>;
|
||||
const loadingCount = ref(0);
|
||||
const error = ref<Error | null>(null);
|
||||
const pageNum = ref(1);
|
||||
const total = ref(0);
|
||||
|
||||
const loading = computed(() => loadingCount.value > 0)
|
||||
const hasMore = computed(() => list.value.length < total.value)
|
||||
const isEmpty = computed(() => list.value.length === 0 && !loading.value)
|
||||
const loading = computed(() => loadingCount.value > 0);
|
||||
const hasMore = computed(() => list.value.length < total.value);
|
||||
const isEmpty = computed(() => list.value.length === 0 && !loading.value);
|
||||
|
||||
/**
|
||||
* 加载更多
|
||||
@@ -233,59 +233,59 @@ export function usePagination<T>(
|
||||
const loadMore = async (): Promise<T[]> => {
|
||||
// 如果正在加载或没有更多数据,直接返回
|
||||
if (loading.value || !hasMore.value) {
|
||||
return list.value
|
||||
return list.value;
|
||||
}
|
||||
|
||||
loadingCount.value++
|
||||
loadingCount.value++;
|
||||
|
||||
try {
|
||||
const result = await requestFn({
|
||||
pageNum: pageNum.value,
|
||||
pageSize,
|
||||
})
|
||||
});
|
||||
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
if (pageNum.value === 1) {
|
||||
list.value = result.list
|
||||
list.value = result.list;
|
||||
} else {
|
||||
list.value = [...list.value, ...result.list]
|
||||
list.value = [...list.value, ...result.list];
|
||||
}
|
||||
|
||||
total.value = result.total
|
||||
pageNum.value++
|
||||
onSuccess?.(result.list, result.total)
|
||||
total.value = result.total;
|
||||
pageNum.value++;
|
||||
onSuccess?.(result.list, result.total);
|
||||
|
||||
return result.list
|
||||
return result.list;
|
||||
} catch (e) {
|
||||
const err = e instanceof Error ? e : new Error(String(e))
|
||||
error.value = err
|
||||
onError?.(err)
|
||||
return []
|
||||
const err = e instanceof Error ? e : new Error(String(e));
|
||||
error.value = err;
|
||||
onError?.(err);
|
||||
return [];
|
||||
} finally {
|
||||
loadingCount.value--
|
||||
loadingCount.value--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新(从第一页开始)
|
||||
*/
|
||||
const refresh = async (): Promise<T[]> => {
|
||||
pageNum.value = 1
|
||||
total.value = 0
|
||||
list.value = []
|
||||
return loadMore()
|
||||
}
|
||||
pageNum.value = 1;
|
||||
total.value = 0;
|
||||
list.value = [];
|
||||
return loadMore();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
const reset = (): void => {
|
||||
list.value = initialData
|
||||
loadingCount.value = 0
|
||||
error.value = null
|
||||
pageNum.value = 1
|
||||
total.value = 0
|
||||
}
|
||||
list.value = initialData;
|
||||
loadingCount.value = 0;
|
||||
error.value = null;
|
||||
pageNum.value = 1;
|
||||
total.value = 0;
|
||||
};
|
||||
|
||||
return {
|
||||
list,
|
||||
@@ -298,5 +298,5 @@ export function usePagination<T>(
|
||||
loadMore,
|
||||
refresh,
|
||||
reset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed } from "vue"
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
/**
|
||||
* TabBar 状态管理
|
||||
@@ -19,15 +19,15 @@
|
||||
|
||||
export interface TabbarItem {
|
||||
/** 唯一标识 */
|
||||
name: string
|
||||
name: string;
|
||||
/** 徽标数字,null 表示不显示 */
|
||||
value: number | null
|
||||
value: number | null;
|
||||
/** 是否激活 */
|
||||
active: boolean
|
||||
active: boolean;
|
||||
/** 显示标题 */
|
||||
title: string
|
||||
title: string;
|
||||
/** 图标名称 */
|
||||
icon: string
|
||||
icon: string;
|
||||
}
|
||||
|
||||
/** 默认 TabBar 配置 */
|
||||
@@ -35,7 +35,7 @@ const DEFAULT_TABBAR_ITEMS: Omit<TabbarItem, "active">[] = [
|
||||
{ name: "home", value: null, title: "首页", icon: "home" },
|
||||
{ name: "work", value: null, title: "工作台", icon: "laptop" },
|
||||
{ name: "mine", value: null, title: "我的", icon: "user" },
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* 创建 TabBar 状态实例
|
||||
@@ -49,24 +49,24 @@ function createTabbarState() {
|
||||
...item,
|
||||
active: index === 0,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
/** TabBar 列表 */
|
||||
const tabbarList = computed(() => items.value)
|
||||
const tabbarList = computed(() => items.value);
|
||||
|
||||
/** 当前激活的 TabBar 项 */
|
||||
const activeTabbar = computed(() => {
|
||||
return items.value.find((item) => item.active) || items.value[0]
|
||||
})
|
||||
return items.value.find((item) => item.active) || items.value[0];
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取指定 TabBar 项的徽标数字
|
||||
* @param name TabBar 项名称
|
||||
*/
|
||||
const getTabbarItemValue = (name: string): number | null => {
|
||||
const item = items.value.find((item) => item.name === name)
|
||||
return item?.value ?? null
|
||||
}
|
||||
const item = items.value.find((item) => item.name === name);
|
||||
return item?.value ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置 TabBar 项的徽标数字
|
||||
@@ -74,11 +74,11 @@ function createTabbarState() {
|
||||
* @param value 徽标数字
|
||||
*/
|
||||
const setTabbarItem = (name: string, value: number): void => {
|
||||
const item = items.value.find((item) => item.name === name)
|
||||
const item = items.value.find((item) => item.name === name);
|
||||
if (item) {
|
||||
item.value = value
|
||||
item.value = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置激活的 TabBar 项
|
||||
@@ -86,9 +86,9 @@ function createTabbarState() {
|
||||
*/
|
||||
const setTabbarItemActive = (name: string): void => {
|
||||
items.value.forEach((item) => {
|
||||
item.active = item.name === name
|
||||
})
|
||||
}
|
||||
item.active = item.name === name;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置 TabBar 状态
|
||||
@@ -97,8 +97,8 @@ function createTabbarState() {
|
||||
items.value = DEFAULT_TABBAR_ITEMS.map((item, index) => ({
|
||||
...item,
|
||||
active: index === 0,
|
||||
}))
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
tabbarList,
|
||||
@@ -107,11 +107,11 @@ function createTabbarState() {
|
||||
setTabbarItem,
|
||||
setTabbarItemActive,
|
||||
resetTabbar,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 全局单例实例 */
|
||||
let tabbarInstance: ReturnType<typeof createTabbarState> | null = null
|
||||
let tabbarInstance: ReturnType<typeof createTabbarState> | null = null;
|
||||
|
||||
/**
|
||||
* TabBar 状态管理 Hook
|
||||
@@ -123,14 +123,14 @@ let tabbarInstance: ReturnType<typeof createTabbarState> | null = null
|
||||
* @param options.singleton 是否使用单例模式,默认 true
|
||||
*/
|
||||
export function useTabbar(options?: { singleton?: boolean }) {
|
||||
const { singleton = true } = options ?? {}
|
||||
const { singleton = true } = options ?? {};
|
||||
|
||||
if (singleton) {
|
||||
if (!tabbarInstance) {
|
||||
tabbarInstance = createTabbarState()
|
||||
tabbarInstance = createTabbarState();
|
||||
}
|
||||
return tabbarInstance
|
||||
return tabbarInstance;
|
||||
}
|
||||
|
||||
return createTabbarState()
|
||||
return createTabbarState();
|
||||
}
|
||||
|
||||
@@ -105,10 +105,6 @@ import { checkLogin, isLoggedIn } from "@/utils/auth";
|
||||
import LogAPI, { type VisitOverview as ApiVisitOverview, type VisitTrend } from "@/api/log";
|
||||
import NoticeAPI, { type NoticeItem } from "@/api/notice";
|
||||
|
||||
// ============================================================================
|
||||
// 类型定义
|
||||
// ============================================================================
|
||||
|
||||
type VisitOverviewVO = ApiVisitOverview;
|
||||
|
||||
interface NavItem {
|
||||
@@ -118,22 +114,17 @@ interface NavItem {
|
||||
perm: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hooks
|
||||
// ============================================================================
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
// custom-navbar 组件内部已处理导航栏高度与胶囊避让
|
||||
|
||||
// ============================================================================
|
||||
// 响应式数据
|
||||
// ============================================================================
|
||||
|
||||
const current = ref(0);
|
||||
const recentDaysRange = ref(7);
|
||||
|
||||
const swiperList = ref(["https://www.youlai.tech/storage/youlai/bg02.png" ,"https://www.youlai.tech/storage/blog/banner9.png" ]);
|
||||
const swiperList = ref([
|
||||
"https://www.youlai.tech/storage/youlai/bg02.png",
|
||||
"https://www.youlai.tech/storage/blog/banner9.png",
|
||||
]);
|
||||
|
||||
const visitOverviewData = ref<VisitOverviewVO>({
|
||||
todayUvCount: 0,
|
||||
@@ -228,10 +219,6 @@ const chartOpts = ref({
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// 数据加载
|
||||
// ============================================================================
|
||||
|
||||
function loadAppVersion() {
|
||||
try {
|
||||
const p: any = (globalThis as any).plus;
|
||||
@@ -287,10 +274,6 @@ async function loadVisitTrendData() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 事件处理
|
||||
// ============================================================================
|
||||
|
||||
function handleNavClick(item: NavItem) {
|
||||
// 未登录 / 无权限时:展示默认导航,但点击统一跳登录
|
||||
if (!isLogged.value || !hasAnyPerm.value) {
|
||||
@@ -332,10 +315,6 @@ function handleDataRangeChange({ value }: { value: number }) {
|
||||
loadVisitTrendData();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 生命周期
|
||||
// ============================================================================
|
||||
|
||||
onReady(() => {
|
||||
loadAppVersion();
|
||||
loadNoticeData();
|
||||
@@ -491,13 +470,13 @@ onShow(() => {
|
||||
|
||||
&--uv,
|
||||
&--green {
|
||||
background: #34d19d;
|
||||
background: var(--color-success);
|
||||
box-shadow: 0 0 10rpx rgba(52, 209, 157, 0.35);
|
||||
}
|
||||
|
||||
&--pv,
|
||||
&--blue {
|
||||
background: #4d80f0;
|
||||
background: var(--color-primary);
|
||||
box-shadow: 0 0 10rpx rgba(77, 128, 240, 0.3);
|
||||
}
|
||||
}
|
||||
@@ -512,12 +491,12 @@ onShow(() => {
|
||||
|
||||
&--uv,
|
||||
&--green {
|
||||
color: #34d19d;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
&--pv,
|
||||
&--blue {
|
||||
color: #4d80f0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,9 @@
|
||||
/>
|
||||
<view
|
||||
class="login__code-btn"
|
||||
:class="bindSmsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
|
||||
:class="
|
||||
bindSmsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'
|
||||
"
|
||||
@click="handleSendBindCode"
|
||||
>
|
||||
{{ bindSmsCountdown > 0 ? `${bindSmsCountdown}s` : "获取验证码" }}
|
||||
@@ -235,10 +237,14 @@
|
||||
|
||||
<!-- 协议确认弹窗 -->
|
||||
<wd-message-box selector="policy-box" root-portal>
|
||||
<view class="text-center text-sm text-gray-500 leading-relaxed">
|
||||
<view class="policy-dialog__content">
|
||||
请阅读并同意有来技术
|
||||
<text class="text-blue-500" @click.stop="navigateToAgreement('user')">《用户协议》</text>
|
||||
<text class="text-blue-500" @click.stop="navigateToAgreement('privacy')">《隐私政策》</text>
|
||||
<text class="policy-dialog__link" @click.stop="navigateToAgreement('user')">
|
||||
《用户协议》
|
||||
</text>
|
||||
<text class="policy-dialog__link" @click.stop="navigateToAgreement('privacy')">
|
||||
《隐私政策》
|
||||
</text>
|
||||
</view>
|
||||
</wd-message-box>
|
||||
|
||||
@@ -305,7 +311,11 @@ const pendingWechatPhoneCode = ref("");
|
||||
|
||||
// 计算属性
|
||||
const loginModeDesc = computed(() => {
|
||||
const modeMap = { PASSWORD: "使用账号密码登录", SMS: "使用手机验证码登录", WECHAT: "使用微信快捷登录" };
|
||||
const modeMap = {
|
||||
PASSWORD: "使用账号密码登录",
|
||||
SMS: "使用手机验证码登录",
|
||||
WECHAT: "使用微信快捷登录",
|
||||
};
|
||||
return modeMap[loginMode.value];
|
||||
});
|
||||
|
||||
@@ -348,7 +358,10 @@ const startSmsCountdown = (
|
||||
countdown.value -= 1;
|
||||
if (countdown.value <= 0) {
|
||||
countdown.value = 0;
|
||||
if (timer.value) { clearInterval(timer.value); timer.value = null; }
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
@@ -394,7 +407,9 @@ const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode = "") => {
|
||||
// 表单登录
|
||||
async function doFormLogin() {
|
||||
if (!canSubmit.value) {
|
||||
toast.error(loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码");
|
||||
toast.error(
|
||||
loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (loading.value) return;
|
||||
@@ -408,7 +423,10 @@ async function doFormLogin() {
|
||||
captchaCode: formData.value.captchaCode,
|
||||
});
|
||||
} else {
|
||||
await userStore.loginBySms({ mobile: formData.value.username.trim(), code: formData.value.code });
|
||||
await userStore.loginBySms({
|
||||
mobile: formData.value.username.trim(),
|
||||
code: formData.value.code,
|
||||
});
|
||||
}
|
||||
await userStore.getInfo();
|
||||
toast.success("登录成功");
|
||||
@@ -422,15 +440,24 @@ async function doFormLogin() {
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!isAgreePolicy.value) { openPolicyDialog("FORM"); return; }
|
||||
if (!isAgreePolicy.value) {
|
||||
openPolicyDialog("FORM");
|
||||
return;
|
||||
}
|
||||
await doFormLogin();
|
||||
};
|
||||
|
||||
const handleSendCode = async () => {
|
||||
if (smsCountdown.value > 0) return;
|
||||
const mobile = formData.value.username.trim();
|
||||
if (!mobile) { toast.error("请输入手机号"); return; }
|
||||
if (!isValidMobile(mobile)) { toast.error("请输入正确的手机号"); return; }
|
||||
if (!mobile) {
|
||||
toast.error("请输入手机号");
|
||||
return;
|
||||
}
|
||||
if (!isValidMobile(mobile)) {
|
||||
toast.error("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AuthAPI.sendSmsLoginCode(mobile);
|
||||
toast.success("验证码已发送");
|
||||
@@ -443,13 +470,22 @@ const handleSendCode = async () => {
|
||||
// 微信登录
|
||||
const handleWechatPhoneLogin = async (e: any) => {
|
||||
const phoneCode = e.detail.code;
|
||||
if (!isAgreePolicy.value) { openPolicyDialog("WECHAT_PHONE", phoneCode); return; }
|
||||
if (!phoneCode) { await handleWechatSilentLogin(); return; }
|
||||
if (!isAgreePolicy.value) {
|
||||
openPolicyDialog("WECHAT_PHONE", phoneCode);
|
||||
return;
|
||||
}
|
||||
if (!phoneCode) {
|
||||
await handleWechatSilentLogin();
|
||||
return;
|
||||
}
|
||||
await doWechatPhoneLogin(phoneCode);
|
||||
};
|
||||
|
||||
async function doWechatPhoneLogin(phoneCode: string) {
|
||||
if (!phoneCode) { await handleWechatSilentLogin(); return; }
|
||||
if (!phoneCode) {
|
||||
await handleWechatSilentLogin();
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const { code: loginCode } = await uni.login();
|
||||
@@ -489,7 +525,10 @@ const handleWechatSilentLogin = async () => {
|
||||
const handleSendBindCode = async () => {
|
||||
if (bindSmsCountdown.value > 0) return;
|
||||
const mobile = bindMobileForm.value.mobile.trim();
|
||||
if (!isValidMobile(mobile)) { toast.error("请输入正确的手机号"); return; }
|
||||
if (!isValidMobile(mobile)) {
|
||||
toast.error("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AuthAPI.sendSmsLoginCode(mobile);
|
||||
toast.success("验证码已发送");
|
||||
@@ -502,14 +541,23 @@ const handleSendBindCode = async () => {
|
||||
const resetBindForm = () => {
|
||||
bindMobileForm.value = { mobile: "", code: "" };
|
||||
bindSmsCountdown.value = 0;
|
||||
if (bindSmsTimer.value) { clearInterval(bindSmsTimer.value); bindSmsTimer.value = null; }
|
||||
if (bindSmsTimer.value) {
|
||||
clearInterval(bindSmsTimer.value);
|
||||
bindSmsTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleBindMobile = async () => {
|
||||
if (bindLoading.value) return;
|
||||
const { mobile, code } = bindMobileForm.value;
|
||||
if (!isValidMobile(mobile)) { toast.error("请输入正确的手机号"); return; }
|
||||
if (!code.trim()) { toast.error("请输入验证码"); return; }
|
||||
if (!isValidMobile(mobile)) {
|
||||
toast.error("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
if (!code.trim()) {
|
||||
toast.error("请输入验证码");
|
||||
return;
|
||||
}
|
||||
bindLoading.value = true;
|
||||
try {
|
||||
await userStore.bindMobileForWxMa({ openid: wechatOpenid.value, mobile, smsCode: code });
|
||||
@@ -526,12 +574,16 @@ const handleBindMobile = async () => {
|
||||
};
|
||||
|
||||
const navigateToAgreement = (type: string) => {
|
||||
const url = type === "user" ? "/pages/mine/settings/agreement/index" : "/pages/mine/settings/privacy/index";
|
||||
const url =
|
||||
type === "user" ? "/pages/mine/settings/agreement/index" : "/pages/mine/settings/privacy/index";
|
||||
uni.navigateTo({ url });
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (getCurrentPages().length > 1) { uni.navigateBack(); return; }
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
uni.reLaunch({ url: "/pages/index/index" });
|
||||
};
|
||||
|
||||
@@ -562,9 +614,6 @@ onUnload(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// ==========================================================================
|
||||
// 页面背景
|
||||
// ==========================================================================
|
||||
.login {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
@@ -574,9 +623,6 @@ onUnload(() => {
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 背景装饰(模糊光晕圆)
|
||||
// ==========================================================================
|
||||
.login__decoration {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -597,7 +643,7 @@ onUnload(() => {
|
||||
left: -160rpx;
|
||||
width: 480rpx;
|
||||
height: 480rpx;
|
||||
background-color: rgba(96, 165, 250, 0.2);
|
||||
background-color: var(--color-primary-alpha-20);
|
||||
}
|
||||
|
||||
&--2 {
|
||||
@@ -605,13 +651,10 @@ onUnload(() => {
|
||||
bottom: -160rpx;
|
||||
width: 640rpx;
|
||||
height: 640rpx;
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
background-color: var(--color-primary-alpha-15);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 导航栏
|
||||
// ==========================================================================
|
||||
.login__navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -642,8 +685,8 @@ onUnload(() => {
|
||||
|
||||
.login__navbar-btn {
|
||||
left: 0;
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.35);
|
||||
background-color: var(--color-glass);
|
||||
border: 2rpx solid var(--color-border-glass);
|
||||
border-radius: 999rpx;
|
||||
|
||||
&--active {
|
||||
@@ -660,19 +703,16 @@ onUnload(() => {
|
||||
font-size: 44rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: rgba(15, 23, 42, 0.92);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.login__navbar-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgba(15, 23, 42, 0.92);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 主内容区 —— 普通流式布局,不需要高 z-index
|
||||
// ==========================================================================
|
||||
.login__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -680,9 +720,6 @@ onUnload(() => {
|
||||
padding: 0 96rpx;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 品牌 Logo
|
||||
// ==========================================================================
|
||||
.login__brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -704,13 +741,10 @@ onUnload(() => {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 登录卡片
|
||||
// ==========================================================================
|
||||
.login__card {
|
||||
width: 100%;
|
||||
padding: 64rpx;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
background-color: var(--color-bg-alpha-95);
|
||||
backdrop-filter: blur(24px);
|
||||
border-radius: 48rpx;
|
||||
box-shadow: 0 20rpx 50rpx -10rpx rgba(0, 0, 0, 0.1);
|
||||
@@ -735,9 +769,6 @@ onUnload(() => {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 表单区域
|
||||
// ==========================================================================
|
||||
.login__form-item {
|
||||
margin-top: 32rpx;
|
||||
|
||||
@@ -771,9 +802,6 @@ onUnload(() => {
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 微信登录按钮
|
||||
// ==========================================================================
|
||||
.login__wx-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -798,9 +826,6 @@ onUnload(() => {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 验证码按钮
|
||||
// ==========================================================================
|
||||
.login__code-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -821,9 +846,6 @@ onUnload(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 登录方式切换
|
||||
// ==========================================================================
|
||||
.login__mode-switch {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -843,9 +865,6 @@ onUnload(() => {
|
||||
border-bottom: 2rpx solid var(--color-primary);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 分割线
|
||||
// ==========================================================================
|
||||
.login__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -864,9 +883,6 @@ onUnload(() => {
|
||||
color: var(--color-text-placeholder);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 第三方登录入口
|
||||
// ==========================================================================
|
||||
.login__oauth-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -882,9 +898,6 @@ onUnload(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 协议勾选
|
||||
// ==========================================================================
|
||||
.login__policy {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -904,9 +917,17 @@ onUnload(() => {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 绑定手机号弹窗
|
||||
// ==========================================================================
|
||||
.policy-dialog__content {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.625;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.policy-dialog__link {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.login__bind-panel {
|
||||
padding: 48rpx;
|
||||
}
|
||||
@@ -920,9 +941,6 @@ onUnload(() => {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 演示环境提示
|
||||
// ==========================================================================
|
||||
.login__demo-hint {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -334,9 +334,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 头部区域(Logo + 应用信息)
|
||||
// ==========================================================================
|
||||
.about__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="page account-page">
|
||||
<view class="page py-16rpx">
|
||||
<wd-card>
|
||||
<wd-cell-group border>
|
||||
<wd-cell
|
||||
@@ -73,7 +73,7 @@
|
||||
:rules="rules.confirmPassword"
|
||||
/>
|
||||
</wd-cell-group>
|
||||
<view class="account-page__submit">
|
||||
<view class="p-24rpx">
|
||||
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
|
||||
</view>
|
||||
</wd-form>
|
||||
@@ -113,7 +113,7 @@
|
||||
</template>
|
||||
</wd-input>
|
||||
</wd-cell-group>
|
||||
<view class="account-page__submit">
|
||||
<view class="p-24rpx">
|
||||
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
|
||||
</view>
|
||||
</wd-form>
|
||||
@@ -153,7 +153,7 @@
|
||||
</template>
|
||||
</wd-input>
|
||||
</wd-cell-group>
|
||||
<view class="account-page__submit">
|
||||
<view class="p-24rpx">
|
||||
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
|
||||
</view>
|
||||
</wd-form>
|
||||
@@ -359,12 +359,3 @@ onUnmounted(() => {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.account-page {
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.account-page__submit {
|
||||
padding: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="community-card__mask" />
|
||||
<view class="community-card__sparkle" />
|
||||
<view class="community-card__logo">
|
||||
<image src="/static/logo.png" mode="aspectFit" class="w-full h-full" />
|
||||
</view>
|
||||
@@ -260,8 +259,8 @@ const genderIconName = computed(() => {
|
||||
});
|
||||
|
||||
const genderIconClass = computed(() => {
|
||||
if (normalizedGender.value === 1) return "gender-icon--male";
|
||||
if (normalizedGender.value === 2) return "gender-icon--female";
|
||||
if (normalizedGender.value === 1) return "profile-card__gender--male";
|
||||
if (normalizedGender.value === 2) return "profile-card__gender--female";
|
||||
return "";
|
||||
});
|
||||
|
||||
@@ -492,26 +491,14 @@ const openOfficialAccount = () => {
|
||||
position: relative;
|
||||
padding: 28rpx 28rpx;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0.12) 100%);
|
||||
background: linear-gradient(135deg, var(--color-glass) 0%, var(--color-glass-light) 100%);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.35);
|
||||
border: 1rpx solid var(--color-border-glass);
|
||||
border-radius: 28rpx;
|
||||
box-shadow:
|
||||
0 8rpx 32rpx rgba(0, 0, 0, 0.08),
|
||||
0 2rpx 8rpx rgba(0, 0, 0, 0.04),
|
||||
inset 0 1rpx 0 rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.profile-card::before {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -20%;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.25) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
inset 0 1rpx 0 var(--color-glass);
|
||||
}
|
||||
|
||||
.profile-card__header {
|
||||
@@ -530,7 +517,7 @@ const openOfficialAccount = () => {
|
||||
flex-shrink: 0;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.9);
|
||||
border: 3rpx solid var(--color-border-glass-strong);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -553,7 +540,7 @@ const openOfficialAccount = () => {
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
background: var(--color-success);
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.95);
|
||||
border: 3rpx solid var(--color-border-glass-strong);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
@@ -574,9 +561,9 @@ const openOfficialAccount = () => {
|
||||
justify-content: center;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
background: var(--color-glass-light);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.22);
|
||||
border: 1rpx solid var(--color-border-glass);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
@@ -593,15 +580,15 @@ const openOfficialAccount = () => {
|
||||
font-size: 18rpx;
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--color-danger);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.95);
|
||||
border: 2rpx solid var(--color-border-glass-strong);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.gender-icon--male {
|
||||
.profile-card__gender--male {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.gender-icon--female {
|
||||
.profile-card__gender--female {
|
||||
background: var(--color-danger);
|
||||
}
|
||||
|
||||
@@ -621,8 +608,7 @@ const openOfficialAccount = () => {
|
||||
min-width: 0;
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.98);
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.profile-card__desc {
|
||||
@@ -631,7 +617,7 @@ const openOfficialAccount = () => {
|
||||
overflow: hidden;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
color: var(--color-text-inverse);
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -641,7 +627,7 @@ const openOfficialAccount = () => {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.profile-card__tags {
|
||||
@@ -658,9 +644,9 @@ const openOfficialAccount = () => {
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 14rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--color-glass-light);
|
||||
border: 1rpx solid var(--color-border-glass);
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
@@ -708,27 +694,12 @@ const openOfficialAccount = () => {
|
||||
z-index: var(--z-sticky);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.62) 0%,
|
||||
rgba(255, 255, 255, 0.34) 55%,
|
||||
rgba(255, 255, 255, 0.58) 100%
|
||||
var(--color-glass) 0%,
|
||||
var(--color-glass-light) 55%,
|
||||
var(--color-glass) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.community-card__sparkle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: var(--z-sticky);
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(circle at 18% 30%, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0) 52%),
|
||||
radial-gradient(circle at 72% 20%, rgba(255, 255, 255, 0.26) 0%, rgba(255, 255, 255, 0) 58%),
|
||||
radial-gradient(circle at 60% 78%, rgba(59, 130, 246, 0.12) 0%, rgba(59, 130, 246, 0) 60%);
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.community-card__logo {
|
||||
position: relative;
|
||||
z-index: var(--z-sticky);
|
||||
@@ -736,7 +707,7 @@ const openOfficialAccount = () => {
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
padding: 10rpx;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
background: var(--color-bg-alpha-95);
|
||||
border: 1rpx solid var(--color-border-glass);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.06);
|
||||
@@ -757,7 +728,7 @@ const openOfficialAccount = () => {
|
||||
justify-content: center;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
background: rgba(255, 255, 255, 0.68);
|
||||
background: var(--color-glass);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
@@ -776,7 +747,7 @@ const openOfficialAccount = () => {
|
||||
letter-spacing: 0.4rpx;
|
||||
}
|
||||
|
||||
:deep(.community-card__tag) {
|
||||
.community-card__tag {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
@@ -963,7 +934,7 @@ const openOfficialAccount = () => {
|
||||
padding: 36rpx 32rpx 0;
|
||||
}
|
||||
|
||||
:deep(.profile-card__button) {
|
||||
.profile-card__button {
|
||||
flex-shrink: 0;
|
||||
min-width: 156rpx;
|
||||
height: 72rpx;
|
||||
@@ -974,7 +945,7 @@ const openOfficialAccount = () => {
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
:deep(.logout-btn) {
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
font-size: 28rpx;
|
||||
@@ -985,7 +956,7 @@ const openOfficialAccount = () => {
|
||||
box-shadow: 0 12rpx 30rpx var(--color-danger-shadow);
|
||||
}
|
||||
|
||||
:deep(.loading-center) {
|
||||
.loading-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
</view>
|
||||
|
||||
<view class="qrcode-card">
|
||||
<image
|
||||
src="/static/images/qrcode-official.png"
|
||||
class="qrcode-card__img"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<image src="/static/images/qrcode-official.png" class="qrcode-card__img" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="qrcode-hint">长按识别二维码关注</view>
|
||||
|
||||
@@ -35,8 +35,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "@/store";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useToast, useMessage } from "wot-design-uni";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const toast = useToast();
|
||||
const { messageBox } = useMessage();
|
||||
const isLogin = computed(() => !!userStore.userInfo);
|
||||
|
||||
// 主题设置
|
||||
@@ -179,8 +182,7 @@ onLoad(() => {
|
||||
margin-top: 60rpx;
|
||||
}
|
||||
|
||||
// 退出登录按钮样式 - 使用更具体的选择器替代 !important
|
||||
:deep(.wd-button.logout-btn) {
|
||||
:deep(.logout-btn) {
|
||||
width: 80%;
|
||||
height: 80rpx;
|
||||
font-size: 32rpx;
|
||||
@@ -193,7 +195,7 @@ onLoad(() => {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.loading-center) {
|
||||
.loading-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -202,11 +204,11 @@ onLoad(() => {
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
:deep(.loading-center .wd-loading__spinner) {
|
||||
.loading-center :deep(.wd-loading__spinner) {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.loading-center .wd-loading__text) {
|
||||
.loading-center :deep(.wd-loading__text) {
|
||||
margin-top: 20rpx;
|
||||
color: var(--color-text-inverse);
|
||||
text-align: center;
|
||||
|
||||
@@ -71,11 +71,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 自定义颜色输入弹窗 -->
|
||||
<wd-popup
|
||||
v-model="showCustomColorInput"
|
||||
position="bottom"
|
||||
custom-class="popup-bottom"
|
||||
>
|
||||
<wd-popup v-model="showCustomColorInput" position="bottom" custom-class="popup-bottom">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">自定义主题色</text>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
>
|
||||
<!-- 自定义节点内容:ID + 名称 + 状态 -->
|
||||
<template #content="{ node }">
|
||||
<view class="flex-1 flex items-center gap-16rpx">
|
||||
<text class="w-120rpx text-24rpx color-text-secondary">{{ node.id }}</text>
|
||||
<text class="flex-1 truncate">{{ node.name }}</text>
|
||||
<view class="dept-node">
|
||||
<text class="dept-node__id">{{ node.id }}</text>
|
||||
<text class="dept-node__name">{{ node.name }}</text>
|
||||
<wd-tag :type="node.status === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ node.status === 1 ? "正常" : "禁用" }}
|
||||
</wd-tag>
|
||||
@@ -72,10 +72,7 @@
|
||||
</wd-popup>
|
||||
|
||||
<!-- 浮动新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasPermission('sys:dept:create') && !dialog.visible"
|
||||
@click="openDeptDialog()"
|
||||
/>
|
||||
<wd-fab v-if="hasPermission('sys:dept:create') && !dialog.visible" @click="openDeptDialog()" />
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<wd-action-sheet
|
||||
@@ -342,3 +339,25 @@ export default { options: { styleIsolation: "shared" } };
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dept-node {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.dept-node__id {
|
||||
width: 120rpx;
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dept-node__name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -84,10 +84,7 @@
|
||||
@select="handleActionSelect"
|
||||
/>
|
||||
|
||||
<wd-fab
|
||||
v-if="hasPermission('sys:dict:create') && !dialog.visible"
|
||||
@click="openDictDialog()"
|
||||
/>
|
||||
<wd-fab v-if="hasPermission('sys:dict:create') && !dialog.visible" @click="openDictDialog()" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -96,16 +93,13 @@ import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { useRouter } from "uni-mini-router";
|
||||
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
||||
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
||||
import { useToast } from "wot-design-uni";
|
||||
import DictAPI, {
|
||||
type DictTypeForm,
|
||||
type DictTypePageQuery,
|
||||
type DictTypeItem,
|
||||
} from "@/api/dict";
|
||||
import { useToast, useMessage } from "wot-design-uni";
|
||||
import DictAPI, { type DictTypeForm, type DictTypePageQuery, type DictTypeItem } from "@/api/dict";
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
const { messageBox } = useMessage();
|
||||
const loadMoreState = ref<LoadMoreState>("loading");
|
||||
const formRef = ref();
|
||||
const submitting = ref(false);
|
||||
@@ -216,13 +210,19 @@ function showDictActions(item: DictTypeItem) {
|
||||
actions.push({ name: "删除", color: "var(--color-danger)" });
|
||||
actionMap["删除"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认删除", msg: `确定要删除字典「${item.name}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认删除",
|
||||
msg: `确定要删除字典「${item.name}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
if (item.id) {
|
||||
await DictAPI.deleteByIds(String(item.id));
|
||||
toast.success("删除成功");
|
||||
loadDictTypeList();
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -100,15 +100,12 @@
|
||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
||||
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
||||
import { useToast } from "wot-design-uni";
|
||||
import DictAPI, {
|
||||
type DictItemForm,
|
||||
type DictItemPageQuery,
|
||||
type DictDataItem,
|
||||
} from "@/api/dict";
|
||||
import { useToast, useMessage } from "wot-design-uni";
|
||||
import DictAPI, { type DictItemForm, type DictItemPageQuery, type DictDataItem } from "@/api/dict";
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
|
||||
const toast = useToast();
|
||||
const { messageBox } = useMessage();
|
||||
|
||||
const dictCode = ref<string>("");
|
||||
const pageTitle = ref<string>("字典数据");
|
||||
@@ -231,13 +228,19 @@ function showItemActions(item: DictDataItem) {
|
||||
actions.push({ name: "删除", color: "var(--color-danger)" });
|
||||
actionMap["删除"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认删除", msg: `确定要删除字典数据「${item.label}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认删除",
|
||||
msg: `确定要删除字典数据「${item.label}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
if (item.id) {
|
||||
await DictAPI.deleteItems(dictCode.value, String(item.id));
|
||||
toast.success("删除成功");
|
||||
loadItemList();
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<custom-navbar title="工作台" :show-back="false" />
|
||||
<view
|
||||
class="page page--tabbar"
|
||||
:style="{ padding: `${navbar.totalHeight.value + 8}px 32rpx 0` }"
|
||||
>
|
||||
<view class="page page--tabbar" :style="{ padding: `${navbar.totalHeight.value + 8}px 32rpx 0` }">
|
||||
<template v-for="(item, index) in visibleGridList" :key="index">
|
||||
<wd-card :title="item.title">
|
||||
<wd-grid clickable :column="4">
|
||||
@@ -13,10 +10,10 @@
|
||||
use-slot
|
||||
@itemclick="handleNavClick(child)"
|
||||
>
|
||||
<view class="p-2">
|
||||
<view class="work-grid__icon p-2">
|
||||
<image class="w-72rpx h-72rpx rounded-8rpx" :src="child.icon" />
|
||||
</view>
|
||||
<view class="text">{{ child.title }}</view>
|
||||
<view class="work-grid__label">{{ child.title }}</view>
|
||||
</wd-grid-item>
|
||||
</wd-grid>
|
||||
</wd-card>
|
||||
@@ -28,19 +25,14 @@
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "uni-mini-router";
|
||||
import { useNavbar } from "@/composables/useNavbar";
|
||||
import { useUserStore } from "@/store";
|
||||
import { menuConfig } from "@/config/menu";
|
||||
import { checkLogin, isLoggedIn } from "@/utils/auth";
|
||||
import { hasPermission as checkPermission } from "@/utils/permission";
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const navbar = useNavbar({ hasTabbar: true });
|
||||
|
||||
// 用户权限列表
|
||||
const userPerms = computed(() => userStore.userInfo?.perms || []);
|
||||
|
||||
// 是否已登录
|
||||
const isLogged = computed(() => isLoggedIn());
|
||||
|
||||
|
||||
@@ -107,10 +107,7 @@
|
||||
</wd-popup>
|
||||
|
||||
<!-- 浮动新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasPermission('sys:menu:create') && !dialog.visible"
|
||||
@click="openMenuDialog()"
|
||||
/>
|
||||
<wd-fab v-if="hasPermission('sys:menu:create') && !dialog.visible" @click="openMenuDialog()" />
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<wd-action-sheet
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
>
|
||||
<!-- 主信息行 -->
|
||||
<view class="flex-start">
|
||||
<view class="flex-1">
|
||||
<view class="notice-card__main">
|
||||
<view class="flex-start mt-12rpx">
|
||||
<text class="font-bold text-32rpx">{{ item.title }}</text>
|
||||
<text class="notice-card__title">{{ item.title }}</text>
|
||||
</view>
|
||||
<text class="text-24rpx color-text-secondary">
|
||||
<text class="notice-card__publisher">
|
||||
{{ item.publisherName || "系统管理员" }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -33,27 +33,27 @@
|
||||
</view>
|
||||
|
||||
<!-- 辅助信息行 -->
|
||||
<view class="flex gap-24rpx mt-12rpx">
|
||||
<view class="flex-start min-w-0">
|
||||
<view class="notice-card__meta">
|
||||
<view class="notice-card__detail">
|
||||
<wd-icon name="user" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary">
|
||||
<text class="notice-card__detail-text">
|
||||
{{ item.targetType === 1 ? "全体" : "指定用户" }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex-start min-w-0">
|
||||
<view class="notice-card__detail">
|
||||
<wd-icon name="warning" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary">
|
||||
<text class="notice-card__detail-text">
|
||||
{{ getLevelText(item.level) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 元信息行 -->
|
||||
<view class="flex-between mt-16rpx">
|
||||
<text class="text-24rpx color-text-placeholder">{{ formatTime(item) }}</text>
|
||||
<view class="notice-card__footer">
|
||||
<text class="notice-card__time">{{ formatTime(item) }}</text>
|
||||
<view
|
||||
class="w-64rpx h-64rpx flex-center rounded-full"
|
||||
hover-class="bg-[var(--color-text-placeholder)]/16"
|
||||
class="notice-card__action"
|
||||
hover-class="notice-card__action--hover"
|
||||
@click.stop="showNoticeActions(item)"
|
||||
>
|
||||
<wd-icon name="more" size="16" class="color-text-secondary" />
|
||||
@@ -156,7 +156,7 @@
|
||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
||||
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
||||
import { useToast } from "wot-design-uni";
|
||||
import { useToast, useMessage } from "wot-design-uni";
|
||||
import NoticeAPI, {
|
||||
type NoticePageQuery,
|
||||
NoticeItem,
|
||||
@@ -166,6 +166,7 @@ import NoticeAPI, {
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
|
||||
const toast = useToast();
|
||||
const { messageBox } = useMessage();
|
||||
const loadMoreState = ref<LoadMoreState>("loading");
|
||||
const formRef = ref();
|
||||
const submitting = ref(false);
|
||||
@@ -324,22 +325,34 @@ function showNoticeActions(item: NoticeItem) {
|
||||
actions.push({ name: "删除", color: "var(--color-danger)" });
|
||||
actionMap["删除"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认删除", msg: `确定要删除通知「${item.title}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认删除",
|
||||
msg: `确定要删除通知「${item.title}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
await NoticeAPI.deleteByIds(item.id);
|
||||
toast.success("删除成功");
|
||||
loadNoticeList();
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
if (hasPermission("sys:notice:publish")) {
|
||||
actions.push({ name: "发布" });
|
||||
actionMap["发布"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认发布", msg: `确定要发布通知「${item.title}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认发布",
|
||||
msg: `确定要发布通知「${item.title}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
await NoticeAPI.publish(Number(item.id));
|
||||
toast.success("发布成功");
|
||||
loadNoticeList();
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -347,11 +360,17 @@ function showNoticeActions(item: NoticeItem) {
|
||||
actions.push({ name: "撤回", color: "var(--color-warning)" });
|
||||
actionMap["撤回"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认撤回", msg: `确定要撤回通知「${item.title}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认撤回",
|
||||
msg: `确定要撤回通知「${item.title}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
await NoticeAPI.revoke(Number(item.id));
|
||||
toast.success("撤回成功");
|
||||
loadNoticeList();
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -390,3 +409,62 @@ export default { options: { styleIsolation: "shared" } };
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notice-card__main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notice-card__title {
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.notice-card__publisher {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.notice-card__meta {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.notice-card__detail {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notice-card__detail-text {
|
||||
margin-left: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.notice-card__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.notice-card__time {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-placeholder);
|
||||
}
|
||||
|
||||
.notice-card__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.notice-card__action--hover {
|
||||
background: rgba(var(--color-text-placeholder-rgb, 148, 163, 184), 0.16);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -279,7 +279,7 @@ export default { options: { styleIsolation: "shared" } };
|
||||
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.bottom-bar .wd-button) {
|
||||
.bottom-bar :deep(.wd-button) {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
>
|
||||
<!-- 主信息行 -->
|
||||
<view class="flex-start">
|
||||
<view class="flex-1">
|
||||
<view class="role-card__main">
|
||||
<view class="flex-start mt-12rpx">
|
||||
<text class="font-bold text-32rpx">{{ item.name }}</text>
|
||||
<text class="role-card__name">{{ item.name }}</text>
|
||||
</view>
|
||||
<text class="text-24rpx color-text-secondary">{{ item.code }}</text>
|
||||
<text class="role-card__code">{{ item.code }}</text>
|
||||
</view>
|
||||
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
|
||||
{{ item.status === 1 ? "正常" : "禁用" }}
|
||||
@@ -31,23 +31,23 @@
|
||||
</view>
|
||||
|
||||
<!-- 辅助信息行 -->
|
||||
<view class="flex gap-24rpx mt-12rpx">
|
||||
<view class="flex-start min-w-0">
|
||||
<view class="role-card__meta">
|
||||
<view class="role-card__detail">
|
||||
<wd-icon name="view" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary">{{ item.dataScopeLabel }}</text>
|
||||
<text class="role-card__detail-text">{{ item.dataScopeLabel }}</text>
|
||||
</view>
|
||||
<view class="flex-start min-w-0">
|
||||
<view class="role-card__detail">
|
||||
<wd-icon name="sort" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary">排序: {{ item.sort }}</text>
|
||||
<text class="role-card__detail-text">排序: {{ item.sort }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 元信息行 -->
|
||||
<view class="flex-between mt-16rpx">
|
||||
<text class="text-24rpx color-text-placeholder">{{ item.createTime }}</text>
|
||||
<view class="role-card__footer">
|
||||
<text class="role-card__time">{{ item.createTime }}</text>
|
||||
<view
|
||||
class="w-88rpx h-88rpx flex-center rounded-full"
|
||||
hover-class="bg-[var(--color-text-placeholder)]/16"
|
||||
class="role-card__action"
|
||||
hover-class="role-card__action--hover"
|
||||
@click.stop="showRoleActions(item)"
|
||||
>
|
||||
<wd-icon name="more" size="18" class="color-text-secondary" />
|
||||
@@ -96,10 +96,7 @@
|
||||
</wd-popup>
|
||||
|
||||
<!-- 浮动新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasPermission('sys:role:create') && !dialog.visible"
|
||||
@click="openRoleDialog()"
|
||||
/>
|
||||
<wd-fab v-if="hasPermission('sys:role:create') && !dialog.visible" @click="openRoleDialog()" />
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<wd-action-sheet
|
||||
@@ -300,3 +297,62 @@ export default { options: { styleIsolation: "shared" } };
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.role-card__main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.role-card__name {
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.role-card__code {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.role-card__meta {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.role-card__detail {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.role-card__detail-text {
|
||||
margin-left: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.role-card__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.role-card__time {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-placeholder);
|
||||
}
|
||||
|
||||
.role-card__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.role-card__action--hover {
|
||||
background: rgba(var(--color-text-placeholder-rgb, 148, 163, 184), 0.16);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
>
|
||||
<!-- 主信息行 -->
|
||||
<view class="flex-start">
|
||||
<image class="w-80rpx h-80rpx rounded-full" :src="item.avatar" mode="aspectFill" />
|
||||
<view class="flex-1 ml-16rpx">
|
||||
<image class="user-card__avatar" :src="item.avatar" mode="aspectFill" />
|
||||
<view class="user-card__main">
|
||||
<view class="flex-start mt-12rpx">
|
||||
<text class="font-bold text-32rpx">{{ item.nickname }}</text>
|
||||
<text class="user-card__name">{{ item.nickname }}</text>
|
||||
<wd-icon
|
||||
v-if="item.gender === 1"
|
||||
name="gender-male"
|
||||
@@ -69,9 +69,7 @@
|
||||
class="ml-8rpx"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-24rpx color-text-secondary">
|
||||
{{ item.roleNames }} · {{ item.deptName }}
|
||||
</text>
|
||||
<text class="user-card__role">{{ item.roleNames }} · {{ item.deptName }}</text>
|
||||
</view>
|
||||
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
|
||||
{{ item.status === 1 ? "正常" : "禁用" }}
|
||||
@@ -79,23 +77,23 @@
|
||||
</view>
|
||||
|
||||
<!-- 辅助信息行 -->
|
||||
<view class="flex gap-24rpx mt-12rpx">
|
||||
<view v-if="item.mobile" class="flex-start min-w-0">
|
||||
<view class="user-card__meta">
|
||||
<view v-if="item.mobile" class="user-card__contact">
|
||||
<wd-icon name="mobile" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary truncate">{{ item.mobile }}</text>
|
||||
<text class="user-card__contact-text">{{ item.mobile }}</text>
|
||||
</view>
|
||||
<view v-if="item.email" class="flex-start min-w-0">
|
||||
<view v-if="item.email" class="user-card__contact">
|
||||
<wd-icon name="mail" size="16" class="color-text-secondary" />
|
||||
<text class="ml-8rpx text-24rpx color-text-secondary truncate">{{ item.email }}</text>
|
||||
<text class="user-card__contact-text">{{ item.email }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 元信息行 -->
|
||||
<view class="flex-between mt-16rpx">
|
||||
<text class="text-24rpx color-text-placeholder">{{ item.createTime }}</text>
|
||||
<view class="user-card__footer">
|
||||
<text class="user-card__time">{{ item.createTime }}</text>
|
||||
<view
|
||||
class="w-88rpx h-88rpx flex-center rounded-full"
|
||||
hover-class="bg-[var(--color-text-placeholder)]/16"
|
||||
class="user-card__action"
|
||||
hover-class="user-card__action--hover"
|
||||
@click.stop="showUserActions(item)"
|
||||
>
|
||||
<wd-icon name="more" size="18" class="color-text-secondary" />
|
||||
@@ -157,10 +155,7 @@
|
||||
</wd-popup>
|
||||
|
||||
<!-- 浮动新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasPermission('sys:user:create') && !dialog.visible"
|
||||
@click="openUserDialog()"
|
||||
/>
|
||||
<wd-fab v-if="hasPermission('sys:user:create') && !dialog.visible" @click="openUserDialog()" />
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<wd-action-sheet
|
||||
@@ -181,13 +176,22 @@
|
||||
label="新密码"
|
||||
placeholder="请输入新密码(至少6位)"
|
||||
prop="password"
|
||||
:rules="[{ required: true, message: '请输入新密码' }, { pattern: /^.{6,}$/, message: '密码至少需要6位字符' }]"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ pattern: /^.{6,}$/, message: '密码至少需要6位字符' },
|
||||
]"
|
||||
/>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
<view class="popup-actions">
|
||||
<wd-button type="info" plain @click="resetPwdDialog.visible = false">取消</wd-button>
|
||||
<wd-button type="primary" :loading="resetPwdDialog.submitting" @click="handleResetPassword">确认</wd-button>
|
||||
<wd-button
|
||||
type="primary"
|
||||
:loading="resetPwdDialog.submitting"
|
||||
@click="handleResetPassword"
|
||||
>
|
||||
确认
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
@@ -198,13 +202,14 @@
|
||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
||||
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
||||
import { useQueue, useToast } from "wot-design-uni";
|
||||
import { useQueue, useToast, useMessage } from "wot-design-uni";
|
||||
import UserAPI, { type UserPageQuery, UserItem, UserForm } from "@/api/user";
|
||||
import RoleAPI from "@/api/role";
|
||||
import DeptAPI from "@/api/dept";
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
|
||||
const toast = useToast();
|
||||
const { messageBox } = useMessage();
|
||||
const { closeOutside } = useQueue();
|
||||
const loadMoreState = ref<LoadMoreState>("loading");
|
||||
const formRef = ref();
|
||||
@@ -414,7 +419,11 @@ const actionSheetVisible = ref(false);
|
||||
const actionSheetActions = ref<{ name: string; color?: string }[]>([]);
|
||||
const pendingAction = ref<Record<string, () => void>>({});
|
||||
|
||||
const resetPwdDialog = reactive({ visible: false, submitting: false, userId: undefined as number | undefined });
|
||||
const resetPwdDialog = reactive({
|
||||
visible: false,
|
||||
submitting: false,
|
||||
userId: undefined as number | undefined,
|
||||
});
|
||||
const resetPwdForm = reactive({ password: "" });
|
||||
const resetPwdFormRef = ref();
|
||||
|
||||
@@ -440,11 +449,17 @@ function showUserActions(item: UserItem) {
|
||||
actions.push({ name: "删除", color: "var(--color-danger)" });
|
||||
actionMap["删除"] = async () => {
|
||||
try {
|
||||
await messageBox({ title: "确认删除", msg: `确定要删除用户「${item.nickname}」吗?`, type: "warning" });
|
||||
await messageBox({
|
||||
title: "确认删除",
|
||||
msg: `确定要删除用户「${item.nickname}」吗?`,
|
||||
type: "warning",
|
||||
});
|
||||
await UserAPI.deleteByIds(String(item.id));
|
||||
toast.success("删除成功");
|
||||
loadUserList();
|
||||
} catch {}
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -515,3 +530,73 @@ export default { options: { styleIsolation: "shared" } };
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-card__avatar {
|
||||
flex-shrink: 0;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-card__main {
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.user-card__name {
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.user-card__role {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.user-card__meta {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.user-card__contact {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-card__contact-text {
|
||||
margin-left: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-card__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.user-card__time {
|
||||
font-size: 24rpx;
|
||||
color: var(--color-text-placeholder);
|
||||
}
|
||||
|
||||
.user-card__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-card__action--hover {
|
||||
background: rgba(var(--color-text-placeholder-rgb, 148, 163, 184), 0.16);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,8 +72,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
// 路由跳转日志(生产环境可通过 vite 配置自动移除)
|
||||
});
|
||||
router.afterEach(() => {});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { Storage } from "@/utils/storage"
|
||||
import { THEME_MODE_KEY, THEME_COLOR_KEY } from "@/constants"
|
||||
import type { ThemeColorOption, ThemeMode } from "@/composables/types/theme"
|
||||
import { themeColorOptions } from "@/composables/types/theme"
|
||||
import { defineStore } from "pinia";
|
||||
import { Storage } from "@/utils/storage";
|
||||
import { THEME_MODE_KEY, THEME_COLOR_KEY } from "@/constants";
|
||||
import type { ThemeColorOption, ThemeMode } from "@/composables/types/theme";
|
||||
import { themeColorOptions } from "@/composables/types/theme";
|
||||
|
||||
/**
|
||||
* 主题状态管理 Store
|
||||
@@ -19,12 +19,12 @@ export const useThemeStore = defineStore("theme", () => {
|
||||
// ==========================================================================
|
||||
|
||||
/** 当前主题模式 */
|
||||
const theme = ref<ThemeMode>(Storage.get<ThemeMode>(THEME_MODE_KEY, "light"))
|
||||
const theme = ref<ThemeMode>(Storage.get<ThemeMode>(THEME_MODE_KEY, "light"));
|
||||
|
||||
/** 当前主题色 */
|
||||
const currentThemeColor = ref<ThemeColorOption>(
|
||||
Storage.get<ThemeColorOption>(THEME_COLOR_KEY, themeColorOptions[0])
|
||||
)
|
||||
);
|
||||
|
||||
/** 主题变量(响应式对象) */
|
||||
const themeVars = reactive({
|
||||
@@ -39,14 +39,14 @@ export const useThemeStore = defineStore("theme", () => {
|
||||
darkColor2: "#9ca3af",
|
||||
darkColor3: "#6b7280",
|
||||
colorTheme: currentThemeColor.value.primary,
|
||||
})
|
||||
});
|
||||
|
||||
// ==========================================================================
|
||||
// 计算属性
|
||||
// ==========================================================================
|
||||
|
||||
/** 是否为暗黑模式 */
|
||||
const isDark = computed(() => theme.value === "dark")
|
||||
const isDark = computed(() => theme.value === "dark");
|
||||
|
||||
// ==========================================================================
|
||||
// 方法
|
||||
@@ -59,41 +59,41 @@ export const useThemeStore = defineStore("theme", () => {
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: theme.value === "light" ? "#000000" : "#ffffff",
|
||||
backgroundColor: theme.value === "light" ? "#ffffff" : "#1f2937",
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换主题
|
||||
* @param mode 指定主题模式,不传则自动切换
|
||||
*/
|
||||
const toggleTheme = (mode?: ThemeMode) => {
|
||||
theme.value = mode || (theme.value === "light" ? "dark" : "light")
|
||||
Storage.set(THEME_MODE_KEY, theme.value)
|
||||
setNavigationBarColor()
|
||||
}
|
||||
theme.value = mode || (theme.value === "light" ? "dark" : "light");
|
||||
Storage.set(THEME_MODE_KEY, theme.value);
|
||||
setNavigationBarColor();
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置主题色
|
||||
* @param color 主题色选项
|
||||
*/
|
||||
const setCurrentThemeColor = (color: ThemeColorOption) => {
|
||||
currentThemeColor.value = color
|
||||
Storage.set(THEME_COLOR_KEY, color)
|
||||
themeVars.colorTheme = color.primary
|
||||
}
|
||||
currentThemeColor.value = color;
|
||||
Storage.set(THEME_COLOR_KEY, color);
|
||||
themeVars.colorTheme = color.primary;
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化主题
|
||||
*/
|
||||
const initTheme = () => {
|
||||
// 更新主题变量中的颜色
|
||||
themeVars.colorTheme = currentThemeColor.value.primary
|
||||
themeVars.colorTheme = currentThemeColor.value.primary;
|
||||
|
||||
// 设置导航栏颜色
|
||||
nextTick(() => {
|
||||
setNavigationBarColor()
|
||||
})
|
||||
}
|
||||
setNavigationBarColor();
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================================================
|
||||
// 导出
|
||||
@@ -113,5 +113,5 @@ export const useThemeStore = defineStore("theme", () => {
|
||||
setCurrentThemeColor,
|
||||
setNavigationBarColor,
|
||||
initTheme,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { defineStore } from "pinia";
|
||||
import AuthAPI, {
|
||||
type LoginData,
|
||||
type SmsLoginData,
|
||||
type WxMaPhoneLoginData,
|
||||
type WxMaBindMobileData,
|
||||
} from "@/api/auth"
|
||||
import UserAPI, { type UserInfo } from "@/api/user"
|
||||
import { setAccessToken, clearTokens } from "@/utils/auth"
|
||||
import { Storage } from "@/utils/storage"
|
||||
import { USER_INFO_KEY } from "@/constants"
|
||||
} from "@/api/auth";
|
||||
import UserAPI, { type UserInfo } from "@/api/user";
|
||||
import { setAccessToken, clearTokens } from "@/utils/auth";
|
||||
import { Storage } from "@/utils/storage";
|
||||
import { USER_INFO_KEY } from "@/constants";
|
||||
|
||||
/**
|
||||
* 用户状态管理 Store
|
||||
@@ -26,7 +26,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
// ==========================================================================
|
||||
|
||||
/** 用户信息 */
|
||||
const userInfo = ref<UserInfo | undefined>(Storage.get<UserInfo>(USER_INFO_KEY))
|
||||
const userInfo = ref<UserInfo | undefined>(Storage.get<UserInfo>(USER_INFO_KEY));
|
||||
|
||||
// ==========================================================================
|
||||
// 登录方法
|
||||
@@ -36,48 +36,48 @@ export const useUserStore = defineStore("user", () => {
|
||||
* 账号密码登录
|
||||
*/
|
||||
const login = async (data: LoginData) => {
|
||||
const result = await AuthAPI.login(data)
|
||||
setAccessToken(result.accessToken)
|
||||
return result
|
||||
}
|
||||
const result = await AuthAPI.login(data);
|
||||
setAccessToken(result.accessToken);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 短信验证码登录
|
||||
*/
|
||||
const loginBySms = async (data: SmsLoginData) => {
|
||||
const result = await AuthAPI.loginBySms(data)
|
||||
setAccessToken(result.accessToken)
|
||||
return result
|
||||
}
|
||||
const result = await AuthAPI.loginBySms(data);
|
||||
setAccessToken(result.accessToken);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 微信小程序静默登录
|
||||
*/
|
||||
const loginByWxMa = async (code: string) => {
|
||||
const result = await AuthAPI.wxMaSilentLogin(code)
|
||||
const result = await AuthAPI.wxMaSilentLogin(code);
|
||||
if (result.accessToken) {
|
||||
setAccessToken(result.accessToken)
|
||||
setAccessToken(result.accessToken);
|
||||
}
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 微信小程序一键登录(企业小程序)
|
||||
*/
|
||||
const loginByWxMaPhone = async (data: WxMaPhoneLoginData) => {
|
||||
const result = await AuthAPI.wxMaPhoneLogin(data)
|
||||
setAccessToken(result.accessToken)
|
||||
return result
|
||||
}
|
||||
const result = await AuthAPI.wxMaPhoneLogin(data);
|
||||
setAccessToken(result.accessToken);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 微信小程序绑定手机号
|
||||
*/
|
||||
const bindMobileForWxMa = async (data: WxMaBindMobileData) => {
|
||||
const result = await AuthAPI.wxMaBindMobile(data)
|
||||
setAccessToken(result.accessToken)
|
||||
return result
|
||||
}
|
||||
const result = await AuthAPI.wxMaBindMobile(data);
|
||||
setAccessToken(result.accessToken);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==========================================================================
|
||||
// 用户信息方法
|
||||
@@ -88,46 +88,46 @@ export const useUserStore = defineStore("user", () => {
|
||||
*/
|
||||
const checkSession = async (): Promise<boolean> => {
|
||||
try {
|
||||
const result = await AuthAPI.checkSession()
|
||||
return result.valid
|
||||
const result = await AuthAPI.checkSession();
|
||||
return result.valid;
|
||||
} catch {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
const getInfo = async () => {
|
||||
const data = await UserAPI.getUserInfo()
|
||||
Storage.set(USER_INFO_KEY, data)
|
||||
userInfo.value = data
|
||||
return data
|
||||
}
|
||||
const data = await UserAPI.getUserInfo();
|
||||
Storage.set(USER_INFO_KEY, data);
|
||||
userInfo.value = data;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
const logout = async () => {
|
||||
try {
|
||||
await AuthAPI.logout()
|
||||
await AuthAPI.logout();
|
||||
} catch {
|
||||
// 登出失败静默处理,继续清理本地状态
|
||||
} finally {
|
||||
clearTokens()
|
||||
Storage.remove(USER_INFO_KEY)
|
||||
userInfo.value = undefined
|
||||
uni.reLaunch({ url: "/pages/login/index" })
|
||||
clearTokens();
|
||||
Storage.remove(USER_INFO_KEY);
|
||||
userInfo.value = undefined;
|
||||
uni.reLaunch({ url: "/pages/login/index" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断用户信息是否完整
|
||||
*/
|
||||
const isUserInfoComplete = (): boolean => {
|
||||
if (!userInfo.value) return false
|
||||
return !!(userInfo.value.nickname && userInfo.value.avatar)
|
||||
}
|
||||
if (!userInfo.value) return false;
|
||||
return !!(userInfo.value.nickname && userInfo.value.avatar);
|
||||
};
|
||||
|
||||
// ==========================================================================
|
||||
// 导出
|
||||
@@ -144,5 +144,5 @@ export const useUserStore = defineStore("user", () => {
|
||||
getInfo,
|
||||
checkSession,
|
||||
isUserInfoComplete,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
@@ -58,8 +58,22 @@ page {
|
||||
--color-border-light: #f3f4f6;
|
||||
--color-border-dark: #d1d5db;
|
||||
--color-border-glass: rgba(148, 163, 184, 0.22);
|
||||
--color-border-glass-strong: rgba(255, 255, 255, 0.9);
|
||||
--color-danger-shadow: rgba(239, 68, 68, 0.08);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 毛玻璃效果色
|
||||
// -----------------------------------------------------------------------
|
||||
--color-glass: rgba(255, 255, 255, 0.22);
|
||||
--color-glass-light: rgba(255, 255, 255, 0.12);
|
||||
--color-bg-alpha-95: rgba(255, 255, 255, 0.95);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 主题色透明度变体
|
||||
// -----------------------------------------------------------------------
|
||||
--color-primary-alpha-20: rgba(77, 128, 240, 0.2);
|
||||
--color-primary-alpha-15: rgba(77, 128, 240, 0.15);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 阴影
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -141,8 +155,22 @@ page {
|
||||
--color-border-light: #1f2937;
|
||||
--color-border-dark: #4b5563;
|
||||
--color-border-glass: rgba(148, 163, 184, 0.15);
|
||||
--color-border-glass-strong: rgba(255, 255, 255, 0.9);
|
||||
--color-danger-shadow: rgba(239, 68, 68, 0.15);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 毛玻璃效果色
|
||||
// -----------------------------------------------------------------------
|
||||
--color-glass: rgba(255, 255, 255, 0.12);
|
||||
--color-glass-light: rgba(255, 255, 255, 0.06);
|
||||
--color-bg-alpha-95: rgba(31, 41, 55, 0.95);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 主题色透明度变体
|
||||
// -----------------------------------------------------------------------
|
||||
--color-primary-alpha-20: rgba(59, 130, 246, 0.2);
|
||||
--color-primary-alpha-15: rgba(59, 130, 246, 0.15);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 阴影
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user