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