mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-13 20:51:09 +00:00
+7
-15
@@ -37,7 +37,6 @@ cython_debug
|
|||||||
|
|
||||||
venv
|
venv
|
||||||
.venv
|
.venv
|
||||||
# ENV
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
.npm
|
.npm
|
||||||
@@ -47,33 +46,26 @@ node_modules
|
|||||||
backend/logs
|
backend/logs
|
||||||
backend/*.db
|
backend/*.db
|
||||||
backend/alembic/versions/*
|
backend/alembic/versions/*
|
||||||
|
backend/env/.env.dev
|
||||||
|
backend/env/.env.prod
|
||||||
|
backend/env/.env.test
|
||||||
!backend/alembic/versions/.gitkeep
|
!backend/alembic/versions/.gitkeep
|
||||||
|
|
||||||
|
docker/.env
|
||||||
docker/nginx/app/dist
|
docker/nginx/app/dist
|
||||||
docker/nginx/docs/dist
|
docker/nginx/docs/dist
|
||||||
docker/nginx/web/dist
|
docker/nginx/web/dist
|
||||||
docker/nginx/ssl/*
|
docker/nginx/ssl/*
|
||||||
!docker/nginx/ssl/.gitkeep
|
!docker/nginx/ssl/.gitkeep
|
||||||
# 环境变量文件(真实配置,不提交)
|
|
||||||
docker/.env
|
|
||||||
backend/env/.env.dev
|
|
||||||
backend/env/.env.prod
|
|
||||||
backend/env/.env.test
|
|
||||||
frontend/.env.development
|
|
||||||
frontend/.env.production
|
|
||||||
frontend/web/.env
|
frontend/web/.env
|
||||||
frontend/web/.env.development
|
frontend/web/.env.development
|
||||||
frontend/web/.env.production
|
frontend/web/.env.production
|
||||||
|
|
||||||
frontend/node_modules
|
# frontend/docs/*
|
||||||
frontend/dist
|
|
||||||
frontend/src/types/auto-imports.d.ts
|
|
||||||
frontend/src/types/components.d.ts
|
|
||||||
|
|
||||||
frontend/docs/*
|
|
||||||
!frontend/docs/.gitkeep
|
!frontend/docs/.gitkeep
|
||||||
|
|
||||||
frontend/app/*
|
# frontend/app/*
|
||||||
!frontend/app/.gitkeep
|
!frontend/app/.gitkeep
|
||||||
|
|
||||||
.plan
|
.plan
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ async def oauth_login_redirect_controller(
|
|||||||
@AuthRouter.get("/oauth/{provider}/callback", summary="第三方OAuth回调", include_in_schema=False)
|
@AuthRouter.get("/oauth/{provider}/callback", summary="第三方OAuth回调", include_in_schema=False)
|
||||||
async def oauth_callback_controller(
|
async def oauth_callback_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
redis: Annotated[Redis, Depends(redis_getter)],
|
redis: Annotated[Redis, Depends(redis_getter)],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
provider: Annotated[OAuthProvider, Path(description="wechat | qq | github | gitee")],
|
provider: Annotated[OAuthProvider, Path(description="wechat | qq | github | gitee")],
|
||||||
@@ -205,6 +206,7 @@ async def oauth_callback_controller(
|
|||||||
provider=provider,
|
provider=provider,
|
||||||
code=code,
|
code=code,
|
||||||
state=state,
|
state=state,
|
||||||
|
background_tasks=background_tasks,
|
||||||
)
|
)
|
||||||
success_url = oauth_service_frontend_redirect_from_token(fe, token)
|
success_url = oauth_service_frontend_redirect_from_token(fe, token)
|
||||||
return RedirectContentResponse(url=success_url, status_code=302)
|
return RedirectContentResponse(url=success_url, status_code=302)
|
||||||
@@ -224,6 +226,7 @@ async def wx_mini_login_controller(
|
|||||||
redis: Annotated[Redis, Depends(redis_getter)],
|
redis: Annotated[Redis, Depends(redis_getter)],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
body: WxLoginSchema,
|
body: WxLoginSchema,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""微信小程序登录(code2Session)。
|
"""微信小程序登录(code2Session)。
|
||||||
|
|
||||||
@@ -252,6 +255,7 @@ async def wx_mini_login_controller(
|
|||||||
redis=redis,
|
redis=redis,
|
||||||
user=user,
|
user=user,
|
||||||
login_type="wx_mini",
|
login_type="wx_mini",
|
||||||
|
background_tasks=background_tasks,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_info = {
|
user_info = {
|
||||||
@@ -282,6 +286,7 @@ async def wx_mini_phone_login_controller(
|
|||||||
redis: Annotated[Redis, Depends(redis_getter)],
|
redis: Annotated[Redis, Depends(redis_getter)],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
body: WxPhoneLoginSchema,
|
body: WxPhoneLoginSchema,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""微信小程序手机号快速登录。
|
"""微信小程序手机号快速登录。
|
||||||
|
|
||||||
@@ -335,6 +340,7 @@ async def wx_mini_phone_login_controller(
|
|||||||
redis=redis,
|
redis=redis,
|
||||||
user=user,
|
user=user,
|
||||||
login_type="wx_mini_phone",
|
login_type="wx_mini_phone",
|
||||||
|
background_tasks=background_tasks,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_info = {
|
user_info = {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import Any, Literal
|
|||||||
from urllib.parse import quote, urlencode
|
from urllib.parse import quote, urlencode
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import Request
|
from fastapi import BackgroundTasks, Request
|
||||||
from redis.asyncio.client import Redis
|
from redis.asyncio.client import Redis
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@@ -347,6 +347,7 @@ async def complete_oauth_login(
|
|||||||
provider: OAuthProvider,
|
provider: OAuthProvider,
|
||||||
code: str,
|
code: str,
|
||||||
state: str,
|
state: str,
|
||||||
|
background_tasks: BackgroundTasks | None = None,
|
||||||
) -> tuple[JWTOutSchema, str]:
|
) -> tuple[JWTOutSchema, str]:
|
||||||
rc = RedisCURD(redis)
|
rc = RedisCURD(redis)
|
||||||
raw = await rc.get(f"{STATE_PREFIX}{state}")
|
raw = await rc.get(f"{STATE_PREFIX}{state}")
|
||||||
@@ -394,7 +395,7 @@ async def complete_oauth_login(
|
|||||||
raise CustomException(msg="用户不存在")
|
raise CustomException(msg="用户不存在")
|
||||||
|
|
||||||
login_type = f"oauth_{provider}"
|
login_type = f"oauth_{provider}"
|
||||||
token = await LoginService.create_token(request=request, redis=redis, user=user, login_type=login_type)
|
token = await LoginService.create_token(request=request, redis=redis, user=user, login_type=login_type, background_tasks=background_tasks)
|
||||||
return token, frontend
|
return token, frontend
|
||||||
finally:
|
finally:
|
||||||
await rc.delete(f"{STATE_PREFIX}{state}")
|
await rc.delete(f"{STATE_PREFIX}{state}")
|
||||||
|
|||||||
@@ -84,6 +84,30 @@ async def _async_fill_login_location(redis, login_log_id: int, ip: str | None) -
|
|||||||
logger.warning(f"异步补全登录归属地失败: {e}")
|
logger.warning(f"异步补全登录归属地失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_fill_session_location(redis, session_id: str, ip: str | None) -> None:
|
||||||
|
"""后台异步补全会话缓存中的归属地(微信/OAuth 登录不写登录日志,需单独更新 Redis 会话)。"""
|
||||||
|
if not ip:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
location = await IpLocalUtil.resolve_location_async(redis, ip)
|
||||||
|
logger.info(f"异步解析IP归属地结果: ip={ip}, session_id={session_id}, location={location}")
|
||||||
|
if location == "归属地查询中" or not location:
|
||||||
|
return
|
||||||
|
key = f"{RedisInitKeyConfig.USER_SESSION.key}:{session_id}"
|
||||||
|
raw = await RedisCURD(redis).get(key)
|
||||||
|
if not raw:
|
||||||
|
return
|
||||||
|
if isinstance(raw, bytes):
|
||||||
|
raw = raw.decode("utf-8")
|
||||||
|
session_dict = json.loads(raw)
|
||||||
|
session_dict["login_location"] = location
|
||||||
|
ttl = await RedisCURD(redis).ttl(key)
|
||||||
|
await RedisCURD(redis).set(key, json.dumps(session_dict, default=str), expire=max(int(ttl), 1))
|
||||||
|
logger.info(f"会话归属地已更新: session_id={session_id}, location={location}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"异步补全会话归属地失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
class LoginService:
|
class LoginService:
|
||||||
"""登录认证服务"""
|
"""登录认证服务"""
|
||||||
|
|
||||||
@@ -275,7 +299,14 @@ class LoginService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_token(cls, request: Request, redis: Redis, user: UserModel, login_type: str) -> JWTOutSchema:
|
async def create_token(
|
||||||
|
cls,
|
||||||
|
request: Request,
|
||||||
|
redis: Redis,
|
||||||
|
user: UserModel,
|
||||||
|
login_type: str,
|
||||||
|
background_tasks: BackgroundTasks | None = None,
|
||||||
|
) -> JWTOutSchema:
|
||||||
"""创建访问令牌和刷新令牌"""
|
"""创建访问令牌和刷新令牌"""
|
||||||
session_id = str(uuid.uuid4())
|
session_id = str(uuid.uuid4())
|
||||||
ua_result = ua_parser.parse(request.headers.get("user-agent") or "")
|
ua_result = ua_parser.parse(request.headers.get("user-agent") or "")
|
||||||
@@ -336,6 +367,10 @@ class LoginService:
|
|||||||
expire=int(refresh_expires.total_seconds()),
|
expire=int(refresh_expires.total_seconds()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 归属地为待解析时后台补全会话中的 login_location(微信/OAuth 登录路径)
|
||||||
|
if background_tasks and login_location == "归属地查询中":
|
||||||
|
background_tasks.add_task(_async_fill_session_location, redis, session_id, request_ip)
|
||||||
|
|
||||||
return JWTOutSchema(
|
return JWTOutSchema(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
refresh_token=refresh_token,
|
refresh_token=refresh_token,
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class Settings(BaseSettings):
|
|||||||
# ================================================= #
|
# ================================================= #
|
||||||
HTTPX_DEFAULT_TIMEOUT: float = 10.0 # 对外 HTTP 请求默认超时(秒)
|
HTTPX_DEFAULT_TIMEOUT: float = 10.0 # 对外 HTTP 请求默认超时(秒)
|
||||||
IP_LOCATION_ENABLE: bool = True # 是否启用 IP 归属地查询(登录时对外发起 HTTP 请求)
|
IP_LOCATION_ENABLE: bool = True # 是否启用 IP 归属地查询(登录时对外发起 HTTP 请求)
|
||||||
IP_LOCATION_CACHE_TTL: int = 604800 # IP 归属地缓存时间(秒,默认 7 天)
|
IP_LOCATION_CACHE_TTL: int = 2592000 # IP 归属地缓存时间(秒,默认 30 天)
|
||||||
IP_LOCATION_QUERY_TIMEOUT: float = 3.0 # IP 归属地查询单次 HTTP 超时(秒)
|
IP_LOCATION_QUERY_TIMEOUT: float = 3.0 # IP 归属地查询单次 HTTP 超时(秒)
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ _IP_CACHE_TTL: int = settings.IP_LOCATION_CACHE_TTL
|
|||||||
# 硬超时(秒),避免外网查询阻塞主流程
|
# 硬超时(秒),避免外网查询阻塞主流程
|
||||||
_IP_QUERY_TIMEOUT: float = settings.IP_LOCATION_QUERY_TIMEOUT
|
_IP_QUERY_TIMEOUT: float = settings.IP_LOCATION_QUERY_TIMEOUT
|
||||||
|
|
||||||
|
# 归属地降级返回文案(登录日志/会话共用,调用方按字面量比较)
|
||||||
|
LOCATION_INTRANET = "内网IP"
|
||||||
|
LOCATION_DISABLED = "未解析(已关闭归属地查询)"
|
||||||
|
LOCATION_PENDING = "归属地查询中"
|
||||||
|
LOCATION_UNKNOWN = "未知"
|
||||||
|
|
||||||
|
|
||||||
def get_client_ip(request: Request) -> str:
|
def get_client_ip(request: Request) -> str:
|
||||||
"""从请求中提取客户端真实 IP(优先取反向代理透传的头部,返回空字串表示无法识别)。"""
|
"""从请求中提取客户端真实 IP(优先取反向代理透传的头部,返回空字串表示无法识别)。"""
|
||||||
@@ -31,10 +37,12 @@ def get_client_ip(request: Request) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class IpLocalUtil:
|
class IpLocalUtil:
|
||||||
"""获取 IP 归属地工具类(带 Redis 缓存、硬超时、降级)。"""
|
"""获取 IP 归属地工具类(带 Redis 缓存、查询决策、硬超时、降级)。"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_valid_ip(cls, ip: str) -> bool:
|
def is_valid_ip(cls, ip: str | None) -> bool:
|
||||||
|
if not ip:
|
||||||
|
return False
|
||||||
try:
|
try:
|
||||||
ipaddress.ip_address(ip)
|
ipaddress.ip_address(ip)
|
||||||
return True
|
return True
|
||||||
@@ -42,11 +50,18 @@ class IpLocalUtil:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_private_ip(cls, ip: str) -> bool:
|
def is_private_ip(cls, ip: str | None) -> bool:
|
||||||
|
"""判断是否为非公网地址(内网/回环/链路本地/保留段)。
|
||||||
|
|
||||||
|
这类地址无法通过外网归属地 API 解析,用 ``is_global`` 取反判断最稳妥。
|
||||||
|
"""
|
||||||
|
if not ip:
|
||||||
|
return False
|
||||||
try:
|
try:
|
||||||
return ipaddress.ip_address(ip).is_private
|
addr = ipaddress.ip_address(ip)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
return not addr.is_global
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _is_location_enabled(cls, redis) -> bool:
|
async def _is_location_enabled(cls, redis) -> bool:
|
||||||
@@ -60,44 +75,67 @@ class IpLocalUtil:
|
|||||||
payload = json.loads(raw)
|
payload = json.loads(raw)
|
||||||
cv = payload.get("config_value", "off")
|
cv = payload.get("config_value", "off")
|
||||||
return cv in (True, "true", "1", "yes", "on")
|
return cv in (True, "true", "1", "yes", "on")
|
||||||
except (json.JSONDecodeError, TypeError, Exception):
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def resolve_location_for_log(cls, redis, ip: str | None) -> str | None:
|
async def _should_query(cls, redis, ip: str | None) -> bool:
|
||||||
"""登录日志写入入口:仅返回可同步获取的值(内网/缓存/降级),
|
"""判断 IP 是否需要发起外网归属地查询(前置条件 + 开关 + IP 类型)。
|
||||||
|
|
||||||
外网查询由后台任务异步执行(见 ``resolve_location_async``)。
|
不查询的情形(短路返回 False):
|
||||||
|
1. IP 为空或非法 —— 无法查询
|
||||||
|
2. Redis 不可用 —— 读不到开关与缓存,直接降级
|
||||||
|
3. 开关关闭(ip_location_enable != on)—— 功能总闸
|
||||||
|
4. 内网/回环/保留地址 —— 外网 API 无法解析
|
||||||
|
|
||||||
|
缓存命中与否由调用方处理(命中直接返回,未命中才需要查)。
|
||||||
"""
|
"""
|
||||||
if not ip:
|
if not cls.is_valid_ip(ip):
|
||||||
return None
|
return False
|
||||||
|
if not redis:
|
||||||
|
return False
|
||||||
if not await cls._is_location_enabled(redis):
|
if not await cls._is_location_enabled(redis):
|
||||||
return "内网IP" if cls.is_private_ip(ip) else "未解析(已关闭归属地查询)"
|
return False
|
||||||
if cls.is_private_ip(ip):
|
if cls.is_private_ip(ip):
|
||||||
return "内网IP"
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def resolve_location_for_log(cls, redis, ip: str | None) -> str | None:
|
||||||
|
"""登录日志写入入口:仅返回可同步获取的值(开关/内网/缓存)。
|
||||||
|
|
||||||
|
外网查询由后台任务异步执行(见 ``resolve_location_async``),
|
||||||
|
此处只做查询决策与缓存读取,不发起任何外网请求。
|
||||||
|
"""
|
||||||
|
if not cls.is_valid_ip(ip):
|
||||||
|
return None
|
||||||
|
assert ip is not None # is_valid_ip 已确保非空
|
||||||
|
if not await cls._should_query(redis, ip):
|
||||||
|
return LOCATION_INTRANET if cls.is_private_ip(ip) else LOCATION_DISABLED
|
||||||
if redis:
|
if redis:
|
||||||
cached = await cls._cache_get(redis, ip)
|
cached = await cls._cache_get(redis, ip)
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
return cached
|
return cached
|
||||||
return "归属地查询中"
|
return LOCATION_PENDING
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def resolve_location_async(cls, redis, ip: str) -> str:
|
async def resolve_location_async(cls, redis, ip: str) -> str:
|
||||||
"""异步查询归属地(含缓存、降级、硬超时)。"""
|
"""异步查询归属地(含决策、缓存、降级、硬超时)。仅供后台任务调用。
|
||||||
|
|
||||||
|
仅查询成功的结果写入缓存池(IP 池),失败(未知)不写缓存,
|
||||||
|
下次登录仍会重试,避免缓存池被无效值污染。
|
||||||
|
"""
|
||||||
if not cls.is_valid_ip(ip):
|
if not cls.is_valid_ip(ip):
|
||||||
return "未知"
|
return LOCATION_UNKNOWN
|
||||||
if not await cls._is_location_enabled(redis):
|
if not await cls._should_query(redis, ip):
|
||||||
return "未解析(已关闭归属地查询)"
|
return LOCATION_INTRANET if cls.is_private_ip(ip) else LOCATION_DISABLED
|
||||||
if cls.is_private_ip(ip):
|
|
||||||
return "内网IP"
|
|
||||||
|
|
||||||
cached = await cls._cache_get(redis, ip) if redis else None
|
|
||||||
if cached is not None:
|
|
||||||
return cached
|
|
||||||
|
|
||||||
result = await cls._query_with_timeout(ip)
|
|
||||||
if redis:
|
if redis:
|
||||||
|
cached = await cls._cache_get(redis, ip)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
result = await cls._query_with_timeout(ip)
|
||||||
|
if redis and result != LOCATION_UNKNOWN:
|
||||||
await cls._cache_set(redis, ip, result)
|
await cls._cache_set(redis, ip, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -119,7 +157,7 @@ class IpLocalUtil:
|
|||||||
return location
|
return location
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"IP 归属地 API 失败: {url} - {e}")
|
logger.warning(f"IP 归属地 API 失败: {url} - {e}")
|
||||||
return "未知"
|
return LOCATION_UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_ipapi(data: dict) -> str | None:
|
def _parse_ipapi(data: dict) -> str | None:
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
---
|
||||||
|
name: create-wot-ui-theme
|
||||||
|
description: '为 wot-ui 生成单文件主题 SCSS,并在用户明确要求接入时追加 App.vue 的 `@use`。当用户要做品牌主题、语义变量落地、单文件主题接入时使用。'
|
||||||
|
argument-hint: '主题名、主题风格、主色阶、自定义范围、是否需要接入 App.vue'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create Wot-ui Theme Skill
|
||||||
|
|
||||||
|
这个 skill 用于在 `wot-ui` 项目中生成“单文件主题 SCSS”方案。它约束主题文件在 `src/themes/styles` 下完成语义变量定义与挂载,`App.vue` 只负责 `@use` 引入,不扩展成 light/dark 双文件结构,也不改造 `uni_modules/wot-ui/styles/theme/index.scss`。
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 用户明确要求为 `wot-ui` 生成品牌主题、业务主题或定制语义变量主题。
|
||||||
|
- 用户希望新增 `src/themes/styles/{主题名}.scss`,并把挂载逻辑也收进主题文件。
|
||||||
|
- 用户要求 `App.vue` 只保留 `@use './themes/styles/{主题名}.scss' as {主题名};` 这一类引入。
|
||||||
|
- 用户强调不要生成 dark 主题文件、不要拆成双文件主题结构、不要改内置主题入口。
|
||||||
|
|
||||||
|
## 不适用场景
|
||||||
|
|
||||||
|
- 用户只是想知道 `wd-config-provider` 或普通 CSS 变量怎么用,这种情况优先使用 `wot-ui-v2` skill。
|
||||||
|
- 用户要做 light/dark 双主题、动态换肤系统、运行时 token 注入,这不属于本 skill 的目标结构。
|
||||||
|
- 当前仓库没有实际业务 `src/App.vue` 或主题目录时,不要臆造文件;应基于用户目标项目路径执行,或先向用户确认目标工程位置。
|
||||||
|
|
||||||
|
## 核心约束
|
||||||
|
|
||||||
|
- 只生成一个主题文件:`src/themes/styles/{主题名}.scss`。
|
||||||
|
- 主题文件内必须同时包含:
|
||||||
|
- 一个 `@mixin {主题名}-theme-vars`
|
||||||
|
- `page`、`.wot-theme-{主题名}`、`.wot-theme-{主题名} .wd-root-portal` 的挂载选择器
|
||||||
|
- `App.vue` 只负责 `@use` 引入,不重复写挂载选择器。
|
||||||
|
- 所有语义变量都必须填写固定色值或 `transparent`,不要写成 `var(--wot-xxx)`。
|
||||||
|
- 不生成 `dark.scss`,不修改 `uni_modules/wot-ui/styles/theme/index.scss`。
|
||||||
|
- 如果用户没有明确要求接入 `App.vue`,默认只生成主题文件并给出应追加的 `@use` 语句。
|
||||||
|
|
||||||
|
## 执行前确认
|
||||||
|
|
||||||
|
生成前优先确认这些信息;缺少时先问清楚再动手:
|
||||||
|
|
||||||
|
1. 主题名称,例如 `antd`、`ocean`、`forest`
|
||||||
|
2. 主题风格描述,例如品牌化、偏中性、偏高对比
|
||||||
|
3. 主色 10 阶是否全部自定义
|
||||||
|
4. `danger`、`success`、`warning` 是否沿用默认值
|
||||||
|
5. 文本、边框、填充、反馈态是否需要整体调性调整
|
||||||
|
6. 是否需要同步接入 `src/App.vue`
|
||||||
|
|
||||||
|
## 推荐流程
|
||||||
|
|
||||||
|
1. 先查看项目里是否已存在 `src/themes/styles/*.scss` 与 `src/App.vue`,确认接入位置和现有顺序。
|
||||||
|
2. 若用户未给足主题信息,先收集主题名、风格和 token 调整范围。
|
||||||
|
3. 创建 `src/themes/styles/{主题名}.scss`,按本 skill 的完整模板输出全部语义变量。
|
||||||
|
4. 如用户明确要求接入,再在 `src/App.vue` 的 `<style lang="scss">` 中只追加 `@use './themes/styles/{主题名}.scss' as {主题名};`。
|
||||||
|
5. 检查变量是否完整、值是否全为固定色值、挂载选择器是否在主题文件内。
|
||||||
|
|
||||||
|
## 主题文件要求
|
||||||
|
|
||||||
|
### 结构要求
|
||||||
|
|
||||||
|
- 只保留一个 mixin,命名为 `{主题名}-theme-vars`
|
||||||
|
- 注释风格尽量贴近 `antd.scss` 这种语义分组写法
|
||||||
|
- 挂载选择器直接写在同一文件末尾
|
||||||
|
- mixin 调用形式固定为 `@include {主题名}-theme-vars();`
|
||||||
|
|
||||||
|
### App.vue 要求
|
||||||
|
|
||||||
|
- 只追加 `@use './themes/styles/{主题名}.scss' as {主题名};`
|
||||||
|
- 不在 `App.vue` 里重复写 `page, .wot-theme-{主题名}` 这些挂载块
|
||||||
|
- 如果已有其他主题 `@use`,保持原有顺序和已有块不被破坏,只追加当前主题
|
||||||
|
|
||||||
|
## 完整变量模板
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@mixin {主题名}-theme-vars {
|
||||||
|
/* {主题描述} semantic tokens */
|
||||||
|
/* Primary */
|
||||||
|
--wot-primary-1: #F5F8FFFF;
|
||||||
|
--wot-primary-2: #E5EDFFFF;
|
||||||
|
--wot-primary-3: #B8CFFFFF;
|
||||||
|
--wot-primary-4: #7CA4FFFF;
|
||||||
|
--wot-primary-5: #4480FFFF;
|
||||||
|
--wot-primary-6: #1C64FDFF;
|
||||||
|
--wot-primary-7: #164ED1FF;
|
||||||
|
--wot-primary-8: #1341ADFF;
|
||||||
|
--wot-primary-9: #0F3285FF;
|
||||||
|
--wot-primary-10: #0A235CFF;
|
||||||
|
|
||||||
|
/* Danger */
|
||||||
|
--wot-danger-main: #F14646FF;
|
||||||
|
--wot-danger-hover: #FB7C7CFF;
|
||||||
|
--wot-danger-clicked: #DC2C2CFF;
|
||||||
|
--wot-danger-disabled: #FFC9C9FF;
|
||||||
|
--wot-danger-particular: #FFE3E3FF;
|
||||||
|
--wot-danger-surface: #FFF5F5FF;
|
||||||
|
|
||||||
|
/* Success */
|
||||||
|
--wot-success-main: #12B886FF;
|
||||||
|
--wot-success-hover: #59CDAAFF;
|
||||||
|
--wot-success-clicked: #0F956CFF;
|
||||||
|
--wot-success-disabled: #B8EADBFF;
|
||||||
|
--wot-success-particular: #E7F8F3FF;
|
||||||
|
--wot-success-surface: #F3FBF9FF;
|
||||||
|
|
||||||
|
/* Warning */
|
||||||
|
--wot-warning-main: #F57F00FF;
|
||||||
|
--wot-warning-hover: #FFA94DFF;
|
||||||
|
--wot-warning-clicked: #D05706FF;
|
||||||
|
--wot-warning-disabled: #FFD8A8FF;
|
||||||
|
--wot-warning-particular: #FFE8CCFF;
|
||||||
|
--wot-warning-surface: #FFF6EBFF;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--wot-text-main: #1D1F29FF;
|
||||||
|
--wot-text-secondary: #4E5369FF;
|
||||||
|
--wot-text-auxiliary: #868A9CFF;
|
||||||
|
--wot-text-disabled: #C9CBD4FF;
|
||||||
|
--wot-text-placeholder: #A9ACB8FF;
|
||||||
|
--wot-text-white: #FFFFFFFF;
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
--wot-icon-main: #1D1F29FF;
|
||||||
|
--wot-icon-secondary: #4E5369FF;
|
||||||
|
--wot-icon-auxiliary: #868A9CFF;
|
||||||
|
--wot-icon-disabled: #C9CBD4FF;
|
||||||
|
--wot-icon-placeholder: #A9ACB8FF;
|
||||||
|
--wot-icon-white: #FFFFFFFF;
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
--wot-border-extra-strong: #868A9CFF;
|
||||||
|
--wot-border-strong: #C9CBD4FF;
|
||||||
|
--wot-border-main: #E5E6EBFF;
|
||||||
|
--wot-border-light: #F2F3F5FF;
|
||||||
|
--wot-border-white: #FFFFFFFF;
|
||||||
|
--wot-border-zero: transparent;
|
||||||
|
|
||||||
|
/* Filled */
|
||||||
|
--wot-filled-extra-strong: #C9CBD4FF;
|
||||||
|
--wot-filled-strong: #E5E6EBFF;
|
||||||
|
--wot-filled-content: #F2F3F5FF;
|
||||||
|
--wot-filled-bottom: #F7F8FAFF;
|
||||||
|
--wot-filled-oppo: #FFFFFFFF;
|
||||||
|
--wot-filled-zero: transparent;
|
||||||
|
|
||||||
|
/* Divider */
|
||||||
|
--wot-divider-main: #00000014;
|
||||||
|
--wot-divider-light: #0000000A;
|
||||||
|
--wot-divider-strong: #00000026;
|
||||||
|
--wot-divider-white: #FFFFFFFF;
|
||||||
|
|
||||||
|
/* Feedback */
|
||||||
|
--wot-feedback-hover: #0000000A;
|
||||||
|
--wot-feedback-active: #00000014;
|
||||||
|
--wot-feedback-accent: #1C64FD14;
|
||||||
|
|
||||||
|
/* Opacity filled */
|
||||||
|
--wot-opacfilled-tooltip-toast-cover: #000000BF;
|
||||||
|
--wot-opacfilled-main-cover: #0000008C;
|
||||||
|
--wot-opacfilled-light-cover: #0000004D;
|
||||||
|
|
||||||
|
/* Picker view mask */
|
||||||
|
--wot-picker-view-mask-start-color: #FFFFFFD9;
|
||||||
|
--wot-picker-view-mask-end-color: #FFFFFF33;
|
||||||
|
|
||||||
|
/* Classify application */
|
||||||
|
--wot-classifyapplication-yellow-background: #FFFAF1FF;
|
||||||
|
--wot-classifyapplication-yellow-border: #FDD78CFF;
|
||||||
|
--wot-classifyapplication-yellow-content: #FAAD14FF;
|
||||||
|
--wot-classifyapplication-Cyan-background: #F4FBFDFF;
|
||||||
|
--wot-classifyapplication-Cyan-border: #BDEAF1FF;
|
||||||
|
--wot-classifyapplication-Cyan-content: #22B8CFFF;
|
||||||
|
--wot-classifyapplication-Purple-background: #F9F8FFFF;
|
||||||
|
--wot-classifyapplication-Purple-border: #D0BFFFFF;
|
||||||
|
--wot-classifyapplication-Purple-content: #8059F3FF;
|
||||||
|
--wot-classifyapplication-Grape-background: #FBF6FDFF;
|
||||||
|
--wot-classifyapplication-Grape-border: #EEBEFAFF;
|
||||||
|
--wot-classifyapplication-Grape-content: #AE3EC9FF;
|
||||||
|
--wot-classifyapplication-Pink-background: #FFF0F6FF;
|
||||||
|
--wot-classifyapplication-Pink-border: #FCC2D7FF;
|
||||||
|
--wot-classifyapplication-Pink-content: #FF357CFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
page,
|
||||||
|
.wot-theme-{主题名},
|
||||||
|
.wot-theme-{主题名} .wd-root-portal {
|
||||||
|
@include {主题名}-theme-vars();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出检查清单
|
||||||
|
|
||||||
|
- [ ] 主题文件位于 `src/themes/styles/{主题名}.scss`
|
||||||
|
- [ ] 文件包含一个 `@mixin {主题名}-theme-vars`
|
||||||
|
- [ ] 文件内完成 `page`、`.wot-theme-{主题名}`、`.wot-theme-{主题名} .wd-root-portal` 挂载
|
||||||
|
- [ ] 包含主色、功能色、文字、图标、边框、填充、分割线、反馈、透明填充、Picker View 遮罩、分类色全部变量
|
||||||
|
- [ ] 所有值都是固定色值或 `transparent`
|
||||||
|
- [ ] `App.vue` 中若接入,只追加 `@use './themes/styles/{主题名}.scss' as {主题名};`
|
||||||
|
- [ ] mixin 调用形式为 `@include {主题名}-theme-vars();`
|
||||||
|
|
||||||
|
## 回答与实现规则
|
||||||
|
|
||||||
|
- 用户未明确要求接入时,不要主动改 `App.vue`
|
||||||
|
- 用户未明确要求时,不要顺手生成 dark 主题、变量映射层或额外目录结构
|
||||||
|
- 生成代码时优先保持最小改动,不破坏现有主题文件顺序、选择器范围和项目风格
|
||||||
|
- 如果发现项目里已有不同主题体系,先说明差异,再确认是否仍按本单文件结构落地
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
name: starter-cleaner
|
||||||
|
description: 将 wot-starter v2 模板精简为最小可开发状态,移除文档、演示分包、生成文件和 monorepo 配置,并同步清理相关 Vite 与 package.json 配置。用户要求“清理模板”“移除示例”“生成最小模板”“精简 wot-starter v2”时使用。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Starter Cleaner
|
||||||
|
|
||||||
|
使用随技能提供的确定性脚本清理 `wot-starter` v2,保留基础页面、Wot UI、`uni-echarts` 和 `echarts` 能力。
|
||||||
|
|
||||||
|
## 执行流程
|
||||||
|
|
||||||
|
1. 在项目根目录运行 `git status --short`,确认并告知用户未提交改动会被保留,但待清理目录中的改动会随目录一起删除。
|
||||||
|
2. 如果用户尚未明确要求执行清理,先说明这是破坏性操作并取得确认。
|
||||||
|
3. 先预览清理计划:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node .agents/skills/starter-cleaner/scripts/clean.js --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 确认目标是 `wot-starter` v2 后执行。脚本会删除旧的 `src/pages.json`,然后受控运行 `pnpm dev:h5`,等新的最小 `src/pages.json` 生成后退出 dev server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node .agents/skills/starter-cleaner/scripts/clean.js
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 执行 `pnpm install --lockfile-only` 更新锁文件,再运行 `pnpm type-check` 和 `pnpm lint`。如果依赖不可用,至少检查 `git diff --check` 和清理后的 diff,并将未运行的验证明确告知用户。
|
||||||
|
|
||||||
|
## 清理范围
|
||||||
|
|
||||||
|
脚本执行以下操作,且支持重复运行:
|
||||||
|
|
||||||
|
- 删除 `docs/`、`src/subPages/`、`src/subEcharts/`、`src/subAsyncEcharts/`。
|
||||||
|
- 删除旧的 `src/pages.json`,再通过 `pnpm dev:h5` 触发 `vite-plugin-uni-pages` 重新生成最小页面配置。
|
||||||
|
- 删除 `pnpm-workspace.yaml`,将项目从文档 workspace 恢复为单包项目。
|
||||||
|
- 从 `vite.config.ts` 的 `UniHelperPages` 配置中移除上述三个示例分包入口。
|
||||||
|
- 从 `package.json` 中移除 `docs:*` 与 `lint:docs` 脚本。
|
||||||
|
- 保留 `uni-echarts`、`echarts` 依赖和相关 Vite 配置,方便业务继续使用图表。
|
||||||
|
|
||||||
|
## 安全约束
|
||||||
|
|
||||||
|
- 不直接拼接或扩展删除路径;仅使用脚本内的固定清单。
|
||||||
|
- 不在非 `wot-starter` v2 项目中运行。仅在用户明确确认目标项目后使用 `--force` 跳过项目身份检查。
|
||||||
|
- 不用 `--force` 绕过未提交改动提示;该参数只用于项目身份检查。
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# eslint-disable yaml/plain-scalar
|
||||||
|
interface:
|
||||||
|
display_name: 'FastapiAdmin Cleaner'
|
||||||
|
short_description: '将 FastapiAdmin 清理为最小可开发模板'
|
||||||
|
default_prompt: '使用 $starter-cleaner 将当前 FastapiAdmin 模板精简至最小状态。'
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const dryRun = process.argv.includes('--dry-run')
|
||||||
|
const force = process.argv.includes('--force')
|
||||||
|
|
||||||
|
const pathsToRemove = [
|
||||||
|
'docs',
|
||||||
|
'src/subPages',
|
||||||
|
'src/subEcharts',
|
||||||
|
'src/subAsyncEcharts',
|
||||||
|
'src/pages.json',
|
||||||
|
'pnpm-workspace.yaml',
|
||||||
|
]
|
||||||
|
|
||||||
|
const subPackagePaths = [
|
||||||
|
'src/subPages',
|
||||||
|
'src/subEcharts',
|
||||||
|
'src/subAsyncEcharts',
|
||||||
|
]
|
||||||
|
|
||||||
|
function findProjectRoot(directory) {
|
||||||
|
const packageJsonPath = path.join(directory, 'package.json')
|
||||||
|
if (fs.existsSync(packageJsonPath)) {
|
||||||
|
return directory
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentDirectory = path.dirname(directory)
|
||||||
|
if (parentDirectory === directory) {
|
||||||
|
throw new Error('找不到项目根目录(未发现 package.json)')
|
||||||
|
}
|
||||||
|
|
||||||
|
return findProjectRoot(parentDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageJson(packageJsonPath) {
|
||||||
|
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertWotStarterV2(packageJson) {
|
||||||
|
const isWotStarter = packageJson.name === 'wot-starter'
|
||||||
|
const isV2 = String(packageJson.version ?? '').startsWith('2.')
|
||||||
|
|
||||||
|
if (!force && (!isWotStarter || !isV2)) {
|
||||||
|
throw new Error(
|
||||||
|
`目标项目不是 wot-starter v2(name=${packageJson.name ?? 'unknown'}, version=${packageJson.version ?? 'unknown'})。确认目标无误后可使用 --force。`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePath(projectRoot, relativePath) {
|
||||||
|
const targetPath = path.join(projectRoot, relativePath)
|
||||||
|
|
||||||
|
if (!fs.existsSync(targetPath)) {
|
||||||
|
console.log(`跳过(不存在):${relativePath}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${dryRun ? '计划删除' : '删除'}:${relativePath}`)
|
||||||
|
if (!dryRun) {
|
||||||
|
fs.rmSync(targetPath, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTextFile(filePath, transform) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`跳过(不存在):${path.basename(filePath)}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalContent = fs.readFileSync(filePath, 'utf8')
|
||||||
|
const nextContent = transform(originalContent)
|
||||||
|
if (nextContent === originalContent) {
|
||||||
|
console.log(`跳过(无需修改):${path.basename(filePath)}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${dryRun ? '计划修改' : '修改'}:${path.basename(filePath)}`)
|
||||||
|
if (!dryRun) {
|
||||||
|
fs.writeFileSync(filePath, nextContent, 'utf8')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanViteConfig(projectRoot) {
|
||||||
|
const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
|
||||||
|
|
||||||
|
updateTextFile(viteConfigPath, (content) => {
|
||||||
|
const lines = content.split('\n').filter((line) => {
|
||||||
|
return !subPackagePaths.some((subPackagePath) => {
|
||||||
|
return line.includes(`'${subPackagePath}'`) || line.includes(`"${subPackagePath}"`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return lines.join('\n').replace(/subPackages:\s*\[\s*\],/g, 'subPackages: [],')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanPackageJson(projectRoot, packageJson) {
|
||||||
|
const scriptNames = Object.keys(packageJson.scripts ?? {})
|
||||||
|
const scriptsToRemove = scriptNames.filter(name => name.startsWith('docs:') || name === 'lint:docs')
|
||||||
|
|
||||||
|
if (scriptsToRemove.length === 0) {
|
||||||
|
console.log('跳过(无需修改):package.json')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const scriptName of scriptsToRemove) {
|
||||||
|
delete packageJson.scripts[scriptName]
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${dryRun ? '计划修改' : '修改'}:package.json(移除 ${scriptsToRemove.join(', ')})`)
|
||||||
|
if (!dryRun) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(projectRoot, 'package.json'),
|
||||||
|
`${JSON.stringify(packageJson, null, 2)}\n`,
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGeneratedPagesReady(projectRoot) {
|
||||||
|
const pagesJsonPath = path.join(projectRoot, 'src/pages.json')
|
||||||
|
|
||||||
|
if (!fs.existsSync(pagesJsonPath)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(pagesJsonPath, 'utf8')
|
||||||
|
return content.includes('"pages"') && !subPackagePaths.some(subPackagePath => content.includes(subPackagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopProcess(childProcess) {
|
||||||
|
if (childProcess.exitCode !== null || childProcess.signalCode !== null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
childProcess.kill('SIGTERM')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.kill(-childProcess.pid, 'SIGTERM')
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
childProcess.kill('SIGTERM')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function regeneratePagesJson(projectRoot) {
|
||||||
|
console.log('生成:src/pages.json(pnpm dev:h5)')
|
||||||
|
|
||||||
|
const childProcess = spawn('pnpm', ['dev:h5'], {
|
||||||
|
cwd: projectRoot,
|
||||||
|
detached: process.platform !== 'win32',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
BROWSER: 'none',
|
||||||
|
FORCE_COLOR: '0',
|
||||||
|
},
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
})
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
childProcess.stdout.on('data', (chunk) => {
|
||||||
|
output += chunk.toString()
|
||||||
|
})
|
||||||
|
childProcess.stderr.on('data', (chunk) => {
|
||||||
|
output += chunk.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeoutAt = Date.now() + 30000
|
||||||
|
try {
|
||||||
|
while (Date.now() < timeoutAt) {
|
||||||
|
if (isGeneratedPagesReady(projectRoot)) {
|
||||||
|
console.log('已生成:src/pages.json')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childProcess.exitCode !== null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(250)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`未能通过 pnpm dev:h5 生成 src/pages.json。\n${output.trim()}`)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stopProcess(childProcess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const projectRoot = findProjectRoot(scriptDirectory)
|
||||||
|
const packageJsonPath = path.join(projectRoot, 'package.json')
|
||||||
|
const packageJson = readPackageJson(packageJsonPath)
|
||||||
|
|
||||||
|
assertWotStarterV2(packageJson)
|
||||||
|
console.log(`${dryRun ? '预览清理' : '开始清理'}:${projectRoot}`)
|
||||||
|
|
||||||
|
for (const relativePath of pathsToRemove) {
|
||||||
|
removePath(projectRoot, relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanViteConfig(projectRoot)
|
||||||
|
cleanPackageJson(projectRoot, packageJson)
|
||||||
|
|
||||||
|
if (!dryRun) {
|
||||||
|
await regeneratePagesJson(projectRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(dryRun ? '预览完成,未修改任何文件。' : '清理完成。请运行 pnpm install --lockfile-only 更新锁文件。')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error.message)
|
||||||
|
process.exitCode = 1
|
||||||
|
})
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: wot-ui-cli
|
||||||
|
description: '回答、使用、调试 @wot-ui/cli 时使用。关键词:wot、@wot-ui/cli、CLI、MCP、doctor、usage、lint、list、info、doc、demo、token、changelog、extract、wot mcp。适用于命令查询、参数说明、MCP 接入、本地调试、数据提取与 open-wot 仓库维护。'
|
||||||
|
argument-hint: '命令名、参数、MCP 场景、调试问题或数据提取需求'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wot UI CLI Skill
|
||||||
|
|
||||||
|
这个 skill 用于让 Agent 在处理 `@wot-ui/cli` 本身相关的问题时,优先基于本仓库 README 与实际命令能力回答,而不是把它误当成纯组件库文档。
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 用户询问 `wot` 命令怎么用。
|
||||||
|
- 用户需要区分 `list`、`info`、`doc`、`demo`、`token`、`changelog`、`doctor`、`usage`、`lint`、`mcp`、`extract` 的用途。
|
||||||
|
- 用户要接入 MCP Server,或需要 `wot mcp` 的配置与调试方法。
|
||||||
|
- 用户要在本仓库中调试 `@wot-ui/cli`、验证构建产物、重新提取数据。
|
||||||
|
- 用户的问题本质上是 open-wot 仓库维护问题,而不是单纯的 wot-ui 组件使用问题。
|
||||||
|
|
||||||
|
## 适用范围
|
||||||
|
|
||||||
|
- 关注对象是 `@wot-ui/cli` 这个工具包,以及仓库 `open-wot` 的开发维护流程。
|
||||||
|
- 重点覆盖命令能力、通用参数、MCP、离线数据来源、提取流程、本地调试和发布包边界。
|
||||||
|
- 如果任务是生成 `wd-*` 页面代码、解释组件 props 或给出主题定制方案,应优先使用 `wot-ui-v2` skill。
|
||||||
|
|
||||||
|
## 推荐流程
|
||||||
|
|
||||||
|
1. 先确认用户是在问 CLI 工具本身,还是在借 CLI 查询组件知识。
|
||||||
|
2. 如果是命令使用问题,优先按命令类别回答:组件知识、项目分析、MCP、数据提取、仓库开发。
|
||||||
|
3. 如果是仓库维护问题,优先给出本仓库里的实际调试命令,而不是泛泛而谈。
|
||||||
|
4. 如果涉及组件内容本身,可引导或切换到 `wot-ui-v2` skill。
|
||||||
|
|
||||||
|
## 命令分组
|
||||||
|
|
||||||
|
### 组件知识查询
|
||||||
|
|
||||||
|
- `wot list`
|
||||||
|
- `wot info <Component>`
|
||||||
|
- `wot doc <Component>`
|
||||||
|
- `wot demo <Component> [name]`
|
||||||
|
- `wot token [Component]`
|
||||||
|
- `wot changelog [version] [component]`
|
||||||
|
|
||||||
|
### 项目分析
|
||||||
|
|
||||||
|
- `wot doctor [dir]`
|
||||||
|
- `wot usage [dir]`
|
||||||
|
- `wot lint [dir]`
|
||||||
|
|
||||||
|
### MCP
|
||||||
|
|
||||||
|
- `wot mcp`
|
||||||
|
|
||||||
|
### 数据提取与仓库维护
|
||||||
|
|
||||||
|
- `pnpm extract:cli --wot-dir ../wot-ui --output data/v2.json`
|
||||||
|
- `pnpm extract:clone`
|
||||||
|
- `pnpm exec tsx src/index.ts <command>`
|
||||||
|
- `pnpm build`
|
||||||
|
- `node dist/index.mjs <command>`
|
||||||
|
|
||||||
|
## 工作规则
|
||||||
|
|
||||||
|
- 包名是 `@wot-ui/cli`,实际可执行命令是 `wot`。
|
||||||
|
- 回答命令问题时,优先用仓库 README 中已承诺的行为和参数,不臆造未声明子命令。
|
||||||
|
- 回答本地调试问题时,优先给源码入口:`pnpm exec tsx src/index.ts ...`。
|
||||||
|
- 回答构建产物问题时,再给 `node dist/index.mjs ...`。
|
||||||
|
- 回答 MCP 问题时,要说明 `wot mcp` 走 stdio,终端无交互输出通常是正常现象。
|
||||||
|
- 回答提取逻辑问题时,要说明数据主要来自上游 `wot-ui/wot-ui` 的 markdown 与 SCSS 源码。
|
||||||
|
- 当用户问的是组件知识但入口是 CLI,也要保留“这是通过 CLI 查询组件知识”这一层语义。
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [Wot UI CLI 概览](./references/overview.md)
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# Wot UI CLI Overview
|
||||||
|
|
||||||
|
本文件根据本仓库 README 整理,目标是让 Agent 快速理解 `@wot-ui/cli` 的能力边界、命令分组、MCP 接入方式、开发调试路径与数据来源。
|
||||||
|
|
||||||
|
## Package Identity
|
||||||
|
|
||||||
|
- 包名:`@wot-ui/cli`
|
||||||
|
- 仓库:open-wot
|
||||||
|
- 可执行命令:`wot`
|
||||||
|
- 核心定位:wot-ui 的 AI 工具链仓库,提供 CLI、MCP Server、离线组件知识库与数据提取脚本。
|
||||||
|
|
||||||
|
## Repository Positioning
|
||||||
|
|
||||||
|
- 面向 wot-ui v2 的组件知识查询工具。
|
||||||
|
- 面向本地项目的组件使用分析与 lint 工具。
|
||||||
|
- 面向 AI 客户端的 MCP stdio 服务。
|
||||||
|
- 面向仓库维护者的数据提取与同步工作流。
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
### Component Knowledge
|
||||||
|
|
||||||
|
- `list`:列出可用组件。
|
||||||
|
- `info <Component>`:查看 props、events、slots、CSS 变量。
|
||||||
|
- `doc <Component>`:输出组件 markdown 文档。
|
||||||
|
- `demo <Component> [name]`:查看 demo 列表或指定 demo 源码。
|
||||||
|
- `token [Component]`:查看组件 CSS 变量与默认值。
|
||||||
|
- `changelog [version] [component]`:查看版本更新记录。
|
||||||
|
|
||||||
|
### Project Analysis
|
||||||
|
|
||||||
|
- `doctor [dir]`:检查项目依赖、运行环境与基础集成情况。
|
||||||
|
- `usage [dir]`:统计 `.vue` 文件中的 `wd-*` 使用情况。
|
||||||
|
- `lint [dir]`:检查未知组件、空按钮等规则。
|
||||||
|
|
||||||
|
### MCP Server
|
||||||
|
|
||||||
|
- `mcp`:启动 MCP stdio server。
|
||||||
|
|
||||||
|
## Typical User Flows
|
||||||
|
|
||||||
|
### Query Component Knowledge Through CLI
|
||||||
|
|
||||||
|
常用顺序:
|
||||||
|
|
||||||
|
1. `wot list`
|
||||||
|
2. `wot info Button`
|
||||||
|
3. `wot demo Button basic`
|
||||||
|
4. `wot doc Button`
|
||||||
|
5. `wot token Button`
|
||||||
|
|
||||||
|
### Analyze A Local Project
|
||||||
|
|
||||||
|
常用顺序:
|
||||||
|
|
||||||
|
1. `wot doctor ./my-project`
|
||||||
|
2. `wot usage ./my-project`
|
||||||
|
3. `wot lint ./my-project`
|
||||||
|
|
||||||
|
### Run MCP In A Client
|
||||||
|
|
||||||
|
典型配置:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"wot-ui": {
|
||||||
|
"command": "wot",
|
||||||
|
"args": ["mcp"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
当前 README 明确列出的 MCP tools 有:
|
||||||
|
|
||||||
|
- `wot_list`
|
||||||
|
- `wot_info`
|
||||||
|
- `wot_doc`
|
||||||
|
- `wot_demo`
|
||||||
|
- `wot_token`
|
||||||
|
- `wot_changelog`
|
||||||
|
- `wot_lint`
|
||||||
|
|
||||||
|
## Common Flags
|
||||||
|
|
||||||
|
多数查询命令支持:
|
||||||
|
|
||||||
|
- `--format text`
|
||||||
|
- `--format json`
|
||||||
|
- `--version v2`
|
||||||
|
|
||||||
|
## Install And Run
|
||||||
|
|
||||||
|
### Global Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @wot-ui/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后直接用 `wot`。
|
||||||
|
|
||||||
|
### Source Mode In This Repo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm exec tsx src/index.ts list
|
||||||
|
pnpm exec tsx src/index.ts info Button
|
||||||
|
pnpm exec tsx src/index.ts mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
适合本地调试源码,不依赖全局安装。
|
||||||
|
|
||||||
|
### Built Artifact Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
node dist/index.mjs list
|
||||||
|
```
|
||||||
|
|
||||||
|
适合验证构建产物行为。
|
||||||
|
|
||||||
|
## MCP Operational Notes
|
||||||
|
|
||||||
|
- `wot mcp` 走 stdio。
|
||||||
|
- 终端里没有交互输出通常是正常现象。
|
||||||
|
- 若要调试 tool 与 prompt 调用过程,建议配合 MCP Inspector 或编辑器内置 MCP 客户端。
|
||||||
|
|
||||||
|
## Data Source And Extraction
|
||||||
|
|
||||||
|
当前版本聚焦 `wot-ui v2`。
|
||||||
|
|
||||||
|
离线数据主要提取自上游 `wot-ui/wot-ui` 的:
|
||||||
|
|
||||||
|
- `docs/component/*.md`
|
||||||
|
- `docs/guide/changelog.md`
|
||||||
|
- `src/uni_modules/wot-ui/components/*/index.scss`
|
||||||
|
|
||||||
|
重新生成数据有两种方式:
|
||||||
|
|
||||||
|
### Use A Local Wot UI Repo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm extract:cli --wot-dir ../wot-ui --output data/v2.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone Latest Upstream And Extract
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm extract:clone
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repo Layout
|
||||||
|
|
||||||
|
- `src`:CLI、MCP 与项目分析源码。
|
||||||
|
- `data`:离线组件元数据。
|
||||||
|
- `scripts`:提取脚本。
|
||||||
|
- `skills`:面向 Agent 的技能说明。
|
||||||
|
- `test`:根包测试。
|
||||||
|
|
||||||
|
## Local Development Commands
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
- Node.js `>= 20`
|
||||||
|
- pnpm `10.x`
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
pnpm test:all
|
||||||
|
pnpm build:all
|
||||||
|
pnpm typecheck:all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package-Level Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
pnpm test
|
||||||
|
pnpm typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent Guidance
|
||||||
|
|
||||||
|
- 如果用户问的是命令怎么用,按“命令组 + 示例命令 + 输出用途”来回答。
|
||||||
|
- 如果用户问的是仓库维护或调试,优先给本仓库中的真实命令和目录。
|
||||||
|
- 如果用户问的是组件本身怎么写页面,不要停留在 CLI 层,应切换到 `wot-ui-v2` skill。
|
||||||
|
- 不要把 `wot` 命令和 `@wot-ui/ui` 组件库 API 混为一谈。
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
name: "wot-ui-unocss-preset-guide"
|
||||||
|
description: "指导安装、配置并使用 @wot-ui/unocss-preset。Invoke when 用户询问该预设的接入、配置、使用示例或常见问题排查。"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wot UnoCSS Preset 使用指南
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
当用户询问以下内容时,优先使用本 Skill:
|
||||||
|
|
||||||
|
- 如何安装 `@wot-ui/unocss-preset`
|
||||||
|
- 如何在 `unocss.config.ts` 配置 `presetWot`
|
||||||
|
- `prefix`、`preflight`、`baseTokens` 怎么用
|
||||||
|
- 为什么类名不生效、自动补全不出现、CI 与本地结果不一致
|
||||||
|
|
||||||
|
目标是给出可直接复制的最佳实践,帮助用户快速完成接入并稳定运行。
|
||||||
|
|
||||||
|
## 最小安装步骤
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D unocss
|
||||||
|
pnpm add @wot-ui/unocss-preset
|
||||||
|
```
|
||||||
|
|
||||||
|
## 推荐配置(完整示例)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { presetWot } from '@wot-ui/unocss-preset'
|
||||||
|
import { defineConfig } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWot({
|
||||||
|
prefix: 'wot',
|
||||||
|
preflight: true,
|
||||||
|
baseTokens: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置项说明
|
||||||
|
|
||||||
|
- `prefix`:工具类前缀,默认 `wot`。示例:`wot-text-primary`、`wot-m-main`。
|
||||||
|
- `preflight`:是否注入 wot-ui CSS 变量,默认 `true`。
|
||||||
|
- `baseTokens`:是否开放基础色板和原始 token 类名,默认 `false`。
|
||||||
|
|
||||||
|
## 常用类名示例
|
||||||
|
|
||||||
|
- 颜色:`wot-text-primary`、`wot-bg-danger-surface`、`wot-border-border-main`
|
||||||
|
- 间距:`wot-m-main`、`wot-gap-tight`、`wot-gap-x-loose`
|
||||||
|
- 内边距:`wot-p-main`、`wot-px-tight`、`wot-pb-loose`
|
||||||
|
- 圆角:`wot-rounded-md`、`wot-rounded-full`
|
||||||
|
- 字重:`wot-font-medium`、`wot-font-semibold`
|
||||||
|
- 排版:`wot-text-body-main`、`wot-text-title-large`
|
||||||
|
- 透明度:`wot-opacity-disabled`
|
||||||
|
- 描边:`wot-border-stroke-main`
|
||||||
|
|
||||||
|
## 上手示例(可复制)
|
||||||
|
|
||||||
|
### 1) 一个“卡片”示例(uni-app / Vue)
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view class="wot-bg-filled-oppo wot-rounded-2xl wot-p-super-loose wot-border-border-main wot-border-stroke-main">
|
||||||
|
<text class="wot-text-title-large wot-text-text-main wot-font-semibold">
|
||||||
|
Wot UnoCSS Preset
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<view class="wot-mt-tight">
|
||||||
|
<text class="wot-text-body-main wot-text-text-secondary">
|
||||||
|
wot-text-body-main + wot-text-text-secondary
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="wot-mt-loose wot-bg-primary wot-rounded-full wot-px-main wot-py-extra-tight">
|
||||||
|
<text class="wot-text-label-large wot-text-text-white wot-font-semibold">
|
||||||
|
wot-bg-primary
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) 间距/布局示例(常见组合)
|
||||||
|
|
||||||
|
- 外边距:`wot-mt-tight`、`wot-mt-main`、`wot-mt-super-loose`
|
||||||
|
- 内边距:`wot-p-loose`、`wot-px-main`、`wot-py-extra-tight`
|
||||||
|
- 横向/纵向组合:`wot-mx-main`、`wot-my-loose`
|
||||||
|
- gap:`wot-gap-tight`、`wot-gap-x-main`、`wot-gap-y-loose`
|
||||||
|
|
||||||
|
### 3) 语义色示例(背景/文字/边框)
|
||||||
|
|
||||||
|
- 背景:`wot-bg-primary`、`wot-bg-success-surface`、`wot-bg-warning-surface`、`wot-bg-danger-surface`
|
||||||
|
- 文字:`wot-text-text-main`、`wot-text-text-secondary`、`wot-text-primary`、`wot-text-success-main`
|
||||||
|
- 边框:`wot-border-border-main`、`wot-border-success-main`、`wot-border-warning-main`、`wot-border-danger-main`
|
||||||
|
|
||||||
|
### 4) 暗黑模式示例
|
||||||
|
|
||||||
|
预设会输出暗色变量选择器 `.wot-theme-dark ...`,你只需要在根节点加 class:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view :class="dark ? 'wot-theme-dark' : ''">
|
||||||
|
<view class="wot-bg-filled-oppo wot-p-main wot-rounded-lg">
|
||||||
|
<text class="wot-text-text-main">当前主题:{{ dark ? 'Dark' : 'Light' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5) 打开 baseTokens(可选)
|
||||||
|
|
||||||
|
当你希望使用基础色板/原始 token 时启用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWot({
|
||||||
|
baseTokens: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
启用后会额外提供类似 `wot-base-black` 这类 token(用于 `theme.colors` 与颜色规则匹配)。
|
||||||
|
|
||||||
|
## 轻量提醒
|
||||||
|
|
||||||
|
- 历史项目若仍使用 `w-` 前缀,可通过 `prefix: 'w'` 兼容。
|
||||||
|
- 若 playground/子包构建异常,优先检查是否:
|
||||||
|
- 使用包名导入(如 `@wot-ui/unocss-preset`)
|
||||||
|
- 在依赖主包产物的场景下先执行主包构建
|
||||||
|
- 若需要同步上游 wot-ui 变量,可使用:
|
||||||
|
- `pnpm generate:css-vars:clone`
|
||||||
|
|
||||||
|
## 常见问题排查
|
||||||
|
|
||||||
|
1. 类名不生效
|
||||||
|
- 确认项目已启用 UnoCSS 且 `presetWot()` 已加入 `presets`。
|
||||||
|
- 确认类名前缀与配置一致(默认 `wot-`)。
|
||||||
|
|
||||||
|
2. VS Code 没有自动补全
|
||||||
|
- 确认安装 UnoCSS 官方扩展。
|
||||||
|
- 确认编辑器能定位到项目 `unocss.config.ts`。
|
||||||
|
- 确认类名写在扩展可扫描的文件类型内。
|
||||||
|
|
||||||
|
3. CI 能复现但本地不复现(或反过来)
|
||||||
|
- 对齐 Node/pnpm 版本与锁文件。
|
||||||
|
- 对齐执行顺序(例如先构建主包,再构建依赖主包产物的 playground)。
|
||||||
|
|
||||||
|
## 响应风格要求
|
||||||
|
|
||||||
|
- 输出简洁、可执行,优先给“可复制配置 + 最短排查路径”。
|
||||||
|
- 当用户目标明确时,先给结论和配置,再补充原因说明。
|
||||||
|
- 不擅自引入本项目未使用的额外工具链或复杂抽象。
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: wot-ui-v2
|
||||||
|
description: '回答、生成、重构、排查 wot-ui v2 相关代码时使用。关键词:wot-ui、uni-app、Vue3、wd-、ConfigProvider、useToast、useDialog、Form、Popup、theme、llms-full。适用于组件选型、API 查询、示例页面生成、主题定制、常见坑排查。'
|
||||||
|
argument-hint: '组件名、页面场景、问题描述或主题定制需求'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wot UI V2 Skill
|
||||||
|
|
||||||
|
这个 skill 用于让 Agent 在处理 wot-ui v2 相关任务时,优先采用组件库既有能力、遵守 uni-app 场景限制,并结合本仓库提供的 `wot` CLI 查询离线知识。
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 用户询问某个 `wd-*` 组件的基础用法、属性、事件、插槽或样式变量。
|
||||||
|
- 需要生成或重构 `uni-app + Vue 3 + TypeScript` 的 wot-ui 页面或组件代码。
|
||||||
|
- 需要在 `ConfigProvider`、主题变量、暗黑模式、国际化、反馈类 hooks、表单等场景下给出正确做法。
|
||||||
|
- 需要排查文档中常见的 `Toast`、`Dialog`、`Popup`、`Tabs`、`Slider`、样式覆盖问题。
|
||||||
|
- 用户只是泛化地提到主题定制,但还没有明确要求按“单文件主题 SCSS + App.vue 只 `@use`”的结构生成时。
|
||||||
|
|
||||||
|
## 推荐流程
|
||||||
|
|
||||||
|
1. 先用本仓库 CLI 或 MCP 工具查组件知识。
|
||||||
|
2. 再根据项目实际安装方式决定导入路径与集成方式。
|
||||||
|
3. 优先复用现成的 `wd-*` 组件、hooks、主题变量与组合模式,不要退化成原生标签堆砌。
|
||||||
|
4. 如果问题涉及约束或坑位,再查阅 [参考知识](./references/overview.md)。
|
||||||
|
5. 如果用户明确要求生成 `src/themes/styles/{主题名}.scss` 单文件主题,并把挂载逻辑收进主题文件,优先切换到 `create-wot-ui-theme` skill。
|
||||||
|
|
||||||
|
## 查询顺序
|
||||||
|
|
||||||
|
1. `wot list` 找组件名。
|
||||||
|
2. `wot info <Component>` 看 props、events、slots、CSS 变量。
|
||||||
|
3. `wot demo <Component>` 看 demo 名称或具体 demo 代码。
|
||||||
|
4. `wot doc <Component>` 看完整 markdown 文档。
|
||||||
|
5. `wot token <Component>` 看主题变量。
|
||||||
|
|
||||||
|
## 工作规则
|
||||||
|
|
||||||
|
- 默认把 wot-ui 视为 `uni-app + Vue 3 + TypeScript` 组件库。
|
||||||
|
- 写页面时优先输出 `script setup` 风格。
|
||||||
|
- 反馈类能力如 `useToast`、`useDialog`、`useNotify`、`useImagePreview`、`useVideoPreview`,除了 hook 调用外,通常还需要页面内显式声明对应组件实例。
|
||||||
|
- 文档里经常出现 `@/uni_modules/wot-ui` 导入路径;如果用户项目采用 npm 安装,应切换成 `@wot-ui/ui`。
|
||||||
|
- 主题定制优先走 `ConfigProvider` 和 CSS 变量,不优先建议深度覆盖内部类名。
|
||||||
|
- 生成代码时尽量沿用组件库文档里的命名和交互模式,例如 `v-model:visible`、`before-confirm`、`confirm`、`change`、`custom-class`、`custom-style`。
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [Wot UI V2 概览](./references/overview.md)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Wot UI V2 Overview
|
||||||
|
|
||||||
|
本文件根据 wot-ui v2 的 `llms-full.txt` 与本仓库现有 CLI 工作流提炼,目标是帮助 Agent 快速掌握适合生成代码与回答问题的高价值知识,而不是逐字复制官方文档。
|
||||||
|
|
||||||
|
## Product Positioning
|
||||||
|
|
||||||
|
- Wot UI v2 是面向 `uni-app` 的 `Vue 3 + TypeScript` 组件库。
|
||||||
|
- 覆盖微信小程序、支付宝小程序、钉钉小程序、H5、APP 等平台。
|
||||||
|
- 组件命名统一为 `wd-*`。
|
||||||
|
- 组件库强调 AI 友好、主题定制、暗黑模式、国际化与跨端一致性。
|
||||||
|
|
||||||
|
## Installation And Integration
|
||||||
|
|
||||||
|
- npm 安装:`pnpm add @wot-ui/ui`
|
||||||
|
- 使用前需要安装 `sass`。
|
||||||
|
- `uni_modules` 安装模式天然支持 easycom 自动引入。
|
||||||
|
- npm 安装模式通常需要配置 vite resolver 或 easycom。
|
||||||
|
- CLI 项目在 npm 模式下可在 `tsconfig.json` 中加入 `@wot-ui/ui/global` 以增强全局组件类型提示。
|
||||||
|
|
||||||
|
## Import Rules
|
||||||
|
|
||||||
|
- npm 安装项目:组合式函数、类型和工具优先从 `@wot-ui/ui` 导入。
|
||||||
|
- `uni_modules` 安装项目:文档中的 `@/uni_modules/wot-ui` 路径通常可直接使用。
|
||||||
|
- 官方文档示例很多基于 `uni_modules` 路径,回答时要按用户项目实际安装方式转换。
|
||||||
|
|
||||||
|
## High Value Conventions
|
||||||
|
|
||||||
|
- 反馈类组件不能依赖全局挂载。页面内通常需要显式写出 `wd-toast`、`wd-dialog`、`wd-notify`、`wd-image-preview`、`wd-video-preview` 等实例。
|
||||||
|
- `useToast`、`useDialog`、`useNotify`、`useQueue` 等 hooks 基于 `provide/inject`,应在 `setup` 中调用。
|
||||||
|
- 页面内如果存在多个 `wd-dialog` 或 `wd-toast`,需要通过 `selector` 区分,否则可能出现实例冲突或重复弹出。
|
||||||
|
- 自定义组件中如果要覆盖 wot-ui 内部样式,小程序环境通常需要把组件配置为 `styleIsolation: 'shared'`。
|
||||||
|
- 在 `Popup`、`ActionSheet`、`DropDownItem` 等延迟渲染弹层里使用 `Slider`、`Tabs` 等依赖尺寸计算的组件时,打开后应调用实例方法重新初始化,例如 `initSlider()` 或 `updateLineStyle()`。
|
||||||
|
|
||||||
|
## Theme And Styling
|
||||||
|
|
||||||
|
- 主题定制优先走 CSS 变量。
|
||||||
|
- Design Token 分三层:基础变量、语义变量、组件变量。
|
||||||
|
- 局部或全局主题可通过 `wd-config-provider` 的 `theme` 和 `theme-vars` 控制。
|
||||||
|
- 深色模式通过 `wd-config-provider theme="dark"` 开启。
|
||||||
|
- 更推荐覆盖语义变量或组件变量,不推荐优先依赖深层 class 选择器覆盖。
|
||||||
|
|
||||||
|
## Common UI Patterns
|
||||||
|
|
||||||
|
- 表单场景优先组合 `wd-form`、`wd-form-item`、`wd-input`、`wd-textarea`、`wd-picker`、`wd-calendar`、`wd-select-picker`。
|
||||||
|
- 弹层类场景优先使用 `wd-popup`、`wd-dialog`、`wd-action-sheet`、`wd-tooltip`、`wd-popover`。
|
||||||
|
- 反馈类场景优先使用 `useToast`、`useDialog`、`useNotify`,不要直接手写临时弹层。
|
||||||
|
- 列表和展示类场景优先考虑 `wd-cell`、`wd-card`、`wd-tag`、`wd-badge`、`wd-empty`、`wd-loadmore`、`wd-skeleton`。
|
||||||
|
- 导航与布局类场景优先考虑 `wd-navbar`、`wd-tabs`、`wd-tabbar`、`wd-sidebar`、`wd-row`、`wd-col`、`wd-gap`。
|
||||||
|
|
||||||
|
## Interaction Patterns
|
||||||
|
|
||||||
|
- 大量组件采用 `v-model` 或 `v-model:visible` 控制状态。
|
||||||
|
- 表单和选择类组件普遍提供 `confirm`、`change`、`close` 等事件。
|
||||||
|
- 反馈组件常通过 hook 返回方法对象,例如 `toast.success()`、`dialog.confirm()`。
|
||||||
|
- `Popover`、`Tooltip`、`SwipeAction` 等场景常与 `useQueue().closeOutside()` 配合,实现点击外部关闭。
|
||||||
|
|
||||||
|
## Component Selection Hints
|
||||||
|
|
||||||
|
- 需要主操作按钮时用 `wd-button`,不要先写原生 `button`。
|
||||||
|
- 需要列表入口、设置页、表单容器时优先用 `wd-cell` 和 `wd-cell-group`。
|
||||||
|
- 需要轻量提示时优先用 `useToast`;需要确认交互时优先用 `useDialog`。
|
||||||
|
- 需要单选或多选弹层时优先用 `wd-select-picker`、`wd-picker`、`wd-cascader`。
|
||||||
|
- 需要统一主题或暗黑模式时优先用 `wd-config-provider`。
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- `Toast`、`Dialog` 等函数式调用没有效果,先检查页面里是否声明了对应组件实例。
|
||||||
|
- 同一个页面里多个无 `selector` 的反馈组件可能相互干扰。
|
||||||
|
- npm 模式使用国际化时,开发态可能需要在 Vite 的 `optimizeDeps.exclude` 中排除 `@wot-ui/ui`。
|
||||||
|
- 文档里的导入路径和项目实际安装方式不一致时,回答要主动修正。
|
||||||
|
- 在弹层中直接渲染依赖尺寸测量的组件时,初始化时机往往比 API 本身更关键。
|
||||||
|
|
||||||
|
## AI Response Heuristics
|
||||||
|
|
||||||
|
- 回答基础用法时,优先给最小可运行模板,再补充常用 props。
|
||||||
|
- 生成页面时,优先给完整的 `template + script setup + style` 结构。
|
||||||
|
- 如果项目已使用 wot-ui,不要建议换用其他 UI 库。
|
||||||
|
- 如果 wot-ui 已有现成组件,就不要用原生结构重复造轮子。
|
||||||
|
- 如果用户只问某个组件,优先给该组件最常见 3 到 5 个用法,不要把整份文档全部展开。
|
||||||
|
|
||||||
|
## Repo-Specific Workflow
|
||||||
|
|
||||||
|
- 在本仓库中,优先使用 `wot list`、`wot info`、`wot doc`、`wot demo`、`wot token` 获取组件知识。
|
||||||
|
- 当仓库数据与线上文档不一致时,以用户目标为准,并明确指出仓库离线数据可能需要重新提取。
|
||||||
|
- 若要补数据或修提取逻辑,关注 `scripts/extract.ts`、`data/v2.json`、`src/data/*` 与相应命令实现。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 开发环境变量
|
||||||
|
# 在开发模式下使用 (npm run dev)
|
||||||
|
|
||||||
|
# API 基础 URL - 开发环境
|
||||||
|
# VITE_API_BASE_URL=http://localhost:8001
|
||||||
|
VITE_API_BASE_URL=https://service.fastapiadmin.com
|
||||||
|
|
||||||
|
# 环境名称
|
||||||
|
VITE_ENV_NAME=development
|
||||||
|
|
||||||
|
# 项目名称
|
||||||
|
VITE_APP_TITLE=fastapiadmin
|
||||||
|
|
||||||
|
# 代理前缀
|
||||||
|
VITE_APP_BASE_API=/api/v1
|
||||||
|
|
||||||
|
# 应用端口(vite dev server 启动端口,对应 vite.config.ts 的 server.port,缺省 vite 默认 5173)
|
||||||
|
VITE_APP_PORT=5180
|
||||||
|
|
||||||
|
# 超时时间
|
||||||
|
VITE_TIMEOUT=10000
|
||||||
|
|
||||||
|
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
||||||
|
# VITE_APP_WS_ENDPOINT= ws://localhost:5180/ws
|
||||||
|
VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 生产环境变量
|
||||||
|
# 在生产构建时使用 (npm run build)
|
||||||
|
|
||||||
|
# API 基础 URL - 生产环境
|
||||||
|
VITE_API_BASE_URL=https://service.fastapiadmin.com
|
||||||
|
|
||||||
|
# 环境名称
|
||||||
|
VITE_ENV_NAME=production
|
||||||
|
|
||||||
|
# 项目名称
|
||||||
|
VITE_APP_TITLE=fastapiadmin
|
||||||
|
|
||||||
|
# 代理前缀
|
||||||
|
VITE_APP_BASE_API=/api/v1
|
||||||
|
|
||||||
|
# 应用端口
|
||||||
|
VITE_APP_PORT=5180
|
||||||
|
|
||||||
|
# 超时时间
|
||||||
|
VITE_TIMEOUT=10000
|
||||||
|
|
||||||
|
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
||||||
|
VITE_APP_WS_ENDPOINT= ws://localhost:5180/ws
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"disableEmoji": false,
|
||||||
|
"list": ["test", "feat", "fix", "chore", "docs", "refactor", "style", "ci", "perf", "release", "revert", "build"],
|
||||||
|
"maxMessageLength": 64,
|
||||||
|
"minMessageLength": 3,
|
||||||
|
"questions": ["type", "scope", "subject", "body", "breaking", "issues", "lerna"],
|
||||||
|
"scopes": [],
|
||||||
|
"types": {
|
||||||
|
"chore": {
|
||||||
|
"description": "Chore | 构建/工程依赖/工具",
|
||||||
|
"emoji": "🚀",
|
||||||
|
"value": "chore"
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"description": "Continuous Integration | CI 配置",
|
||||||
|
"emoji": "👷",
|
||||||
|
"value": "ci"
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
"description": "Documentation | 文档",
|
||||||
|
"emoji": "✏️ ",
|
||||||
|
"value": "docs"
|
||||||
|
},
|
||||||
|
"feat": {
|
||||||
|
"description": "Features | 新功能",
|
||||||
|
"emoji": "✨",
|
||||||
|
"value": "feat"
|
||||||
|
},
|
||||||
|
"fix": {
|
||||||
|
"description": "Bug Fixes | Bug 修复",
|
||||||
|
"emoji": "🐛",
|
||||||
|
"value": "fix"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"description": "Performance Improvements | 性能优化",
|
||||||
|
"emoji": "⚡",
|
||||||
|
"value": "perf"
|
||||||
|
},
|
||||||
|
"refactor": {
|
||||||
|
"description": "Code Refactoring | 代码重构",
|
||||||
|
"emoji": "♻️ ",
|
||||||
|
"value": "refactor"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"description": "Create a release commit | 发版提交",
|
||||||
|
"emoji": "🏹",
|
||||||
|
"value": "release"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"description": "Styles | 风格",
|
||||||
|
"emoji": "💄",
|
||||||
|
"value": "style"
|
||||||
|
},
|
||||||
|
"revert": {
|
||||||
|
"description": "Revert | 回退",
|
||||||
|
"emoji": "⏪",
|
||||||
|
"value": "revert"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"description": "Build System | 打包构建",
|
||||||
|
"emoji": "📦",
|
||||||
|
"value": "build"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"description": "Tests | 测试",
|
||||||
|
"emoji": "✅",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://github.com/Moonofweisheng/sponsors']
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
name: 向 FastapiAdmin 反馈 Bug
|
||||||
|
description: 创建一个 Issue 描述你遇到的问题。
|
||||||
|
title: '[Bug 上报] 请在此填写标题'
|
||||||
|
labels: ['🐞bug: need confirm']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
在向我们提交 Bug 报告前,请优先使用以下方式尝试解决问题:
|
||||||
|
- 在组件文档 [wot-starter](https://starter.wot-ui.cn/) 确认使用方法是否正确
|
||||||
|
- 尝试在 [Issue](https://github.com/wot-ui/wot-starter/issues) 列表中搜索相同问题
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: FastapiAdmin 版本号
|
||||||
|
description: 你正在使用的模板版本号(请填写 package.json 里版本)
|
||||||
|
placeholder: 例如:0.1.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: 平台
|
||||||
|
multiple: true
|
||||||
|
description: 选择对应的平台
|
||||||
|
options:
|
||||||
|
- h5
|
||||||
|
- 微信小程序
|
||||||
|
- 支付宝小程序
|
||||||
|
- APP
|
||||||
|
- 钉钉小程序
|
||||||
|
- 其他小程序
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: reproduce
|
||||||
|
attributes:
|
||||||
|
label: 复现Demo地址(如不提供,将被直接关闭)
|
||||||
|
description: |
|
||||||
|
我们需要你提供一个最小重现demo,以便于我们帮你排查问题。你可以通过 fork 本项目,快速创建一个 wot-ui 项目,并添加相关复现逻辑来提供。不要随便填写一个东西,这会导致你的 issue 被直接关闭。即使在你看来问题很容易复现,也请认真对待,因为一个完整复现demo可以大大提高我们排查问题的效率。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce-steps
|
||||||
|
attributes:
|
||||||
|
label: 重现步骤
|
||||||
|
description: |
|
||||||
|
请提供一个最简洁清晰的重现步骤,方便我们快速重现问题。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: 期望的结果是什么?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actually-happening
|
||||||
|
attributes:
|
||||||
|
label: 实际的结果是什么?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: uni-app
|
||||||
|
attributes:
|
||||||
|
label: 环境信息
|
||||||
|
description: |
|
||||||
|
在这里填写你的环境信息
|
||||||
|
- 发行平台: [如 微信小程序、H5平台、App等]
|
||||||
|
- 操作系统 [如 iOS 12.1.2、Android 7.0]
|
||||||
|
- HBuilderX版本 [如使用HBuilderX,则需提供 HBuilderX 版本号]
|
||||||
|
- uni-app版本 [如使用Vue-cli创建/运行项目,则提供`npm run info`的运行结果]
|
||||||
|
- 设备信息 [如 iPhone8 Plus]
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: 其他补充信息
|
||||||
|
description: |
|
||||||
|
根据你的分析,出现这个问题的原因可能在哪里,或者你认为可能产生关联的信息:比如 Vue 版本、vite 版本、Node 版本、采用哪种自动引入方案等,或者进行了哪些配置,使用了哪些插件等信息。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: 向 FastapiAdmin 提出新功能需求
|
||||||
|
description: 创建一个 Issue 描述一下你的功能需求。
|
||||||
|
title: '[新功能需求] 请在此填写标题'
|
||||||
|
labels: ['feature: need confirm']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
在提交功能需求前,请注意:
|
||||||
|
|
||||||
|
- 确认这是一个通用功能,并且无法通过现有的 API 或 Slot 实现。
|
||||||
|
- 尝试在 [Issue](https://github.com/wot-ui/wot-starter/issues)列表中搜索,并且没有发现同样的需求。
|
||||||
|
- 请确保描述清楚你的需求,以便其他开发者更好地理解你的需求。
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: 这个功能解决了什么问题?
|
||||||
|
description: 请尽可能详细地说明这个功能的使用场景。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: api
|
||||||
|
attributes:
|
||||||
|
label: 你期望的 API 是什么样子的?
|
||||||
|
description: 描述一下这个新功能的 API,并提供一些代码示例。
|
||||||
|
placeholder: |
|
||||||
|
```xml
|
||||||
|
<wd-interesting some-prop="xxx" />
|
||||||
|
```
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<!-- (将"[ ]"更新为"[x]"以勾选一个框) -->
|
||||||
|
|
||||||
|
### 🤔 这个 PR 的性质是?(至少选择一个)
|
||||||
|
|
||||||
|
- [ ] 日常 bug 修复
|
||||||
|
- [ ] 新特性提交
|
||||||
|
- [ ] 站点、文档改进
|
||||||
|
- [ ] 演示代码改进
|
||||||
|
- [ ] 组件样式/交互改进
|
||||||
|
- [ ] TypeScript 定义更新
|
||||||
|
- [ ] CI/CD 改进
|
||||||
|
- [ ] 包体积优化
|
||||||
|
- [ ] 性能优化
|
||||||
|
- [ ] 功能增强
|
||||||
|
- [ ] 国际化改进
|
||||||
|
- [ ] 代码重构
|
||||||
|
- [ ] 代码风格优化
|
||||||
|
- [ ] 测试用例
|
||||||
|
- [ ] 分支合并
|
||||||
|
- [ ] 其他改动(是关于什么的改动?)
|
||||||
|
|
||||||
|
### 🔗 相关 Issue
|
||||||
|
|
||||||
|
<!--
|
||||||
|
1. 描述相关需求的来源,如相关的 issue 讨论链接。
|
||||||
|
-->
|
||||||
|
|
||||||
|
### 💡 需求背景和解决方案
|
||||||
|
|
||||||
|
<!--
|
||||||
|
1. 要解决的具体问题。
|
||||||
|
2. 列出最终的 API 实现和用法。
|
||||||
|
3. 涉及UI/交互变动需要有截图或 GIF。
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### ☑️ 请求合并前的自查清单
|
||||||
|
|
||||||
|
⚠️ 请自检并全部**勾选全部选项**。⚠️
|
||||||
|
|
||||||
|
- [ ] 文档已补充或无须补充
|
||||||
|
- [ ] 代码演示已提供或无须提供
|
||||||
|
- [ ] TypeScript 定义已补充或无须补充
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
name: Build and Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9.9.0
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies (workspace recursive)
|
||||||
|
run: pnpm -r install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Test WeChat Mini Program dev mode
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Testing WeChat Mini Program dev mode startup..."
|
||||||
|
timeout 30s pnpm dev:mp-weixin || true
|
||||||
|
echo "✅ WeChat Mini Program dev mode test completed"
|
||||||
|
|
||||||
|
- name: Test H5 dev mode
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Testing H5 dev mode startup..."
|
||||||
|
timeout 30s pnpm dev:h5 || true
|
||||||
|
echo "✅ H5 dev mode test completed"
|
||||||
|
|
||||||
|
- name: Test iOS dev mode
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Testing iOS dev mode startup..."
|
||||||
|
timeout 30s pnpm dev:app-ios || true
|
||||||
|
echo "✅ iOS dev mode test completed"
|
||||||
|
|
||||||
|
- name: Build WeChat Mini Program
|
||||||
|
run: pnpm build:mp-weixin
|
||||||
|
|
||||||
|
- name: Build H5
|
||||||
|
run: pnpm build:h5
|
||||||
|
|
||||||
|
- name: Build iOS
|
||||||
|
run: pnpm build:app-ios
|
||||||
|
|
||||||
|
- name: Upload WeChat Mini Program artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wechat-miniprogram-build
|
||||||
|
path: dist/build/mp-weixin/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload H5 artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: h5-build
|
||||||
|
path: dist/build/h5/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload iOS artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ios-build
|
||||||
|
path: dist/build/app-ios/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Build summary
|
||||||
|
run: |
|
||||||
|
echo "## Build and Test Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Development Mode Tests" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ WeChat Mini Program dev mode test completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ H5 dev mode test completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ iOS dev mode test completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Build Results" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ WeChat Mini Program build completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ H5 build completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ iOS build completed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "📦 All artifacts uploaded successfully" >> $GITHUB_STEP_SUMMARY
|
||||||
|
- name: Build Docs (VitePress)
|
||||||
|
run: pnpm -C docs run docs:build
|
||||||
|
|
||||||
|
- name: Upload Docs artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: docs-build
|
||||||
|
path: docs/.vitepress/dist/
|
||||||
|
retention-days: 7
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9.9.0
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
generate_release_notes: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate GitHub Changelog
|
||||||
|
run: pnpx changelogithub
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Release summary
|
||||||
|
run: |
|
||||||
|
echo "## Release Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Version: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Release Status" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ Release created successfully" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx commitlint --edit $1
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged --allow-empty $1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
22
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"header": "# 更新日志 \n\n",
|
||||||
|
"types": [{
|
||||||
|
"type": "feat",
|
||||||
|
"section": "✨ Features | 新功能",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "fix",
|
||||||
|
"section": "🐛 Bug Fixes | Bug 修复",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "init",
|
||||||
|
"section": "🎉 Init | 初始化",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docs",
|
||||||
|
"section": "✏️ Documentation | 文档",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "style",
|
||||||
|
"section": "💄 Styles | 风格",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "refactor",
|
||||||
|
"section": "♻️ Code Refactoring | 代码重构",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
"section": "⚡ Performance Improvements | 性能优化",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"section": "✅ Tests | 测试",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "revert",
|
||||||
|
"section": "⏪ Revert | 回退",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"section": "📦 Build System | 打包构建",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"section": "🚀 Chore | 构建/工程依赖/工具",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ci",
|
||||||
|
"section": "👷 Continuous Integration | CI 配置",
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
|
||||||
|
## [2.0.0](https://github.com/wot-ui/wot-starter/compare/v1.5.0...v2.0.0) (2026-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 集成 @wot-ui/vitepress-theme ([341f935](https://github.com/wot-ui/wot-starter/commit/341f935fef0da676c7f002506b51662baff09025))
|
||||||
|
* ✨ 支持 wot-ui v2 ([874455f](https://github.com/wot-ui/wot-starter/commit/874455f80abdd97d8ef11b4624d169ac3020a100))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ 更新全局反馈组件文档,增加使用前提和示例 ([8866c45](https://github.com/wot-ui/wot-starter/commit/8866c459a0b33d933a1cbfecaacb4d3673a85351))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes | Bug 修复
|
||||||
|
|
||||||
|
* 🐛 更新 banner URLs 为 wot-starter-v2-banner.json ([aa773d4](https://github.com/wot-ui/wot-starter/commit/aa773d409db38c041ba0e32b68602dc55f3fd2ec))
|
||||||
|
|
||||||
|
## [1.5.0](https://github.com/wot-ui/wot-starter/compare/v1.4.0...v1.5.0) (2026-04-07)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ add wot-ui skill ([2b9b664](https://github.com/wot-ui/wot-starter/commit/2b9b664a087ca25fba26bcde22d1337a8d00309d))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ add wot-ui skill docs ([1c15e03](https://github.com/wot-ui/wot-starter/commit/1c15e03c47a1e6cdc3eb0e53cd65b41ef2dadf01))
|
||||||
|
|
||||||
|
## [1.4.0](https://github.com/wot-ui/wot-starter/compare/v1.3.2...v1.4.0) (2026-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ remove gitee-vote-2025 ([ed32556](https://github.com/wot-ui/wot-starter/commit/ed32556db46d7922cde1a60d1efc32bfb1c87d63))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes | Bug 修复
|
||||||
|
|
||||||
|
* 🐛 仅在微信小程序端开启 optimization 修复运行到支付宝小程序报错的问题 ([420aff4](https://github.com/wot-ui/wot-starter/commit/420aff484878ff88934b913d9aa84916a36c2de8))
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 添加基于本项目实际使用场景的 Agent Skills ([f2a58f7](https://github.com/wot-ui/wot-starter/commit/f2a58f748c8758b4a083fa7181862dc1ad97e303))
|
||||||
|
* ✨ 新增清理演示页面提供精简模板的 skill starter-cleaner ([8b4c4c4](https://github.com/wot-ui/wot-starter/commit/8b4c4c4d0c9abdee1616484a435444c9ae4ce000))
|
||||||
|
|
||||||
|
### [1.3.2](https://github.com/wot-ui/wot-starter/compare/v1.3.1...v1.3.2) (2026-01-07)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 升级 @uni-ku/bundle-optimizer 至 2.0 并处理相关迁移配置 ([e0ab94c](https://github.com/wot-ui/wot-starter/commit/e0ab94cfd7e9971b52c929de5d89e7c5ab11eb3a))
|
||||||
|
|
||||||
|
### [1.3.1](https://github.com/wot-ui/wot-starter/compare/v1.3.0...v1.3.1) (2026-01-05)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 更新 @wot-ui/router 以修复 route 类型问题和 afterEach多次触发的问题 ([fd4f585](https://github.com/wot-ui/wot-starter/commit/fd4f58524bc580a3f3cc249a9447d7c3d0c556d5))
|
||||||
|
|
||||||
|
## [1.3.0](https://github.com/wot-ui/wot-starter/compare/v1.2.2...v1.3.0) (2026-01-04)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 更新 wot-ui 到 v1.14.0 版本 ([4fd2532](https://github.com/wot-ui/wot-starter/commit/4fd25328ba5c3d3e7ea202919071cd8fb98ffd74))
|
||||||
|
|
||||||
|
### [1.2.2](https://github.com/wot-ui/wot-starter/compare/v1.2.1...v1.2.2) (2025-12-29)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 替换 uni-mini-router 为 @wot-ui/router ([171f054](https://github.com/wot-ui/wot-starter/commit/171f054ac7bb0976ee26edcbf4028c80cc4387a2))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ 调整路由文档和演示demo ([73a9cf3](https://github.com/wot-ui/wot-starter/commit/73a9cf36b5b770a556e2ea74be9c5c21602ff661))
|
||||||
|
* ✏️ 更新 readme ([6bf2618](https://github.com/wot-ui/wot-starter/commit/6bf261833a6a8485c749c0123e3e8ba7b306156e))
|
||||||
|
* ✏️ 更新分包调整后demo的地址 ([5e358ec](https://github.com/wot-ui/wot-starter/commit/5e358ec27997c5437043ec3137cfbd4513154fd4))
|
||||||
|
* ✏️ 更新文档首页介绍内容 ([3a1a0a4](https://github.com/wot-ui/wot-starter/commit/3a1a0a409effcc730064d1acc1edc64fe58fd7d1))
|
||||||
|
* ✏️ 添加 PageSpy 远程的教程 ([f29a53c](https://github.com/wot-ui/wot-starter/commit/f29a53cd01cdc80c45e858e409f1564cf7716f73))
|
||||||
|
* ✏️ add about me ([046897a](https://github.com/wot-ui/wot-starter/commit/046897a862e402fd8ebbd993cac525de5e830f25))
|
||||||
|
* ✏️ add gitee vote 2025 ([14af7a0](https://github.com/wot-ui/wot-starter/commit/14af7a04cf1e26772782f0cad0d426e220dba40a))
|
||||||
|
|
||||||
|
### [1.2.1](https://github.com/wot-ui/wot-starter/compare/v1.2.0...v1.2.1) (2025-12-04)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes | Bug 修复
|
||||||
|
|
||||||
|
* **manualTheme:** 修复跟随系统自动切换主题失效的问题 ([380c702](https://github.com/wot-ui/wot-starter/commit/380c7026eeb37a12e9a2866b18bd70880efdfecc))
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* **index:** 首页设置中新增"跟随系统"按钮 ([d031788](https://github.com/wot-ui/wot-starter/commit/d031788d4c9b31a7d030f17856f69f2d177eb1b8))
|
||||||
|
* **logo:** 更新logo ([550caa2](https://github.com/wot-ui/wot-starter/commit/550caa243e423969745d62c3872dfae14976409a))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ 更新 logo ([0e57c45](https://github.com/wot-ui/wot-starter/commit/0e57c45b94284320d5fdc3be6c082faf12ddcbad))
|
||||||
|
* ✏️ 首页添加 uni-ku 插件入口 ([a5b05c9](https://github.com/wot-ui/wot-starter/commit/a5b05c9091f5396c27c556ed23eed87b7f06fef0))
|
||||||
|
* ✏️ 文档增加显示版本号 ([10b7078](https://github.com/wot-ui/wot-starter/commit/10b707810529af53bd2a85fca6624807b5b2d9ec))
|
||||||
|
* ✏️ 移动非主包必需示例页面到分包中 ([#43](https://github.com/wot-ui/wot-starter/issues/43)) ([3d7a076](https://github.com/wot-ui/wot-starter/commit/3d7a07619cf4b84c26a91b5028b2635bcc6d44ff)), closes [#35](https://github.com/wot-ui/wot-starter/issues/35)
|
||||||
|
* ✏️ update logo ([6b9e4f9](https://github.com/wot-ui/wot-starter/commit/6b9e4f9e66d7be10b1e678b46472c9e44a270fc3))
|
||||||
|
|
||||||
|
## [1.2.0](https://github.com/wot-ui/wot-starter/compare/v1.1.0...v1.2.0) (2025-11-26)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 合并模板与文档项目开发便利性优化 ([#41](https://github.com/wot-ui/wot-starter/issues/41)) ([646d215](https://github.com/wot-ui/wot-starter/commit/646d2158c96dcf83518ed22bc27cc8e20f2ed0d2)), closes [#35](https://github.com/wot-ui/wot-starter/issues/35)
|
||||||
|
* ✨ 支持 esm 并更新 unocss 和 [@uni-helper](https://github.com/uni-helper) 插件 ([#39](https://github.com/wot-ui/wot-starter/issues/39)) ([f433b49](https://github.com/wot-ui/wot-starter/commit/f433b49023c572488254b18584a4dbca0ba66336))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ 添加 vite base ([b6f5cf8](https://github.com/wot-ui/wot-starter/commit/b6f5cf83084d0cc04bc94c9714722a7a4c9e6327))
|
||||||
|
* ✏️ 增加更新日志入口 ([7037d8f](https://github.com/wot-ui/wot-starter/commit/7037d8f10a9aa0274d5cc5d7afc8978342f96f1a))
|
||||||
|
|
||||||
|
## [1.1.0](https://github.com/wot-ui/wot-starter/compare/v1.0.0...v1.1.0) (2025-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 添加 uni_modules 插件引入示例 ([8493761](https://github.com/wot-ui/wot-starter/commit/8493761ad6ea4e6478d3b7764b43b813e5178e86))
|
||||||
|
* ✨ 支持 harmony next 自定义 tabbar ([f71e8ba](https://github.com/wot-ui/wot-starter/commit/f71e8ba62504a4c0b79d02e61979b52e1f538e59))
|
||||||
|
|
||||||
|
## 1.0.0 (2025-10-28)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes | Bug 修复
|
||||||
|
|
||||||
|
* 🐛 修复分包路由未注册的问题 ([3da843b](https://github.com/wot-ui/wot-starter/commit/3da843ba33bf62d2a8032dabf3061b2ce87e46a9))
|
||||||
|
|
||||||
|
|
||||||
|
### ✏️ Documentation | 文档
|
||||||
|
|
||||||
|
* ✏️ 更新 README ([a630274](https://github.com/wot-ui/wot-starter/commit/a63027496f9f75e8106437bf7e4285164a7f91b1))
|
||||||
|
* ✏️ 更新README ([abde3bc](https://github.com/wot-ui/wot-starter/commit/abde3bca57cbee293d0751dd6f273366425e2474))
|
||||||
|
* ✏️ 添加分包示例 ([809b65b](https://github.com/wot-ui/wot-starter/commit/809b65b8384029d9ed2c7807709023d65bf6bb4c))
|
||||||
|
* **README:** ✏️ 更新 vitesse-uni-app 项目链接 ([#11](https://github.com/wot-ui/wot-starter/issues/11)) ([6f1585a](https://github.com/wot-ui/wot-starter/commit/6f1585a6da97a9aeed4071125e6b618f30a50bb7))
|
||||||
|
|
||||||
|
|
||||||
|
### ✨ Features | 新功能
|
||||||
|
|
||||||
|
* ✨ 全局反馈组件兼容支付宝小程序 ([7f04d43](https://github.com/wot-ui/wot-starter/commit/7f04d43d44b6eaedabcf32d6c5842b056a0ac8ba))
|
||||||
|
* ✨ 新增主题切换示例 ([#4](https://github.com/wot-ui/wot-starter/issues/4)) ([c39e756](https://github.com/wot-ui/wot-starter/commit/c39e756821b08ba9934c88f5576d6eabda8fd449))
|
||||||
|
* ✨ 引入 @uni-ku/root 解决使用 page-meta 和根组件的问题 ([989e9fd](https://github.com/wot-ui/wot-starter/commit/989e9fd05a9c5a3b103608c941e5e30040a19f32))
|
||||||
|
* ✨ 引入 uni-echarts 支持图表功能,增加分包异步化示例 ([f933a61](https://github.com/wot-ui/wot-starter/commit/f933a6143d5fe02783ade63c669001245970756e))
|
||||||
|
* ✨ 引入 vite-plugin-uni-pages 的 definePage 宏,优化开发体验 ([498df4f](https://github.com/wot-ui/wot-starter/commit/498df4f26b1a84e8e91827178167ff853ae1f1a9))
|
||||||
|
* 升级 uni-echarts 版本 ([#17](https://github.com/wot-ui/wot-starter/issues/17)) ([af94964](https://github.com/wot-ui/wot-starter/commit/af9496440e440afae589277c12999644ccfffe3e))
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023-PRESENT KeJun
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
<div align="center">
|
||||||
|
<p align="center">
|
||||||
|
<img src="src/static//logo.png" width="200" />
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
FastApp
|
||||||
|
<sup style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.4em; vertical-align: super; margin-left: 5px;">v3.1.0</sup>
|
||||||
|
</h1>
|
||||||
|
<p align="center">
|
||||||
|
基于 uni-app + Vue 3 + TypeScript 的现代化移动端跨平台开发模板
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/Vue-3.5.22-green.svg" alt="Vue">
|
||||||
|
<img src="https://img.shields.io/badge/TypeScript-5.9.2-blue.svg" alt="TypeScript">
|
||||||
|
<img src="https://img.shields.io/badge/uni--app-3.0.0-orange.svg" alt="uni-app">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> **与仓库根文档的关系**:项目总览、一键前后端启动、演示账号、Docker 部署等请以 [根目录 README.md](../../README.md) 为准;**本文档**侧重 `frontend/app/` 移动端开发说明。
|
||||||
|
|
||||||
|
## 项目介绍
|
||||||
|
|
||||||
|
FastApp 是 FastapiAdmin 项目的移动端应用,基于 uni-app 框架开发,支持一套代码多端运行。采用 Vue 3 + TypeScript + Vite 等现代化技术栈,集成了完善的代码规范和开发工具链,为开发者提供开箱即用的移动端开发解决方案。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - 就是快!
|
||||||
|
|
||||||
|
- 🐂 [Wot UI V2](https://github.com/wot-ui/wot-ui) - 一个轻量、美观、AI友好的 uni-app 组件库
|
||||||
|
|
||||||
|
- 🚦 [@wot-ui/router](https://github.com/wot-ui/my-uni) - 适用于uni-app&vue3的轻量级路由库
|
||||||
|
|
||||||
|
- 🔄 [Uni Mini CI](https://github.com/Moonofweisheng/uni-mini-ci) - 一个小程序端持续集成的插件
|
||||||
|
|
||||||
|
- 🌐 [Alova](https://alova.js.org/zh-CN/) - 极致高效的请求工具集
|
||||||
|
|
||||||
|
- 🆒 [Uni Ku](https://uni-ku.js.org/) - 非常酷的 uni-app 插件库
|
||||||
|
|
||||||
|
- 📊 [Uni Echarts](https://uni-echarts.xiaohe.ink/) - 适用于 uni-app 的 Apache ECharts 组件
|
||||||
|
|
||||||
|
- 🎨 [UnoCSS](https://github.com/unocss/unocss) - 高性能且极具灵活性的即时原子化 CSS 引擎
|
||||||
|
|
||||||
|
- 😃 [各种图标集为你所用](https://github.com/antfu/unocss/tree/main/packages/preset-icons)
|
||||||
|
|
||||||
|
- 🔥 使用 [新的 `<script setup>` 语法](https://github.com/vuejs/rfcs/pull/227)
|
||||||
|
|
||||||
|
- 📥 [API 自动加载](https://github.com/antfu/unplugin-auto-import) - 直接使用 Composition API 无需引入
|
||||||
|
|
||||||
|
- 🦾 [TypeScript](https://www.typescriptlang.org/) & [ESLint](https://eslint.org/) - 保证代码质量
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```bash
|
||||||
|
src/
|
||||||
|
├── api/ # API 接口定义
|
||||||
|
├── components/ # 公共组件
|
||||||
|
│ ├── DateQuery/ # 日期查询组件
|
||||||
|
│ └── Picker/ # 选择器组件
|
||||||
|
├── composables/ # 组合式函数
|
||||||
|
├── constants/ # 常量定义
|
||||||
|
├── enums/ # 枚举定义
|
||||||
|
├── http/ # HTTP 请求相关
|
||||||
|
│ ├── adapters/ # 请求适配器
|
||||||
|
│ ├── tools/ # 工具函数
|
||||||
|
│ └── types.ts # 类型定义
|
||||||
|
├── layouts/ # 布局组件
|
||||||
|
├── pages/ # 页面目录
|
||||||
|
│ ├── index/ # 首页
|
||||||
|
│ ├── login/ # 登录页
|
||||||
|
│ ├── work/ # 工作台
|
||||||
|
│ └── mine/ # 个人中心
|
||||||
|
├── router/ # 路由配置
|
||||||
|
├── store/ # 状态管理
|
||||||
|
├── types/ # TypeScript 类型
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── App.vue # 应用根组件
|
||||||
|
├── main.ts # 应用入口文件
|
||||||
|
├── manifest.json # 应用配置文件
|
||||||
|
├── pages.json # 页面路由配置
|
||||||
|
└── theme.json # 主题配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 在线演示
|
||||||
|
|
||||||
|
- 📱 移动端:[https://service.fastapiadmin.com/app](https://service.fastapiadmin.com/app)
|
||||||
|
- 📖 在线文档:[https://service.fastapiadmin.com/](https://service.fastapiadmin.com/)
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
- **Node.js** >= 20
|
||||||
|
- **pnpm** >= 9
|
||||||
|
|
||||||
|
### 安装与运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend/app
|
||||||
|
pnpm install
|
||||||
|
pnpm run dev:h5 # 启动 H5 开发服务器
|
||||||
|
pnpm run build:h5 # 构建 H5 应用
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run lint:eslint # ESLint 检查并自动修复
|
||||||
|
pnpm run lint:prettier # Prettier 格式化
|
||||||
|
pnpm run lint:stylelint # Stylelint 检查样式
|
||||||
|
pnpm run type-check # TypeScript 类型检查
|
||||||
|
```
|
||||||
|
|
||||||
|
## 截图
|
||||||
|
|
||||||
|
| 登录 | 首页 | 个人中心 |
|
||||||
|
| ---- | ---- | -------- |
|
||||||
|
|  |  |  |
|
||||||
|
|
||||||
|
## 鸣谢
|
||||||
|
|
||||||
|
- [uni-app](https://uniapp.dcloud.net.cn/) - 跨平台应用开发框架
|
||||||
|
- [Vue 3](https://cn.vuejs.org/) - 渐进式 JavaScript 框架
|
||||||
|
- [Vite](https://cn.vitejs.dev/) - 下一代前端构建工具
|
||||||
|
- [uni-helper](https://github.com/uni-helper) - 感谢 uni-helper 团队为 uni-app 开发体验优化做出的贡献。
|
||||||
|
- [vitesse-uni-app](https://github.com/uni-helper/vitesse-uni-app) - 感谢 vitesse-uni-app 提供的快速起手项目。
|
||||||
|
- [uni-ku](https://uni-ku.js.org/) - 感谢 uni-ku 团队为 uni-app 插件生态做出的贡献。
|
||||||
|
- [wot-ui-intellisense](https://github.com/wot-ui/wot-ui-intellisense) - wot-ui vscode 代码提示插件
|
||||||
|
- [awesome-uni-app](https://github.com/uni-helper/awesome-uni-app) - 多端统一开发框架 uni-app 优秀开发资源汇总
|
||||||
|
- [create-uni](https://github.com/uni-helper/create-uni) - 快速创建 uni-app 项目
|
||||||
|
- [wot-starter-retail](https://github.com/Moonofweisheng/wot-starter-retail) - 基于 wot-ui 的 uni-app 零售行业模板
|
||||||
|
- [uni-mini-ci](https://github.com/Moonofweisheng/uni-mini-ci) - 一个 uni-app 小程序端构建后支持 CI(持续集成)的插件
|
||||||
|
- [@wot-ui/router](https://github.com/wot-ui/my-uni) - 一个基于 vue3 和 Typescript 的轻量级 uni-app 路由库
|
||||||
|
- [uni-ku-root](https://github.com/uni-ku/root) - 一个模拟 App.vue 原有能力的根组件插件
|
||||||
|
- [uni-echarts](https://uni-echarts.xiaohe.ink/) - 适用于 uni-app 的 Apache ECharts 组件
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目采用 [MIT](LICENSE) 许可证。
|
||||||
|
|
||||||
|
[](https://star-history.com/#FastapiAdmin/FastapiAdmin&Date)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**如果这个项目对你有帮助,请给一个 ⭐ Star**
|
||||||
|
|
||||||
|
Made with ❤️ by FastApp Team
|
||||||
|
|
||||||
|
## wot-UI 组件库
|
||||||
|
|
||||||
|
基础组件:
|
||||||
|
wd-button 按钮
|
||||||
|
wd-icon 图标
|
||||||
|
wd-text 文本
|
||||||
|
Layout 布局:wd-row、wd-col提供了 24列 栅格,通过在 wd-col 上设置 span 属性,通过计算当前内容所占百分比进行分栏
|
||||||
|
wd-cell-group、wd-cell 单元格
|
||||||
|
wd-fab 悬浮按钮
|
||||||
|
wd-transition 过渡动画
|
||||||
|
wd-resize 监听元素尺寸变化
|
||||||
|
wd-config-provider 组件Wot全局配置
|
||||||
|
wd-root-portal 根节点传送,是否从页面中脱离出来,用于解决各种 fixed 失效问题,主要用于制作弹窗、弹出层等。
|
||||||
|
导航类组件:
|
||||||
|
wd-navbar 导航栏
|
||||||
|
wd-tabbar、wd-tabbar-item 标签栏
|
||||||
|
wd-tabs 标签页
|
||||||
|
wd-segmented 分段控制器
|
||||||
|
wd-sidebar、wd-sidebar-item 侧边栏
|
||||||
|
wd-pagination 分页组件
|
||||||
|
wd-index-bar、wd-index-anchor 索引栏
|
||||||
|
wd-backtop 回到顶部
|
||||||
|
录入类组件:
|
||||||
|
wd-form、wd-form-item 表单
|
||||||
|
wd-input 输入框
|
||||||
|
wd-textarea 文本域
|
||||||
|
wd-password-input 密码输入框
|
||||||
|
wd-keyboard 键盘输入框
|
||||||
|
wd-input-number 数字输入框
|
||||||
|
wd-search 搜索框
|
||||||
|
wd-checkbox-group、wd-checkbox 复选框
|
||||||
|
wd-radio-group、wd-radio 单选框
|
||||||
|
wd-switch 开关按钮
|
||||||
|
wd-rate 评分
|
||||||
|
wd-slider 滑块
|
||||||
|
wd-picker 选择器
|
||||||
|
wd-picker-view 选择器视图
|
||||||
|
wd-select-picker 单复选选择器
|
||||||
|
wd-cascader 级联选择器
|
||||||
|
wd-calendar 日历选择器
|
||||||
|
wd-calendar-view 日历面板
|
||||||
|
wd-datetime-picker 日期时间选择器
|
||||||
|
wd-upload 图片、视频和文件上传组件
|
||||||
|
wd-signature 签名组件
|
||||||
|
wd-slide-verify 滑动验证组件
|
||||||
|
反馈组件:
|
||||||
|
wd-popup 弹出层,用于展示弹窗、信息提示等内容。
|
||||||
|
wd-overlay 遮罩层,用于在弹出层显示时,遮挡背景,防止用户操作。
|
||||||
|
wd-dialog 弹出对话框,常用于消息提示、操作确认和输入收集,支持函数式调用
|
||||||
|
wd-action-sheet 操作表单,从底部弹出的动作菜单面板
|
||||||
|
wd-drop-menu wd-drop-menu-item 下拉菜单
|
||||||
|
wd-popover 气泡,常用于展示提示信息或菜单操作
|
||||||
|
wd-tooltip 文字提示,用于展示简短提示信息,支持多方向定位、受控显隐、自定义内容和动态更新位置
|
||||||
|
wd-floating-panel 浮动在页面底部的面板,用户可以通过上下拖动秒板来浏览内容,常用于地图导航
|
||||||
|
wd-loading 加载中组件,用于在异步操作进行中显示加载状态,防止用户操作
|
||||||
|
wd-progress 进度条,用于展示任务完成进度
|
||||||
|
wd-circle 圆形进度条,用于展示任务完成进度,支持自定义进度条颜色、进度条宽度、进度条高度等
|
||||||
|
wd-toast 轻提示,轻提示组件,用于消息通知、加载提示和操作结果反馈,支持组件挂载点配合 useToast() 进行函数式调用。
|
||||||
|
wd-notify 消息通知,用于在页面顶部展示通知信息。
|
||||||
|
wd-notice-bar 通知栏,用于在页面顶部展示通知信息,支持自定义内容和样式
|
||||||
|
wd-swipe-action 滑动操作,用于在列表项上添加滑动操作按钮,支持自定义按钮内容和样式
|
||||||
|
wd-sort-button 排序按钮,用于在列表项上添加排序按钮,支持自定义按钮内容和样式
|
||||||
|
wd-empty 空状态组件,用于展示无数据或无结果的情况,一般用于兜底占位展示
|
||||||
|
wd-count-down 倒计时组件,用于实时展示倒计时数值,支持毫秒级渲染与手动控制
|
||||||
|
wd-count-to 数字滚动组件,用于展示数值变化,支持自定义滚动速度、滚动时间等
|
||||||
|
展示类组件:
|
||||||
|
wd-avatar 头像, 用来代表用户或事物,支持图片、文本或图标展示
|
||||||
|
wd-badge 徽标,用于展示未读消息、未完成任务等数量,支持自定义数量、颜色、位置等
|
||||||
|
wd-tag 标签,用于展示分类、状态、标签等信息,支持自定义标签内容、样式、位置等
|
||||||
|
wd-card 卡片,用于展示信息、操作、内容等,支持自定义卡片内容、样式、位置等
|
||||||
|
wd-divider 分割线,用于分隔不同内容区域,支持自定义分割线内容、样式、位置等
|
||||||
|
wd-gap 间距组件,用于在元素之间添加间距,支持自定义间距大小、方向等
|
||||||
|
wd-grid 宫格,用于在页面上创建网格布局,支持自定义网格列数、网格间距等
|
||||||
|
wd-collapse、wd-collapse-item 折叠面板,将一组内容放置在多个折叠面板中,点击面板标题可展开或收起内容
|
||||||
|
wd-steps、wd-step 步骤条,用于引导用户按照流程完成任务,或向用户展示当前所处的步骤状态
|
||||||
|
wd-sticky 粘性组件,用于在页面滚动时保持元素在顶部或底部,不被内容遮挡
|
||||||
|
wd-skeleton 骨架屏组件,用于在数据加载中展示占位符,防止用户操作
|
||||||
|
wd-loadmore 加载更多组件,用于在列表底部添加加载更多按钮,点击后加载更多数据
|
||||||
|
wd-img 增强版图片组件,支持填充模式、懒加载、加载态/失败态插槽,以及点击预览。
|
||||||
|
wd-image-preview 图片预览组件,用于在点击图片时预览图片,支持自定义预览位置、预览大小等。
|
||||||
|
wd-video-preview 视频预览组件,用于在点击视频时预览视频,支持自定义预览位置、预览大小等。
|
||||||
|
wd-img-cropper 图片剪裁组件,用于图片裁剪,支持拖拽、缩放、旋转等操作
|
||||||
|
wd-swiper 轮播图组件,用于展示图片、视频等内容,支持自动播放、手动切换、循环播放等
|
||||||
|
wd-table wd-table-column 用于展示多条结构类似的数据,支持固定列、排序、合并单元格与虚拟滚动等能力
|
||||||
|
wd-watermark 在页面或组件上添加指定的图片或文字,可用于版权保护、品牌宣传等场景
|
||||||
|
wd-curtain 幕帘组件,用于在页面上创建一个透明的遮罩层,一般用于公告类图片弹窗展示
|
||||||
|
组合式api
|
||||||
|
useUpload:用于处理文件上传和选择相关的逻辑
|
||||||
|
useCountDown:用于处理倒计时相关的逻辑
|
||||||
|
useToast:用于处理轻提示相关的逻辑
|
||||||
|
useDialog:用于处理弹窗相关的逻辑,useDialog 用于函数式调用 wd-dialog,支持 alert、confirm、prompt、show 和 close
|
||||||
|
useImagePreview:用于处理图片预览相关的逻辑,useImagePreview 用于函数式调用 wd-image-preview,支持自定义预览位置、预览大小等。
|
||||||
|
useVideoPreview:用于处理视频预览相关的逻辑,useVideoPreview 用于函数式调用 wd-video-preview,支持自定义预览位置、预览大小等。
|
||||||
|
useConfigProvider:用于处理全局配置相关的逻辑,useConfigProvider 用于函数式调用 wd-config-provider,支持自定义全局配置项,用于在 JS 逻辑中注入全局配置(如主题变量),解决在微信小程序等环境中,由于组件渲染机制限制(如原生插槽作用域隔离)或使用 root-portal 导致无法获取父级 ConfigProvider 配置的问题。
|
||||||
|
提示:需要和 ConfigProvider 组件配合使用,使用 ConfigProvider 组件包裹你的组件。用于解决小程序端依赖注入的限制,导致部分场景下无法获取父级 ConfigProvider 配置的问题。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-10-29 20:12:08
|
||||||
|
* @LastEditTime: 2024-10-29 20:12:20
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \salary-calculator\commitlint.config.js
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import uni from '@uni-helper/eslint-config'
|
||||||
|
|
||||||
|
export default uni(
|
||||||
|
{
|
||||||
|
unocss: true,
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
'eslint-comments/no-unlimited-disable': 'off',
|
||||||
|
},
|
||||||
|
ignores: [
|
||||||
|
'src/uni_modules/**/*',
|
||||||
|
'docs/.vitepress/dist',
|
||||||
|
'docs/.vitepress/cache',
|
||||||
|
'**/*.md',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="static/favicon.ico">
|
||||||
|
<script>
|
||||||
|
const coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)')
|
||||||
|
|| CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${
|
||||||
|
coverSupport ? ', viewport-fit=cover' : ''}" />`)
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2025-08-28 20:59:43
|
||||||
|
* @LastEditTime: 2025-11-17 14:28:09
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-starter/manifest.config.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
|
||||||
|
|
||||||
|
export default defineManifestConfig({
|
||||||
|
'name': 'wot-starter',
|
||||||
|
'appid': '__UNI__1208592',
|
||||||
|
'description': '',
|
||||||
|
'versionName': '1.0.0',
|
||||||
|
'versionCode': '100',
|
||||||
|
'transformPx': false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
'app-plus': {
|
||||||
|
usingComponents: true,
|
||||||
|
nvueStyleCompiler: 'uni-app',
|
||||||
|
compilerVersion: 3,
|
||||||
|
splashscreen: {
|
||||||
|
alwaysShowBeforeRender: true,
|
||||||
|
waiting: true,
|
||||||
|
autoclose: true,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
modules: {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
distribute: {
|
||||||
|
/* android打包配置 */
|
||||||
|
android: {
|
||||||
|
permissions: [
|
||||||
|
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
|
||||||
|
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
|
||||||
|
'<uses-permission android:name="android.permission.VIBRATE"/>',
|
||||||
|
'<uses-permission android:name="android.permission.READ_LOGS"/>',
|
||||||
|
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
|
||||||
|
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
|
||||||
|
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
|
||||||
|
'<uses-permission android:name="android.permission.CAMERA"/>',
|
||||||
|
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
|
||||||
|
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
|
||||||
|
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
|
||||||
|
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
|
||||||
|
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
|
||||||
|
'<uses-feature android:name="android.hardware.camera"/>',
|
||||||
|
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
ios: {},
|
||||||
|
/* SDK配置 */
|
||||||
|
sdkConfigs: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
'quickapp': {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
'mp-weixin': {
|
||||||
|
optimization: {
|
||||||
|
subPackages: true,
|
||||||
|
},
|
||||||
|
// TODO: 替换为实际的微信小程序 appid(在微信公众平台 → 开发管理 → 开发设置 中获取)
|
||||||
|
appid: 'wxf306aa91619d2e1c',
|
||||||
|
setting: {
|
||||||
|
urlCheck: false,
|
||||||
|
},
|
||||||
|
usingComponents: true,
|
||||||
|
darkmode: true,
|
||||||
|
themeLocation: 'theme.json',
|
||||||
|
},
|
||||||
|
'app-harmony': {},
|
||||||
|
'mp-harmony': {},
|
||||||
|
'mp-alipay': {
|
||||||
|
usingComponents: true,
|
||||||
|
compileOptions: {
|
||||||
|
globalObjectMode: 'enable',
|
||||||
|
treeShaking: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'mp-baidu': {
|
||||||
|
usingComponents: true,
|
||||||
|
},
|
||||||
|
'mp-toutiao': {
|
||||||
|
usingComponents: true,
|
||||||
|
},
|
||||||
|
'h5': {
|
||||||
|
darkmode: true,
|
||||||
|
themeLocation: 'theme.json',
|
||||||
|
},
|
||||||
|
'uniStatistics': {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
|
'vueVersion': '3',
|
||||||
|
})
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"name": "fastapp",
|
||||||
|
"type": "module",
|
||||||
|
"version": "3.1.0",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@9.9.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0 || >=22.12.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "uni",
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:app-android": "uni -p app-android",
|
||||||
|
"dev:app-ios": "uni -p app-ios",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:h5:development": "uni --mode development",
|
||||||
|
"dev:h5:production": "uni --mode production",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build": "uni build",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:app-android": "uni build -p app-android",
|
||||||
|
"build:app-ios": "uni build -p app-ios",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:h5:development": "uni build --mode development",
|
||||||
|
"build:h5:production": "uni build --mode production",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"type-check": "vue-tsc --noEmit",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"prepare": "cd ../.. && husky install frontend/app/.husky",
|
||||||
|
"commit": "git-cz",
|
||||||
|
"release-major": "npm version major",
|
||||||
|
"release-minor": "npm version minor",
|
||||||
|
"release-patch": "npm version patch",
|
||||||
|
"alova-gen": "alova gen -f"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alova/adapter-uniapp": "^2.0.16",
|
||||||
|
"@alova/shared": "^1.3.2",
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-harmony": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4080520251106001",
|
||||||
|
"@vueuse/core": "^11.0.3",
|
||||||
|
"@vueuse/shared": "11.0.3",
|
||||||
|
"@wot-ui/router": "^1.1.2",
|
||||||
|
"@wot-ui/ui": "^2.2.0",
|
||||||
|
"@wot-ui/unocss-preset": "0.0.1-beta.4",
|
||||||
|
"alova": "^3.5.1",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"pinia": "^2.3.1",
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"uni-echarts": "^2.2.5",
|
||||||
|
"vue": "~3.4.38",
|
||||||
|
"vue-demi": "0.14.10",
|
||||||
|
"vue-i18n": "9.14.5",
|
||||||
|
"zrender": "6.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@alova/wormhole": "^1.5.0",
|
||||||
|
"@commitlint/cli": "^19.5.0",
|
||||||
|
"@commitlint/config-conventional": "^19.5.0",
|
||||||
|
"@dcloudio/types": "^3.4.12",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4080520251106001",
|
||||||
|
"@dcloudio/uni-vue-devtools": "3.0.0-4020420240722002",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4080520251106001",
|
||||||
|
"@iconify-json/carbon": "^1.1.37",
|
||||||
|
"@mini-types/alipay": "^3.0.14",
|
||||||
|
"@types/node": "^20.16.2",
|
||||||
|
"@uni-helper/eslint-config": "^0.5.0",
|
||||||
|
"@uni-helper/plugin-uni": "^0.1.0",
|
||||||
|
"@uni-helper/uni-env": "^0.2.0",
|
||||||
|
"@uni-helper/uni-types": "^1.1.0",
|
||||||
|
"@uni-helper/unocss-preset-uni": "^0.2.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-components": "^0.3.2",
|
||||||
|
"@uni-helper/vite-plugin-uni-layouts": "^0.1.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-manifest": "^0.2.12",
|
||||||
|
"@uni-helper/vite-plugin-uni-pages": "^0.3.23",
|
||||||
|
"@uni-helper/volar-service-uni-pages": "^0.3.24",
|
||||||
|
"@uni-ku/bundle-optimizer": "^2.1.0",
|
||||||
|
"@uni-ku/root": "^1.4.1",
|
||||||
|
"@unocss/eslint-config": "^66.5.6",
|
||||||
|
"@vue/runtime-core": "^3.4.38",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
"commitizen": "^4.3.1",
|
||||||
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"git-cz": "^4.9.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"lint-staged": "^15.2.9",
|
||||||
|
"miniprogram-api-typings": "^3.12.3",
|
||||||
|
"sass": "^1.99.0",
|
||||||
|
"typescript": "~5.5.4",
|
||||||
|
"unocss": "66.0.0",
|
||||||
|
"unplugin-auto-import": "^0.18.2",
|
||||||
|
"vite": "^5.2.8",
|
||||||
|
"vue-tsc": "^2.0.29"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@intlify/message-compiler": "9.14.5",
|
||||||
|
"@intlify/shared": "9.14.5",
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"allowedVersions": {
|
||||||
|
"vue": "3.4.38",
|
||||||
|
"@dcloudio/types": "3.4.31"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2025-06-23 22:23:05
|
||||||
|
* @LastEditTime: 2025-06-27 13:04:54
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-starter/pages.config.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import process from 'node:process'
|
||||||
|
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||||
|
|
||||||
|
// 按构建平台动态裁剪配置(vite-plugin-uni-pages 不处理条件编译注释,用 JS 逻辑)
|
||||||
|
const UNI_PLATFORM = process.env.UNI_PLATFORM || ''
|
||||||
|
const isWeixin = UNI_PLATFORM === 'mp-weixin'
|
||||||
|
const isAlipay = UNI_PLATFORM === 'mp-alipay'
|
||||||
|
|
||||||
|
export default defineUniPages({
|
||||||
|
pages: [],
|
||||||
|
globalStyle: {
|
||||||
|
// 导航栏配置
|
||||||
|
navigationBarBackgroundColor: '@navBgColor',
|
||||||
|
navigationBarTextStyle: '@navTxtStyle',
|
||||||
|
navigationBarTitleText: 'FastapiAdmin',
|
||||||
|
|
||||||
|
// 页面背景配置
|
||||||
|
backgroundColor: '@bgColor',
|
||||||
|
backgroundTextStyle: '@bgTxtStyle',
|
||||||
|
backgroundColorTop: '@bgColorTop',
|
||||||
|
backgroundColorBottom: '@bgColorBottom',
|
||||||
|
|
||||||
|
// 下拉刷新配置
|
||||||
|
enablePullDownRefresh: false,
|
||||||
|
onReachBottomDistance: 50,
|
||||||
|
|
||||||
|
// 页面切换动画(微信小程序 window 不支持 animationType/animationDuration,构建时排除)
|
||||||
|
...(isWeixin ? {} : { animationType: 'pop-in', animationDuration: 300 }),
|
||||||
|
},
|
||||||
|
tabBar: {
|
||||||
|
custom: true,
|
||||||
|
// customize/overlay 为支付宝小程序 tabBar 配置(微信端编译进 app.json 会告警,按平台裁剪)
|
||||||
|
...(isAlipay ? { customize: true, overlay: true } : {}),
|
||||||
|
height: '0',
|
||||||
|
color: '@tabColor',
|
||||||
|
selectedColor: '@tabSelectedColor',
|
||||||
|
backgroundColor: '@tabBgColor',
|
||||||
|
borderStyle: '@tabBorderStyle',
|
||||||
|
list: [{
|
||||||
|
pagePath: 'pages/index/index',
|
||||||
|
}, {
|
||||||
|
pagePath: 'pages/work/index',
|
||||||
|
}, {
|
||||||
|
pagePath: 'pages/mine/index',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
})
|
||||||
Generated
+15738
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
|||||||
|
# 单包项目,无子包;必须声明 packages,否则 pnpm 报 "packages field missing or empty"
|
||||||
|
packages: []
|
||||||
|
|
||||||
|
# pnpm 11.5+ 的 build 白名单(map 形式,取代旧 onlyBuiltDependencies 数组)
|
||||||
|
allowBuilds:
|
||||||
|
'@parcel/watcher': true
|
||||||
|
'@uni-helper/unocss-preset-uni': true
|
||||||
|
core-js: true
|
||||||
|
core-js-pure: true
|
||||||
|
esbuild: true
|
||||||
|
vue-demi: true
|
||||||
|
|
||||||
|
# pnpm 11+ 配置中心(.npmrc 仅保留 auth/registry;package.json 的 pnpm 字段已废弃)
|
||||||
|
# 从 package.json#pnpm 与 .npmrc 迁移而来,勿改回旧位置
|
||||||
|
|
||||||
|
# 依赖版本覆盖(原 package.json#pnpm.overrides)
|
||||||
|
overrides:
|
||||||
|
# vue-i18n@9.14.5 的 core-base/message-compiler 依赖需要 9.14.5 的 @intlify/*;
|
||||||
|
# 但公共提升区混有 uni 工具链内置的 9.1.9,H5 按 symlink 路径解析会命中旧版(缺 join/CompileWarnCodes 等导出),统一版本解决
|
||||||
|
'@intlify/message-compiler': 9.14.5
|
||||||
|
'@intlify/shared': 9.14.5
|
||||||
|
unconfig: 7.3.2
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"setting": {
|
||||||
|
"es6": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true,
|
||||||
|
"uglifyFileName": false,
|
||||||
|
"enhance": true,
|
||||||
|
"packNpmRelationList": [],
|
||||||
|
"babelSetting": {
|
||||||
|
"ignore": [],
|
||||||
|
"disablePlugins": [],
|
||||||
|
"outputPath": ""
|
||||||
|
},
|
||||||
|
"useCompilerPlugins": false,
|
||||||
|
"minifyWXML": true
|
||||||
|
},
|
||||||
|
"compileType": "miniprogram",
|
||||||
|
"simulatorPluginLibVersion": {},
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": [],
|
||||||
|
"include": []
|
||||||
|
},
|
||||||
|
"appid": "wxf306aa91619d2e1c",
|
||||||
|
"editorSetting": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"libVersion": "3.17.0",
|
||||||
|
"projectname": "app",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": true,
|
||||||
|
"coverView": true,
|
||||||
|
"lazyloadPlaceholderEnable": false,
|
||||||
|
"skylineRenderEnable": false,
|
||||||
|
"preloadBackgroundData": false,
|
||||||
|
"autoAudits": false,
|
||||||
|
"showShadowRootInWxmlPanel": true,
|
||||||
|
"compileHotReLoad": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": ["github>uni-helper/renovate-config"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"skills": {
|
||||||
|
"create-wot-ui-theme": {
|
||||||
|
"source": "wot-ui/open-wot",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "0557819ce03183457bd196af213996963b5713641bf76546306d826501bab188"
|
||||||
|
},
|
||||||
|
"wot-ui-cli": {
|
||||||
|
"source": "wot-ui/open-wot",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "b2e370a30ff8f1e31c4b1a0ac91d16928d6fc90e68f02913e659986c94d88b0c"
|
||||||
|
},
|
||||||
|
"wot-ui-unocss-preset-guide": {
|
||||||
|
"source": "wot-ui/open-wot",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "bf1f6f752d0fef15063b56df11a902ff110ae177d867cef76b953ef237c05a70"
|
||||||
|
},
|
||||||
|
"wot-ui-v2": {
|
||||||
|
"source": "wot-ui/open-wot",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "207723b8a0e23cb5657ce9dae012ab92c0d6397212d56b07fafbdbc1ba9c3c2e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { themeVars, theme, currentThemeColor } = useTheme()
|
||||||
|
|
||||||
|
/** 根节点类名:page-wraper + 主题模式 + 主题色(供全局水滴渐变按主题色换肤,如 theme-blue) */
|
||||||
|
const rootClass = computed(() => `page-wraper ${theme.value} theme-${currentThemeColor.value.value}`)
|
||||||
|
|
||||||
|
/** 主题色 → 原生页面背景色(与水滴渐变基底中段色一致,用于下拉/回弹区无缝衔接) */
|
||||||
|
const THEME_PAGE_BG: Record<string, string> = {
|
||||||
|
blue: '#F2FAFF',
|
||||||
|
orange: '#FFFBF6',
|
||||||
|
green: '#F5FDF8',
|
||||||
|
pink: '#FFF9FC',
|
||||||
|
purple: '#FAF7FF',
|
||||||
|
red: '#FFF9F9',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
同步原生页面背景色:亮色取当前主题色基底色,暗色取黑,消除下拉/回弹时的中性灰断层
|
||||||
|
(uni.setBackgroundColor 仅小程序端支持,H5/APP 由 CSS 渐变直接铺满,无需调用)
|
||||||
|
*/
|
||||||
|
function syncPageBackground() {
|
||||||
|
// 暗色用 --wot-filled-content 对应色(coolgrey-9 #272B3B),与导航栏/页面背景一致
|
||||||
|
const bg = theme.value === 'dark' ? '#272B3B' : (THEME_PAGE_BG[currentThemeColor.value.value] ?? '#F2FAFF')
|
||||||
|
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU
|
||||||
|
uni.setBackgroundColor({ backgroundColor: bg, backgroundColorTop: bg, backgroundColorBottom: bg })
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [theme.value, currentThemeColor.value.value], syncPageBackground, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-config-provider :theme-vars="themeVars" :theme="theme" :custom-class="rootClass">
|
||||||
|
<ku-root-view />
|
||||||
|
<wd-notify />
|
||||||
|
<wd-dialog />
|
||||||
|
<wd-toast />
|
||||||
|
<global-loading />
|
||||||
|
<global-toast />
|
||||||
|
<global-message />
|
||||||
|
<global-dialog />
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<privacy-popup />
|
||||||
|
<!-- #endif -->
|
||||||
|
</wd-config-provider>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
onLaunch(() => {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 全局样式按职责拆分:
|
||||||
|
- theme.scss:玻璃水滴渐变主题变量(6 套主题色)
|
||||||
|
- base.scss:页面容器 / 暗色背景等基础全局样式
|
||||||
|
页面级样式(登录表单输入框、滑块等)保留在各页面 scoped 内 */
|
||||||
|
@import './styles/theme.scss';
|
||||||
|
@import './styles/base.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* AI 聊天 API 模块(对应后端 module_ai 插件)
|
||||||
|
*/
|
||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const AI_BASE = '/ai/chat'
|
||||||
|
|
||||||
|
export const ChatAPI = {
|
||||||
|
/** 获取会话列表 */
|
||||||
|
getSessions(params?: Record<string, any>): Promise<PageResult<ChatSession>> {
|
||||||
|
return http.Get(`${AI_BASE}/list`, params)
|
||||||
|
},
|
||||||
|
/** 创建会话 */
|
||||||
|
createSession(title?: string): Promise<ChatSession> {
|
||||||
|
return http.Post(`${AI_BASE}/create`, { title: title || '新对话' })
|
||||||
|
},
|
||||||
|
/** 获取会话详情(含消息列表) */
|
||||||
|
getDetail(sessionId: number): Promise<{ messages: ChatMessage[] }> {
|
||||||
|
return http.Get(`${AI_BASE}/detail/${sessionId}`)
|
||||||
|
},
|
||||||
|
/** 更新会话标题 */
|
||||||
|
updateSession(sessionId: number, data: Record<string, any>): Promise<void> {
|
||||||
|
return http.Put(`${AI_BASE}/update/${sessionId}`, data)
|
||||||
|
},
|
||||||
|
/** 删除会话 */
|
||||||
|
removeSession(ids: number[]): Promise<void> {
|
||||||
|
return http.Delete(`${AI_BASE}/delete`, { ids: JSON.stringify(ids) })
|
||||||
|
},
|
||||||
|
/** 发送消息 (非流式) */
|
||||||
|
sendMessage(sessionId: number, content: string): Promise<ChatMessage> {
|
||||||
|
// silent:聊天页将错误内联展示为 AI 消息,避免与全局 toast 重复提示
|
||||||
|
return http.Post(`${AI_BASE}/ai-chat`, { session_id: sessionId, content }, { meta: { silent: true } })
|
||||||
|
},
|
||||||
|
/** 获取 AI 模型配置列表 */
|
||||||
|
getModels(): Promise<AIModelList> {
|
||||||
|
return http.Get(`${AI_BASE}/model`)
|
||||||
|
},
|
||||||
|
/** 新增 AI 模型配置 */
|
||||||
|
createModel(data: AIModelForm): Promise<AIModelConfig> {
|
||||||
|
return http.Post(`${AI_BASE}/model`, data)
|
||||||
|
},
|
||||||
|
/** 更新 AI 模型配置 */
|
||||||
|
updateModel(configId: number, data: AIModelForm): Promise<AIModelConfig> {
|
||||||
|
return http.Put(`${AI_BASE}/model/${configId}`, data)
|
||||||
|
},
|
||||||
|
/** 删除 AI 模型配置 */
|
||||||
|
deleteModel(configId: number): Promise<void> {
|
||||||
|
return http.Delete(`${AI_BASE}/model/${configId}`)
|
||||||
|
},
|
||||||
|
/** 切换激活的 AI 模型配置 */
|
||||||
|
activateModel(configId: number): Promise<void> {
|
||||||
|
return http.Post(`${AI_BASE}/model/${configId}/activate`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 类型定义 ==================== */
|
||||||
|
|
||||||
|
export interface ChatSession {
|
||||||
|
id: number
|
||||||
|
session_name?: string
|
||||||
|
title?: string
|
||||||
|
created_at?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id?: string
|
||||||
|
role: string
|
||||||
|
content: string
|
||||||
|
created_at?: number
|
||||||
|
time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AIModelForm {
|
||||||
|
name: string
|
||||||
|
base_url: string
|
||||||
|
api_key: string
|
||||||
|
model_id: string
|
||||||
|
temperature?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AIModelConfig extends AIModelForm {
|
||||||
|
config_id: number
|
||||||
|
is_active?: boolean
|
||||||
|
created_time?: string
|
||||||
|
updated_time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 模型配置列表响应:items 为配置数组,active_id 为当前激活的配置 id */
|
||||||
|
export interface AIModelList {
|
||||||
|
items: AIModelConfig[]
|
||||||
|
active_id: number | null
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const MONITOR_BASE = '/monitor'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运营大盘 API
|
||||||
|
* 与 web 端 module_monitor/dashboard.ts 对齐
|
||||||
|
*/
|
||||||
|
export const DashboardAPI = {
|
||||||
|
getStats(): Promise<DashboardStats> {
|
||||||
|
return http.Get(`${MONITOR_BASE}/online/stats`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecentLoginItem {
|
||||||
|
username: string
|
||||||
|
status: number
|
||||||
|
login_time: string
|
||||||
|
login_ip?: string
|
||||||
|
login_location?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStats {
|
||||||
|
online_users: number
|
||||||
|
total_users: number
|
||||||
|
today_login_count: number
|
||||||
|
today_unique_users: number
|
||||||
|
week_user_created: number
|
||||||
|
login_trend: LoginTrendItem[]
|
||||||
|
recent_logins: RecentLoginItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 登录趋势(按天聚合,近7天) */
|
||||||
|
export interface LoginTrendItem {
|
||||||
|
day: string
|
||||||
|
logins: number
|
||||||
|
unique_users: number
|
||||||
|
new_users: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const MONITOR_BASE = '/monitor'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器监控 API
|
||||||
|
* 与 web 端 module_monitor/server.ts 对齐(补全完整字段)
|
||||||
|
*/
|
||||||
|
export const ServerAPI = {
|
||||||
|
getInfo(): Promise<ServerInfo> {
|
||||||
|
return http.Get(`${MONITOR_BASE}/server/info`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpuInfo {
|
||||||
|
/** 逻辑核心数 */
|
||||||
|
cpu_num: number
|
||||||
|
/** user 占用百分比 */
|
||||||
|
used: number
|
||||||
|
/** system 占用百分比 */
|
||||||
|
sys: number
|
||||||
|
/** idle 百分比 */
|
||||||
|
free: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MemoryInfo {
|
||||||
|
total: string
|
||||||
|
used: string
|
||||||
|
free: string
|
||||||
|
usage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiskInfo {
|
||||||
|
dir_name: string
|
||||||
|
sys_type_name: string
|
||||||
|
type_name: string
|
||||||
|
total: string
|
||||||
|
used: string
|
||||||
|
free: string
|
||||||
|
/** 使用率百分比 */
|
||||||
|
usage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SysInfo {
|
||||||
|
computer_ip: string
|
||||||
|
computer_name: string
|
||||||
|
os_arch: string
|
||||||
|
os_name: string
|
||||||
|
user_dir: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PyInfo {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
start_time: string
|
||||||
|
run_time: string
|
||||||
|
home: string
|
||||||
|
memory_total: string
|
||||||
|
memory_used: string
|
||||||
|
memory_free: string
|
||||||
|
/** 进程内存占用率百分比(rss / available) */
|
||||||
|
memory_usage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerInfo {
|
||||||
|
cpu?: CpuInfo
|
||||||
|
mem?: MemoryInfo
|
||||||
|
sys?: SysInfo
|
||||||
|
py?: PyInfo
|
||||||
|
disks?: DiskInfo[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
import { ContentTypeEnum } from '@/http/tools/enum'
|
||||||
|
|
||||||
|
const AUTH_BASE_URL = '/system/auth'
|
||||||
|
|
||||||
|
/** 方案提供方 */
|
||||||
|
export type OAuthProvider = 'wechat' | 'qq' | 'github' | 'gitee'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证 API
|
||||||
|
* 与 web 端 module_system/auth.ts 对齐(完整字段定义)
|
||||||
|
*/
|
||||||
|
const AuthAPI = {
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
* @param body 登录表单数据
|
||||||
|
* @returns 登录结果
|
||||||
|
*/
|
||||||
|
login(body: LoginFormData): Promise<LoginResult> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/login`, body, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': ContentTypeEnum.FORM_URLENCODED,
|
||||||
|
},
|
||||||
|
// authRole: 'login':登录 401(密码错误)按普通错误处理,不触发 token 刷新逻辑
|
||||||
|
meta: { ignoreAuth: true, authRole: 'login' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌
|
||||||
|
* @param body 刷新令牌请求体
|
||||||
|
* @returns 新的访问令牌
|
||||||
|
*/
|
||||||
|
refreshToken(body: RefreshToekenBody): Promise<LoginResult> {
|
||||||
|
// authRole: 'refreshToken':让 http 层 401 处理器识别刷新请求,自身 401 时不触发刷新逻辑(避免死循环)
|
||||||
|
// silent:刷新失败由 http 层统一跳转登录,无需全局 toast
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/token/refresh`, body, {
|
||||||
|
meta: { ignoreAuth: true, silent: true, authRole: 'refreshToken' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
* @returns 验证码信息
|
||||||
|
*/
|
||||||
|
getCaptcha(): Promise<CaptchaInfo> {
|
||||||
|
// 添加随机参数防止缓存
|
||||||
|
const timestamp = new Date().getTime()
|
||||||
|
return http.Get(`${AUTH_BASE_URL}/captcha/get?timestamp=${timestamp}`, { meta: { ignoreAuth: true, authRole: 'visitor' } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登出
|
||||||
|
* 后端 logout 接口 body 为纯字符串(JWT 原文,Annotated[str, Body]),
|
||||||
|
* 需显式 JSON.stringify 使请求体成为合法 JSON 字符串(uni.request 对字符串原样发送)
|
||||||
|
* @param token 访问令牌
|
||||||
|
*/
|
||||||
|
logout(token: string): Promise<void> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/logout`, JSON.stringify(token))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第三方 OAuth 登录跳转 URL
|
||||||
|
* @param provider oauth 提供商: wechat / qq / github / gitee
|
||||||
|
* @returns 跳转 URL
|
||||||
|
*/
|
||||||
|
getOAuthLoginUrl(provider: OAuthProvider): Promise<{ url: string }> {
|
||||||
|
return http.Get(`${AUTH_BASE_URL}/oauth/${provider}/login`, { meta: { ignoreAuth: true, authRole: 'visitor' } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块验证码完成
|
||||||
|
* 后端仅标记 captcha_key 状态为 verified,不校验 x 坐标值(x 为占位字段)
|
||||||
|
* @param data 验证数据
|
||||||
|
* @param data.captcha_key 验证码 key
|
||||||
|
* @param data.x 滑块 x 坐标(占位,后端未使用)
|
||||||
|
* @returns 验证结果 { captcha_key, verified }
|
||||||
|
*/
|
||||||
|
completeSliderCaptcha(data: { captcha_key: string, x: number }): Promise<{ captcha_key: string, verified: boolean }> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/captcha/slider/complete`, data, { meta: { ignoreAuth: true, authRole: 'visitor' } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序登录
|
||||||
|
* 前端通过 uni.login 获取 code,后端调用 code2Session 换取 openid 后返回 JWT
|
||||||
|
* @param data 微信登录数据
|
||||||
|
* @param data.code uni.login 返回的 code
|
||||||
|
* @param data.nickname 用户昵称(可选,来自 getUserProfile)
|
||||||
|
* @param data.avatar 头像 URL(可选)
|
||||||
|
* @returns JWT 登录结果
|
||||||
|
*/
|
||||||
|
wxLogin(data: WxLoginData): Promise<LoginResult> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/wx-login`, data, { meta: { ignoreAuth: true, authRole: 'visitor' } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序手机号快速登录
|
||||||
|
* 用户点击<button open-type="getPhoneNumber">后,回调 e.detail.code 发送给后端
|
||||||
|
* 后端通过 getuserphonenumber API 直接获取手机号(2023+ 新方案,无需 AES 解密)
|
||||||
|
* @param data 手机号登录数据
|
||||||
|
* @param data.code getPhoneNumber 回调返回的动态令牌 code
|
||||||
|
* @returns JWT 登录结果
|
||||||
|
*/
|
||||||
|
wxPhoneLogin(data: WxPhoneLoginData): Promise<LoginResult> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/wx-phone-login`, data, { meta: { ignoreAuth: true, authRole: 'visitor' } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成小程序码
|
||||||
|
* 调用后端接口,后端通过微信 getWXACodeUnlimit API 生成无限制小程序码
|
||||||
|
* @param data 生成参数
|
||||||
|
* @param data.scene 场景参数(最大32字符,如 invite_123)
|
||||||
|
* @param data.page 小程序页面路径(可选,默认主页)
|
||||||
|
* @param data.width 图片宽度(px,默认 430)
|
||||||
|
* @returns 包含 base64 图片 URL 的结果
|
||||||
|
*/
|
||||||
|
generateWxQrCode(data: WxQrCodeParams): Promise<WxQrCodeResult> {
|
||||||
|
return http.Post(`${AUTH_BASE_URL}/wx-qrcode/generate`, data)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthAPI
|
||||||
|
|
||||||
|
/** 登录表单数据 */
|
||||||
|
export interface LoginFormData {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
captcha_key?: string
|
||||||
|
captcha?: string
|
||||||
|
remember?: boolean
|
||||||
|
login_type?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 刷新令牌请求体 */
|
||||||
|
export interface RefreshToekenBody {
|
||||||
|
refresh_token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** JWT 响应 */
|
||||||
|
export interface LoginResult {
|
||||||
|
access_token: string
|
||||||
|
refresh_token: string
|
||||||
|
token_type: string
|
||||||
|
expires_in: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 验证码信息(滑块模式:img_base 为空字符串) */
|
||||||
|
export interface CaptchaInfo {
|
||||||
|
enable: boolean
|
||||||
|
key: string
|
||||||
|
img_base: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 微信小程序登录数据 */
|
||||||
|
export interface WxLoginData {
|
||||||
|
code: string
|
||||||
|
nickname?: string
|
||||||
|
avatar?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 微信手机号登录数据(2023+ 新方案:仅传 code) */
|
||||||
|
export interface WxPhoneLoginData {
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 小程序码生成参数 */
|
||||||
|
export interface WxQrCodeParams {
|
||||||
|
/** 场景值(最大32字符,如 invite_123) */
|
||||||
|
scene: string
|
||||||
|
/** 目标页面路径(不带 /,如 pages/index/index),为空则默认主页 */
|
||||||
|
page?: string
|
||||||
|
/** 宽度(px),默认 430,范围 280-1280 */
|
||||||
|
width?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 小程序码生成结果 */
|
||||||
|
export interface WxQrCodeResult {
|
||||||
|
/** 小程序码图片 URL(data:image/png;base64,...) */
|
||||||
|
url: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import type { BatchSetStatus } from './user'
|
||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const SYSTEM_BASE = '/system'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公告管理 API
|
||||||
|
* 与 web 端 module_system/notice.ts 对齐(完整字段定义)
|
||||||
|
*/
|
||||||
|
export const NoticeAPI = {
|
||||||
|
getPage(params?: Record<string, any>) {
|
||||||
|
return http.Get<PageResult<NoticeItem>>(`${SYSTEM_BASE}/notice/list`, params)
|
||||||
|
},
|
||||||
|
getDetail(id: number): Promise<NoticeItem> {
|
||||||
|
return http.Get(`${SYSTEM_BASE}/notice/detail/${id}`)
|
||||||
|
},
|
||||||
|
create(data: NoticeForm): Promise<NoticeItem> {
|
||||||
|
return http.Post(`${SYSTEM_BASE}/notice/create`, data)
|
||||||
|
},
|
||||||
|
update(id: number, data: NoticeForm): Promise<NoticeItem> {
|
||||||
|
return http.Put(`${SYSTEM_BASE}/notice/update/${id}`, data)
|
||||||
|
},
|
||||||
|
remove(ids: number[]): Promise<void> {
|
||||||
|
return http.Delete(`${SYSTEM_BASE}/notice/delete`, { ids: JSON.stringify(ids) })
|
||||||
|
},
|
||||||
|
batchStatus(data: BatchSetStatus): Promise<void> {
|
||||||
|
return http.Patch(`${SYSTEM_BASE}/notice/status/batch`, data)
|
||||||
|
},
|
||||||
|
getAvailable(): Promise<NoticeItem[]> {
|
||||||
|
return http.Get(`${SYSTEM_BASE}/notice/available`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoticeForm extends BaseFormType {
|
||||||
|
notice_title?: string
|
||||||
|
notice_type?: string
|
||||||
|
notice_content?: string
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoticeItem extends BaseType, NoticeForm {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const PARAM_BASE_URL = '/system/param'
|
||||||
|
|
||||||
|
/** 系统参数项(与后端 ParamsOutSchema 对齐) */
|
||||||
|
export interface ParamsItem {
|
||||||
|
id: number
|
||||||
|
config_name: string
|
||||||
|
config_key: string
|
||||||
|
config_value: string | null
|
||||||
|
config_type: boolean
|
||||||
|
status: number
|
||||||
|
description?: string | null
|
||||||
|
created_time?: string
|
||||||
|
update_time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 系统参数管理 API */
|
||||||
|
const ParamsAPI = {
|
||||||
|
/**
|
||||||
|
* 获取初始化缓存参数(系统配置列表)
|
||||||
|
*
|
||||||
|
* 免认证接口:登录页(无 token)也会调用,需标记 meta.ignoreAuth 跳过鉴权注入
|
||||||
|
*
|
||||||
|
* @returns 系统配置项列表,config_key 唯一标识
|
||||||
|
*/
|
||||||
|
getInitConfig(): Promise<ParamsItem[]> {
|
||||||
|
return http.Get(`${PARAM_BASE_URL}/info`, {
|
||||||
|
meta: { ignoreAuth: true },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParamsAPI
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const SYSTEM_BASE = '/system'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单管理 API
|
||||||
|
* 与 web 端 module_system/ticket.ts 对齐(完整字段定义)
|
||||||
|
*/
|
||||||
|
export const TicketAPI = {
|
||||||
|
getPage(params?: Record<string, any>) {
|
||||||
|
return http.Get<PageResult<TicketItem>>(`${SYSTEM_BASE}/ticket/list`, params)
|
||||||
|
},
|
||||||
|
getDetail(id: number): Promise<TicketItem> {
|
||||||
|
return http.Get(`${SYSTEM_BASE}/ticket/detail/${id}`)
|
||||||
|
},
|
||||||
|
create(data: TicketForm): Promise<TicketItem> {
|
||||||
|
return http.Post(`${SYSTEM_BASE}/ticket/create`, data)
|
||||||
|
},
|
||||||
|
update(id: number, data: Record<string, any>): Promise<TicketItem> {
|
||||||
|
return http.Put(`${SYSTEM_BASE}/ticket/update/${id}`, data)
|
||||||
|
},
|
||||||
|
remove(ids: number[]): Promise<void> {
|
||||||
|
return http.Delete(`${SYSTEM_BASE}/ticket/delete`, { ids: JSON.stringify(ids) })
|
||||||
|
},
|
||||||
|
batch(data: { ids: number[], status: number, assigned_id?: number }): Promise<void> {
|
||||||
|
return http.Put(`${SYSTEM_BASE}/ticket/batch`, data)
|
||||||
|
},
|
||||||
|
exportTickets(params?: Record<string, any>): Promise<unknown> {
|
||||||
|
return http.Post(`${SYSTEM_BASE}/ticket/export`, params)
|
||||||
|
},
|
||||||
|
getComments(ticketId: number, params?: Record<string, any>): Promise<PageResult<TicketComment>> {
|
||||||
|
return http.Get(`${SYSTEM_BASE}/ticket/${ticketId}/comments`, params)
|
||||||
|
},
|
||||||
|
createComment(ticketId: number, data: { content: string }): Promise<TicketComment> {
|
||||||
|
return http.Post(`${SYSTEM_BASE}/ticket/${ticketId}/comments`, data)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 工单聚合统计(待处理/处理中/已完成),由后端一次聚合返回,
|
||||||
|
* 替代前端 3 次分页请求计数。alova 缓存 30 秒,work/mine 页共享。
|
||||||
|
*/
|
||||||
|
getStats() {
|
||||||
|
const method = http.Get<TicketStats>(`${SYSTEM_BASE}/ticket/stats`)
|
||||||
|
method.config.cacheFor = 30_000
|
||||||
|
return method
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TicketForm extends BaseFormType {
|
||||||
|
title: string
|
||||||
|
ticket_content?: string
|
||||||
|
summary?: string
|
||||||
|
ticket_type: string
|
||||||
|
images?: string
|
||||||
|
reply?: string
|
||||||
|
assigned_id?: number
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TicketItem extends BaseType {
|
||||||
|
title?: string
|
||||||
|
ticket_content?: string
|
||||||
|
summary?: string
|
||||||
|
ticket_type?: string
|
||||||
|
status?: string | number
|
||||||
|
assigned_id?: number
|
||||||
|
assigned_by?: CommonType
|
||||||
|
images?: string
|
||||||
|
reply?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TicketComment extends BaseType {
|
||||||
|
ticket_id?: number
|
||||||
|
user_id?: number
|
||||||
|
username?: string
|
||||||
|
content?: string
|
||||||
|
created_by_name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工单聚合统计结果(后端一次性返回) */
|
||||||
|
export interface TicketStats {
|
||||||
|
pending: number
|
||||||
|
processing: number
|
||||||
|
done: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
import { http } from '@/http'
|
||||||
|
|
||||||
|
const USER_BASE_URL = '/system/user'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户管理 API
|
||||||
|
* 与 web 端 module_system/user.ts 对齐(完整字段定义)
|
||||||
|
*/
|
||||||
|
const UserAPI = {
|
||||||
|
/**
|
||||||
|
* 个人中心用户信息
|
||||||
|
*
|
||||||
|
* @returns 登录用户昵称、头像信息,包括角色和权限
|
||||||
|
*/
|
||||||
|
getCurrentUserInfo(): Promise<UserInfo> {
|
||||||
|
return http.Get(`${USER_BASE_URL}/current/info`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户头像上传
|
||||||
|
*
|
||||||
|
* @param body 上传参数
|
||||||
|
* @param body.filePath 本地临时文件路径(uni.chooseImage 选择结果)
|
||||||
|
* @param body.name 上传字段名,后端约定为 file
|
||||||
|
* @returns uni.uploadFile 成功回调(statusCode + data 响应体字符串,需调用方解析)
|
||||||
|
*/
|
||||||
|
uploadCurrentUserAvatar(body: { filePath: string, name?: string }): Promise<{ statusCode: number, data: string }> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/current/avatar/upload`, body, { requestType: 'upload' })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改个人中心用户信息
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @returns 修改后的用户信息
|
||||||
|
*/
|
||||||
|
updateCurrentUserInfo(body: UserProfileForm): Promise<UserInfo> {
|
||||||
|
return http.Put(`${USER_BASE_URL}/current/info/update`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改个人中心用户密码
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @returns 修改后的用户信息
|
||||||
|
*/
|
||||||
|
changeCurrentUserPassword(body: PasswordChangeForm): Promise<void> {
|
||||||
|
return http.Put(`${USER_BASE_URL}/current/password/change`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册用户(公开接口)
|
||||||
|
*
|
||||||
|
* @param body 注册参数
|
||||||
|
* @param body.username 用户名(字母开头,3-32 位)
|
||||||
|
* @param body.password 密码(6-128 位)
|
||||||
|
* @param body.name 昵称(可选)
|
||||||
|
* @returns 注册结果
|
||||||
|
*/
|
||||||
|
registerUser(body: RegisterForm): Promise<void> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/register`, body, { meta: { ignoreAuth: true } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忘记密码(公开接口)
|
||||||
|
*
|
||||||
|
* @param body 重置参数
|
||||||
|
* @param body.username 用户名(字母开头,3-32 位)
|
||||||
|
* @param body.new_password 新密码(6-128 位)
|
||||||
|
* @returns 重置结果
|
||||||
|
*/
|
||||||
|
forgetPassword(body: ForgetPasswordForm): Promise<void> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/password/forget`, body, { meta: { ignoreAuth: true } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置用户密码
|
||||||
|
*
|
||||||
|
* @param id 用户ID
|
||||||
|
* @param body 新密码
|
||||||
|
* @param body.password 新密码
|
||||||
|
*/
|
||||||
|
resetPassword(id: number, body: { password: string }): Promise<void> {
|
||||||
|
return http.Put(`${USER_BASE_URL}/password/reset/${id}`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量修改用户状态
|
||||||
|
*
|
||||||
|
* @param body 批量操作参数
|
||||||
|
* @param body.ids 用户ID列表
|
||||||
|
* @param body.status 目标状态
|
||||||
|
*/
|
||||||
|
batchStatus(body: BatchSetStatus): Promise<void> {
|
||||||
|
return http.Patch(`${USER_BASE_URL}/status/batch`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出用户
|
||||||
|
*
|
||||||
|
* @param params 导出参数
|
||||||
|
*/
|
||||||
|
exportUsers(params?: Record<string, any>): Promise<unknown> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/export`, params)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户导入模板
|
||||||
|
*/
|
||||||
|
getImportTemplate(): Promise<unknown> {
|
||||||
|
return http.Get(`${USER_BASE_URL}/import/template`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户数据
|
||||||
|
*
|
||||||
|
* @param body 导入数据
|
||||||
|
*/
|
||||||
|
importData(body: Record<string, any>): Promise<void> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/import/data`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户分页列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
*/
|
||||||
|
getUserPage(queryParams: UserPageQuery): Promise<PageResult<UserInfo>> {
|
||||||
|
return http.Get(`${USER_BASE_URL}/list`, queryParams)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户表单详情
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @returns 用户表单详情
|
||||||
|
*/
|
||||||
|
getUserDetail(userId: number): Promise<UserForm> {
|
||||||
|
return http.Get(`${USER_BASE_URL}/detail/${userId}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加用户
|
||||||
|
*
|
||||||
|
* @param body 用户表单数据
|
||||||
|
*/
|
||||||
|
addUser(body: UserForm): Promise<void> {
|
||||||
|
return http.Post(`${USER_BASE_URL}/create`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户
|
||||||
|
*
|
||||||
|
* @param body 用户表单数据
|
||||||
|
*/
|
||||||
|
updateUser(body: UserForm): Promise<void> {
|
||||||
|
return http.Put(`${USER_BASE_URL}/update`, body)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户
|
||||||
|
*
|
||||||
|
* @param ids 用户ID数组
|
||||||
|
*/
|
||||||
|
deleteUser(ids: number[]): Promise<void> {
|
||||||
|
return http.Delete(`${USER_BASE_URL}/delete`, ids)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserAPI
|
||||||
|
|
||||||
|
/* 忘记密码表单(与后端 UserForgetPasswordSchema 一致,confirmPassword 为前端校验字段不提交) */
|
||||||
|
export interface ForgetPasswordForm {
|
||||||
|
username: string
|
||||||
|
new_password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注册表单 */
|
||||||
|
export interface RegisterForm {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页查询表单 */
|
||||||
|
export interface UserPageQuery extends PageQuery {
|
||||||
|
username?: string
|
||||||
|
name?: string
|
||||||
|
mobile?: string
|
||||||
|
email?: string
|
||||||
|
dept_id?: number
|
||||||
|
status?: number
|
||||||
|
start_time?: string
|
||||||
|
end_time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索选择器数据类型 */
|
||||||
|
export interface searchSelectDataType {
|
||||||
|
name?: string
|
||||||
|
status?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户表单 */
|
||||||
|
export interface UserForm extends BaseFormType {
|
||||||
|
username?: string
|
||||||
|
name?: string
|
||||||
|
dept_id?: number
|
||||||
|
dept_name?: string
|
||||||
|
role_ids?: number[]
|
||||||
|
role_names?: string[]
|
||||||
|
position_ids?: number[]
|
||||||
|
position_names?: string[]
|
||||||
|
password?: string
|
||||||
|
gender?: number
|
||||||
|
email?: string
|
||||||
|
mobile?: string
|
||||||
|
avatar?: string
|
||||||
|
is_superuser?: boolean
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录用户信息 */
|
||||||
|
export interface UserInfo extends BaseType {
|
||||||
|
username?: string
|
||||||
|
name?: string
|
||||||
|
avatar?: string
|
||||||
|
email?: string
|
||||||
|
mobile?: string
|
||||||
|
gender?: string
|
||||||
|
password?: string
|
||||||
|
menus?: MenuTable[]
|
||||||
|
dept?: deptTreeType
|
||||||
|
dept_id?: deptTreeType['id']
|
||||||
|
dept_name?: deptTreeType['name']
|
||||||
|
roles?: roleSelectorType[]
|
||||||
|
role_names?: roleSelectorType['name'][]
|
||||||
|
role_ids?: roleSelectorType['id'][]
|
||||||
|
positions?: positionSelectorType[]
|
||||||
|
position_names?: positionSelectorType['name'][]
|
||||||
|
position_ids?: positionSelectorType['id'][]
|
||||||
|
is_superuser?: boolean
|
||||||
|
last_login?: string
|
||||||
|
gitee_login?: string
|
||||||
|
github_login?: string
|
||||||
|
wx_login?: string
|
||||||
|
qq_login?: string
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单表 */
|
||||||
|
export interface MenuTable extends BaseType {
|
||||||
|
name?: string
|
||||||
|
type?: number
|
||||||
|
icon?: string
|
||||||
|
order?: number
|
||||||
|
permission?: string
|
||||||
|
route_name?: string
|
||||||
|
route_path?: string
|
||||||
|
component_path?: string
|
||||||
|
redirect?: string
|
||||||
|
parent_id?: number
|
||||||
|
parent_name?: string
|
||||||
|
keep_alive?: boolean
|
||||||
|
hidden?: boolean
|
||||||
|
always_show?: boolean
|
||||||
|
title?: string
|
||||||
|
params?: { key: string, value: string }[]
|
||||||
|
affix?: boolean
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
children?: MenuTable[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 部门树 */
|
||||||
|
export interface deptTreeType {
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
parent_id?: number
|
||||||
|
children?: deptTreeType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 角色选择器 */
|
||||||
|
export interface roleSelectorType {
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 职位选择器 */
|
||||||
|
export interface positionSelectorType {
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
status?: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 个人中心用户信息表单 */
|
||||||
|
export interface UserProfileForm extends BaseFormType {
|
||||||
|
name?: string
|
||||||
|
gender?: string
|
||||||
|
mobile?: string
|
||||||
|
email?: string
|
||||||
|
username?: string
|
||||||
|
dept_name?: string
|
||||||
|
positions?: positionSelectorType[]
|
||||||
|
roles?: roleSelectorType[]
|
||||||
|
avatar?: string
|
||||||
|
created_time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改密码表单 */
|
||||||
|
export interface PasswordChangeForm {
|
||||||
|
old_password: string
|
||||||
|
new_password: string
|
||||||
|
confirm_password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 重置密码表单 */
|
||||||
|
export interface ResetPasswordForm {
|
||||||
|
id: number
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 批量设置状态 */
|
||||||
|
export interface BatchSetStatus {
|
||||||
|
ids: number[]
|
||||||
|
status: number
|
||||||
|
}
|
||||||
Vendored
+706
@@ -0,0 +1,706 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const CommonUtil: typeof import('./utils/common')['CommonUtil']
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const Storage: typeof import('./utils/storage')['Storage']
|
||||||
|
const TEMPLATE_IDS: typeof import('./composables/useSubscribeMessage')['TEMPLATE_IDS']
|
||||||
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
|
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||||
|
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||||
|
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||||
|
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||||
|
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||||
|
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||||
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
|
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||||
|
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||||
|
const createRouter: typeof import('@wot-ui/router')['createRouter']
|
||||||
|
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||||
|
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||||
|
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||||
|
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentPath: typeof import('./utils/index')['getCurrentPath']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const getSystemTheme: typeof import('./utils/systemTheme')['getSystemTheme']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
|
const initializeThemeOnce: typeof import('./utils/systemTheme')['initializeThemeOnce']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||||
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||||
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
const mapStores: typeof import('pinia')['mapStores']
|
||||||
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
|
||||||
|
const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onError: typeof import('@dcloudio/uni-app')['onError']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onHide: typeof import('@dcloudio/uni-app')['onHide']
|
||||||
|
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||||
|
const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
|
||||||
|
const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
|
||||||
|
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
|
||||||
|
const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
|
||||||
|
const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
|
||||||
|
const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
|
||||||
|
const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
|
||||||
|
const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
|
||||||
|
const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
|
||||||
|
const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
|
||||||
|
const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
|
||||||
|
const onReady: typeof import('@dcloudio/uni-app')['onReady']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onResize: typeof import('@dcloudio/uni-app')['onResize']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
|
||||||
|
const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
|
||||||
|
const onShow: typeof import('@dcloudio/uni-app')['onShow']
|
||||||
|
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||||
|
const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
|
||||||
|
const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
|
||||||
|
const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
|
||||||
|
const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
|
const persistPlugin: typeof import('./store/persist')['persistPlugin']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
|
const reactify: typeof import('@vueuse/core')['reactify']
|
||||||
|
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||||
|
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||||
|
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||||
|
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||||
|
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||||
|
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||||
|
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||||
|
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||||
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const subscribeSystemThemeChange: typeof import('./utils/systemTheme')['subscribeSystemThemeChange']
|
||||||
|
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||||
|
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||||
|
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||||
|
const themeColorOptions: typeof import('./composables/useTheme')['themeColorOptions']
|
||||||
|
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||||
|
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||||
|
const toLoginPage: typeof import('./utils/toLoginPage')['toLoginPage']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||||
|
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||||
|
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||||
|
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||||
|
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||||
|
const until: typeof import('@vueuse/core')['until']
|
||||||
|
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||||
|
const useAiChat: typeof import('./composables/useAiChat')['useAiChat']
|
||||||
|
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||||
|
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||||
|
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||||
|
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||||
|
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
||||||
|
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
||||||
|
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
||||||
|
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
|
||||||
|
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
||||||
|
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
||||||
|
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
||||||
|
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
||||||
|
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
||||||
|
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||||
|
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||||
|
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||||
|
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||||
|
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||||
|
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||||
|
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||||
|
const useCached: typeof import('@vueuse/core')['useCached']
|
||||||
|
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||||
|
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||||
|
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||||
|
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||||
|
const useConfigStore: typeof import('./store/configStore')['useConfigStore']
|
||||||
|
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||||
|
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||||
|
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||||
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
|
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||||
|
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||||
|
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||||
|
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||||
|
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||||
|
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||||
|
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||||
|
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||||
|
const useDialog: typeof import('@wot-ui/ui')['useDialog']
|
||||||
|
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||||
|
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||||
|
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||||
|
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||||
|
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||||
|
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||||
|
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||||
|
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||||
|
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||||
|
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||||
|
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||||
|
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||||
|
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||||
|
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||||
|
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||||
|
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||||
|
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||||
|
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||||
|
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||||
|
const useFps: typeof import('@vueuse/core')['useFps']
|
||||||
|
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||||
|
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||||
|
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||||
|
const useGlobalDialog: typeof import('./composables/useGlobalDialog')['useGlobalDialog']
|
||||||
|
const useGlobalLoading: typeof import('./composables/useGlobalLoading')['useGlobalLoading']
|
||||||
|
const useGlobalMessage: typeof import('./composables/useGlobalMessage')['useGlobalMessage']
|
||||||
|
const useGlobalToast: typeof import('./composables/useGlobalToast')['useGlobalToast']
|
||||||
|
const useI18nNavTitle: typeof import('./composables/useI18nNavTitle')['useI18nNavTitle']
|
||||||
|
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||||
|
const useImage: typeof import('@vueuse/core')['useImage']
|
||||||
|
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||||
|
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||||
|
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||||
|
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||||
|
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||||
|
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||||
|
const useListPage: typeof import('./composables/useListPage')['useListPage']
|
||||||
|
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||||
|
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||||
|
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||||
|
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||||
|
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||||
|
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||||
|
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||||
|
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||||
|
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||||
|
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||||
|
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||||
|
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||||
|
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||||
|
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||||
|
const useNotify: typeof import('@wot-ui/ui')['useNotify']
|
||||||
|
const useNow: typeof import('@vueuse/core')['useNow']
|
||||||
|
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||||
|
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||||
|
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||||
|
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||||
|
const usePagination: typeof import('alova/client')['usePagination']
|
||||||
|
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||||
|
const useParentElement: typeof import('@vueuse/core')['useParentElement']
|
||||||
|
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
|
||||||
|
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||||
|
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||||
|
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||||
|
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||||
|
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||||
|
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||||
|
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||||
|
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||||
|
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||||
|
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||||
|
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||||
|
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||||
|
const useRequest: typeof import('alova/client')['useRequest']
|
||||||
|
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||||
|
const useRoute: typeof import('@wot-ui/router')['useRoute']
|
||||||
|
const useRouter: typeof import('@wot-ui/router')['useRouter']
|
||||||
|
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||||
|
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||||
|
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||||
|
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||||
|
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||||
|
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||||
|
const useShare: typeof import('./composables/useShare')['useShare']
|
||||||
|
const useSharePoster: typeof import('./composables/useSharePoster')['useSharePoster']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||||
|
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||||
|
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||||
|
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||||
|
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||||
|
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||||
|
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||||
|
const useSubscribeMessage: typeof import('./composables/useSubscribeMessage')['useSubscribeMessage']
|
||||||
|
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||||
|
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||||
|
const useTabbar: typeof import('./composables/useTabbar')['useTabbar']
|
||||||
|
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||||
|
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||||
|
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||||
|
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||||
|
const useTheme: typeof import('./composables/useTheme')['useTheme']
|
||||||
|
const useThemeStore: typeof import('./store/themeStore')['useThemeStore']
|
||||||
|
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||||
|
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||||
|
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||||
|
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||||
|
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||||
|
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||||
|
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||||
|
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||||
|
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||||
|
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||||
|
const useToString: typeof import('@vueuse/core')['useToString']
|
||||||
|
const useToast: typeof import('@wot-ui/ui')['useToast']
|
||||||
|
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||||
|
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||||
|
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||||
|
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||||
|
const useUserStore: typeof import('./store/userStore')['useUserStore']
|
||||||
|
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||||
|
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||||
|
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||||
|
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||||
|
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||||
|
const useWatermark: typeof import('./composables/useWatermark')['useWatermark']
|
||||||
|
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||||
|
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||||
|
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||||
|
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||||
|
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||||
|
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||||
|
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||||
|
const useWxLogin: typeof import('./composables/useWxLogin')['useWxLogin']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||||
|
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||||
|
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||||
|
const watchDeep: typeof import('@vueuse/core')['watchDeep']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||||
|
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
|
||||||
|
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||||
|
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||||
|
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||||
|
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||||
|
const whenever: typeof import('@vueuse/core')['whenever']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
||||||
|
// for vue template auto import
|
||||||
|
import { UnwrapRef } from 'vue'
|
||||||
|
declare module 'vue' {
|
||||||
|
interface GlobalComponents {}
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
readonly CommonUtil: UnwrapRef<typeof import('./utils/common')['CommonUtil']>
|
||||||
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
|
readonly Storage: UnwrapRef<typeof import('./utils/storage')['Storage']>
|
||||||
|
readonly TEMPLATE_IDS: UnwrapRef<typeof import('./composables/useSubscribeMessage')['TEMPLATE_IDS']>
|
||||||
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
|
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||||
|
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||||
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
|
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||||
|
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
||||||
|
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
||||||
|
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
||||||
|
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||||
|
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||||
|
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||||
|
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
||||||
|
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
||||||
|
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
||||||
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
|
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||||
|
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
||||||
|
readonly createRouter: UnwrapRef<typeof import('@wot-ui/router')['createRouter']>
|
||||||
|
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
||||||
|
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
||||||
|
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
|
||||||
|
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||||
|
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
|
||||||
|
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
|
||||||
|
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||||
|
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||||
|
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||||
|
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
|
||||||
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
|
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
||||||
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
|
readonly getCurrentPath: UnwrapRef<typeof import('./utils/index')['getCurrentPath']>
|
||||||
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
|
readonly getSystemTheme: UnwrapRef<typeof import('./utils/systemTheme')['getSystemTheme']>
|
||||||
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
|
readonly initializeThemeOnce: UnwrapRef<typeof import('./utils/systemTheme')['initializeThemeOnce']>
|
||||||
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||||
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||||
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
|
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
|
||||||
|
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
|
||||||
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
|
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||||
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
|
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||||
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
|
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
|
||||||
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
|
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
|
||||||
|
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
||||||
|
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
|
||||||
|
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
|
||||||
|
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
||||||
|
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||||
|
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
|
||||||
|
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
|
||||||
|
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
|
||||||
|
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
|
||||||
|
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
|
||||||
|
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
|
||||||
|
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
|
||||||
|
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
|
||||||
|
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
|
||||||
|
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
|
||||||
|
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||||
|
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||||
|
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
|
||||||
|
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||||
|
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||||
|
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
|
||||||
|
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
|
||||||
|
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
|
||||||
|
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
||||||
|
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
|
||||||
|
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
|
||||||
|
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
|
||||||
|
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
|
||||||
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
|
readonly persistPlugin: UnwrapRef<typeof import('./store/persist')['persistPlugin']>
|
||||||
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
|
||||||
|
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
|
||||||
|
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
|
||||||
|
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||||
|
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||||
|
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
||||||
|
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
||||||
|
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
||||||
|
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
||||||
|
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
||||||
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
|
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||||
|
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||||
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
|
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||||
|
readonly subscribeSystemThemeChange: UnwrapRef<typeof import('./utils/systemTheme')['subscribeSystemThemeChange']>
|
||||||
|
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
||||||
|
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
||||||
|
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
||||||
|
readonly themeColorOptions: UnwrapRef<typeof import('./composables/useTheme')['themeColorOptions']>
|
||||||
|
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
|
||||||
|
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
|
||||||
|
readonly toLoginPage: UnwrapRef<typeof import('./utils/toLoginPage')['toLoginPage']>
|
||||||
|
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||||
|
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
||||||
|
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||||
|
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||||
|
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||||
|
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||||
|
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
||||||
|
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
|
||||||
|
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
|
||||||
|
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
|
||||||
|
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
|
||||||
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
|
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||||
|
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||||
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
|
readonly useAiChat: UnwrapRef<typeof import('./composables/useAiChat')['useAiChat']>
|
||||||
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
|
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||||
|
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||||
|
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||||
|
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
|
||||||
|
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
|
||||||
|
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
|
||||||
|
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
|
||||||
|
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
|
||||||
|
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
|
||||||
|
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
|
||||||
|
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
|
||||||
|
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
|
||||||
|
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
||||||
|
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
||||||
|
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||||
|
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
||||||
|
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
||||||
|
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
||||||
|
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
|
||||||
|
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
|
||||||
|
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
|
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||||
|
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||||
|
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
|
readonly useConfigStore: UnwrapRef<typeof import('./store/configStore')['useConfigStore']>
|
||||||
|
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||||
|
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||||
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
|
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||||
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
|
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
||||||
|
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
||||||
|
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
||||||
|
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
||||||
|
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
|
||||||
|
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
|
||||||
|
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
|
||||||
|
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
|
||||||
|
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
||||||
|
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
||||||
|
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
||||||
|
readonly useDialog: UnwrapRef<typeof import('@wot-ui/ui')['useDialog']>
|
||||||
|
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
||||||
|
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
||||||
|
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
||||||
|
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
|
||||||
|
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
|
||||||
|
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
|
||||||
|
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
|
||||||
|
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
|
||||||
|
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
|
||||||
|
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
|
||||||
|
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
|
||||||
|
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
|
||||||
|
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
|
||||||
|
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
|
||||||
|
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
|
||||||
|
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
|
||||||
|
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
|
||||||
|
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
|
||||||
|
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
|
||||||
|
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
|
||||||
|
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
||||||
|
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
||||||
|
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||||
|
readonly useGlobalDialog: UnwrapRef<typeof import('./composables/useGlobalDialog')['useGlobalDialog']>
|
||||||
|
readonly useGlobalLoading: UnwrapRef<typeof import('./composables/useGlobalLoading')['useGlobalLoading']>
|
||||||
|
readonly useGlobalMessage: UnwrapRef<typeof import('./composables/useGlobalMessage')['useGlobalMessage']>
|
||||||
|
readonly useGlobalToast: UnwrapRef<typeof import('./composables/useGlobalToast')['useGlobalToast']>
|
||||||
|
readonly useI18nNavTitle: UnwrapRef<typeof import('./composables/useI18nNavTitle')['useI18nNavTitle']>
|
||||||
|
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||||
|
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||||
|
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
||||||
|
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
||||||
|
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
||||||
|
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
||||||
|
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||||
|
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||||
|
readonly useListPage: UnwrapRef<typeof import('./composables/useListPage')['useListPage']>
|
||||||
|
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||||
|
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||||
|
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
||||||
|
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
||||||
|
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
|
||||||
|
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
||||||
|
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
||||||
|
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
||||||
|
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
||||||
|
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||||
|
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
||||||
|
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
||||||
|
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
||||||
|
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
||||||
|
readonly useNotify: UnwrapRef<typeof import('@wot-ui/ui')['useNotify']>
|
||||||
|
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
||||||
|
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
|
||||||
|
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
|
||||||
|
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
|
||||||
|
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
|
||||||
|
readonly usePagination: UnwrapRef<typeof import('alova/client')['usePagination']>
|
||||||
|
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
|
||||||
|
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
|
||||||
|
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
|
||||||
|
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
|
||||||
|
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
|
||||||
|
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
|
||||||
|
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
|
||||||
|
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
|
||||||
|
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
|
||||||
|
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
||||||
|
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||||
|
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||||
|
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||||
|
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||||
|
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||||
|
readonly useRequest: UnwrapRef<typeof import('alova/client')['useRequest']>
|
||||||
|
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||||
|
readonly useRoute: UnwrapRef<typeof import('@wot-ui/router')['useRoute']>
|
||||||
|
readonly useRouter: UnwrapRef<typeof import('@wot-ui/router')['useRouter']>
|
||||||
|
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
||||||
|
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
||||||
|
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||||
|
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
||||||
|
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
||||||
|
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||||
|
readonly useShare: UnwrapRef<typeof import('./composables/useShare')['useShare']>
|
||||||
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
|
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
||||||
|
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
||||||
|
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
||||||
|
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
||||||
|
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
|
||||||
|
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
|
||||||
|
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
|
||||||
|
readonly useSubscribeMessage: UnwrapRef<typeof import('./composables/useSubscribeMessage')['useSubscribeMessage']>
|
||||||
|
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
||||||
|
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
||||||
|
readonly useTabbar: UnwrapRef<typeof import('./composables/useTabbar')['useTabbar']>
|
||||||
|
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
||||||
|
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||||
|
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
||||||
|
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
||||||
|
readonly useTheme: UnwrapRef<typeof import('./composables/useTheme')['useTheme']>
|
||||||
|
readonly useThemeStore: UnwrapRef<typeof import('./store/themeStore')['useThemeStore']>
|
||||||
|
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
||||||
|
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||||
|
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||||
|
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||||
|
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||||
|
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||||
|
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||||
|
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
|
||||||
|
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
||||||
|
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
||||||
|
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
||||||
|
readonly useToast: UnwrapRef<typeof import('@wot-ui/ui')['useToast']>
|
||||||
|
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
||||||
|
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||||
|
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||||
|
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
||||||
|
readonly useUserStore: UnwrapRef<typeof import('./store/userStore')['useUserStore']>
|
||||||
|
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
||||||
|
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||||
|
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||||
|
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||||
|
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||||
|
readonly useWatermark: UnwrapRef<typeof import('./composables/useWatermark')['useWatermark']>
|
||||||
|
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||||
|
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||||
|
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
||||||
|
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
||||||
|
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
||||||
|
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
||||||
|
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
||||||
|
readonly useWxLogin: UnwrapRef<typeof import('./composables/useWxLogin')['useWxLogin']>
|
||||||
|
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||||
|
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
|
||||||
|
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
|
||||||
|
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
|
||||||
|
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
|
||||||
|
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||||
|
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
|
||||||
|
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
|
||||||
|
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
|
||||||
|
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
|
||||||
|
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||||
|
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||||
|
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
|
||||||
|
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
|
||||||
|
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
||||||
|
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+79
@@ -0,0 +1,79 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
|
// Generated by vite-plugin-uni-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
GlobalDialog: typeof import('./components/GlobalDialog.vue')['default']
|
||||||
|
GlobalLoading: typeof import('./components/GlobalLoading.vue')['default']
|
||||||
|
GlobalMessage: typeof import('./components/GlobalMessage.vue')['default']
|
||||||
|
GlobalToast: typeof import('./components/GlobalToast.vue')['default']
|
||||||
|
PrivacyPopup: typeof import('./components/PrivacyPopup.vue')['default']
|
||||||
|
SkeletonPage: typeof import('./components/SkeletonPage.vue')['default']
|
||||||
|
StatusBadge: typeof import('./components/StatusBadge.vue')['default']
|
||||||
|
UniEcharts: typeof import('uni-echarts')['UniEcharts']
|
||||||
|
WdActionSheet: typeof import('@wot-ui/ui/components/wd-action-sheet/wd-action-sheet.vue')['WdActionSheet']
|
||||||
|
WdAvatar: typeof import('@wot-ui/ui/components/wd-avatar/wd-avatar.vue')['WdAvatar']
|
||||||
|
WdBacktop: typeof import('@wot-ui/ui/components/wd-backtop/wd-backtop.vue')['WdBacktop']
|
||||||
|
WdBadge: typeof import('@wot-ui/ui/components/wd-badge/wd-badge.vue')['WdBadge']
|
||||||
|
WdButton: typeof import('@wot-ui/ui/components/wd-button/wd-button.vue')['WdButton']
|
||||||
|
WdCell: typeof import('@wot-ui/ui/components/wd-cell/wd-cell.vue')['WdCell']
|
||||||
|
WdCellGroup: typeof import('@wot-ui/ui/components/wd-cell-group/wd-cell-group.vue')['WdCellGroup']
|
||||||
|
WdCheckbox: typeof import('@wot-ui/ui/components/wd-checkbox/wd-checkbox.vue')['WdCheckbox']
|
||||||
|
WdCol: typeof import('@wot-ui/ui/components/wd-col/wd-col.vue')['WdCol']
|
||||||
|
WdCollapse: typeof import('@wot-ui/ui/components/wd-collapse/wd-collapse.vue')['WdCollapse']
|
||||||
|
WdCollapseItem: typeof import('@wot-ui/ui/components/wd-collapse-item/wd-collapse-item.vue')['WdCollapseItem']
|
||||||
|
WdConfigProvider: typeof import('@wot-ui/ui/components/wd-config-provider/wd-config-provider.vue')['WdConfigProvider']
|
||||||
|
WdCountTo: typeof import('@wot-ui/ui/components/wd-count-to/wd-count-to.vue')['WdCountTo']
|
||||||
|
WdDialog: typeof import('@wot-ui/ui/components/wd-dialog/wd-dialog.vue')['WdDialog']
|
||||||
|
WdDivider: typeof import('@wot-ui/ui/components/wd-divider/wd-divider.vue')['WdDivider']
|
||||||
|
WdDropMenu: typeof import('@wot-ui/ui/components/wd-drop-menu/wd-drop-menu.vue')['WdDropMenu']
|
||||||
|
WdDropMenuItem: typeof import('@wot-ui/ui/components/wd-drop-menu-item/wd-drop-menu-item.vue')['WdDropMenuItem']
|
||||||
|
WdEmpty: typeof import('@wot-ui/ui/components/wd-empty/wd-empty.vue')['WdEmpty']
|
||||||
|
WdFab: typeof import('@wot-ui/ui/components/wd-fab/wd-fab.vue')['WdFab']
|
||||||
|
WdForm: typeof import('@wot-ui/ui/components/wd-form/wd-form.vue')['WdForm']
|
||||||
|
WdFormItem: typeof import('@wot-ui/ui/components/wd-form-item/wd-form-item.vue')['WdFormItem']
|
||||||
|
WdGap: typeof import('@wot-ui/ui/components/wd-gap/wd-gap.vue')['WdGap']
|
||||||
|
WdGrid: typeof import('@wot-ui/ui/components/wd-grid/wd-grid.vue')['WdGrid']
|
||||||
|
WdGridItem: typeof import('@wot-ui/ui/components/wd-grid-item/wd-grid-item.vue')['WdGridItem']
|
||||||
|
WdIcon: typeof import('@wot-ui/ui/components/wd-icon/wd-icon.vue')['WdIcon']
|
||||||
|
WdImagePreview: typeof import('@wot-ui/ui/components/wd-image-preview/wd-image-preview.vue')['WdImagePreview']
|
||||||
|
WdImg: typeof import('@wot-ui/ui/components/wd-img/wd-img.vue')['WdImg']
|
||||||
|
WdInput: typeof import('@wot-ui/ui/components/wd-input/wd-input.vue')['WdInput']
|
||||||
|
WdLoading: typeof import('@wot-ui/ui/components/wd-loading/wd-loading.vue')['WdLoading']
|
||||||
|
WdNavbar: typeof import('@wot-ui/ui/components/wd-navbar/wd-navbar.vue')['WdNavbar']
|
||||||
|
WdNoticeBar: typeof import('@wot-ui/ui/components/wd-notice-bar/wd-notice-bar.vue')['WdNoticeBar']
|
||||||
|
WdNotify: typeof import('@wot-ui/ui/components/wd-notify/wd-notify.vue')['WdNotify']
|
||||||
|
WdPicker: typeof import('@wot-ui/ui/components/wd-picker/wd-picker.vue')['WdPicker']
|
||||||
|
WdPopup: typeof import('@wot-ui/ui/components/wd-popup/wd-popup.vue')['WdPopup']
|
||||||
|
WdRadio: typeof import('@wot-ui/ui/components/wd-radio/wd-radio.vue')['WdRadio']
|
||||||
|
WdRadioGroup: typeof import('@wot-ui/ui/components/wd-radio-group/wd-radio-group.vue')['WdRadioGroup']
|
||||||
|
WdRow: typeof import('@wot-ui/ui/components/wd-row/wd-row.vue')['WdRow']
|
||||||
|
WdSearch: typeof import('@wot-ui/ui/components/wd-search/wd-search.vue')['WdSearch']
|
||||||
|
WdSkeleton: typeof import('@wot-ui/ui/components/wd-skeleton/wd-skeleton.vue')['WdSkeleton']
|
||||||
|
WdSlider: typeof import('@wot-ui/ui/components/wd-slider/wd-slider.vue')['WdSlider']
|
||||||
|
WdSlideVerify: typeof import('@wot-ui/ui/components/wd-slide-verify/wd-slide-verify.vue')['WdSlideVerify']
|
||||||
|
WdStep: typeof import('@wot-ui/ui/components/wd-step/wd-step.vue')['WdStep']
|
||||||
|
WdSteps: typeof import('@wot-ui/ui/components/wd-steps/wd-steps.vue')['WdSteps']
|
||||||
|
WdSwipeAction: typeof import('@wot-ui/ui/components/wd-swipe-action/wd-swipe-action.vue')['WdSwipeAction']
|
||||||
|
WdSwiper: typeof import('@wot-ui/ui/components/wd-swiper/wd-swiper.vue')['WdSwiper']
|
||||||
|
WdSwitch: typeof import('@wot-ui/ui/components/wd-switch/wd-switch.vue')['WdSwitch']
|
||||||
|
WdTab: typeof import('@wot-ui/ui/components/wd-tab/wd-tab.vue')['WdTab']
|
||||||
|
WdTabbar: typeof import('@wot-ui/ui/components/wd-tabbar/wd-tabbar.vue')['WdTabbar']
|
||||||
|
WdTabbarItem: typeof import('@wot-ui/ui/components/wd-tabbar-item/wd-tabbar-item.vue')['WdTabbarItem']
|
||||||
|
WdTabs: typeof import('@wot-ui/ui/components/wd-tabs/wd-tabs.vue')['WdTabs']
|
||||||
|
WdTag: typeof import('@wot-ui/ui/components/wd-tag/wd-tag.vue')['WdTag']
|
||||||
|
WdText: typeof import('@wot-ui/ui/components/wd-text/wd-text.vue')['WdText']
|
||||||
|
WdTextarea: typeof import('@wot-ui/ui/components/wd-textarea/wd-textarea.vue')['WdTextarea']
|
||||||
|
WdToast: typeof import('@wot-ui/ui/components/wd-toast/wd-toast.vue')['WdToast']
|
||||||
|
WdUpload: typeof import('@wot-ui/ui/components/wd-upload/wd-upload.vue')['WdUpload']
|
||||||
|
WdWatermark: typeof import('@wot-ui/ui/components/wd-watermark/wd-watermark.vue')['WdWatermark']
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { deepClone, isFunction } from '@wot-ui/ui/common/util'
|
||||||
|
|
||||||
|
const { dialogOptions, currentPage } = storeToRefs(useGlobalDialog())
|
||||||
|
|
||||||
|
const dialog = useDialog('globalDialog')
|
||||||
|
const currentPath = getCurrentPath()
|
||||||
|
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
const hackAlipayVisible = ref(false)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hackAlipayVisible.value = true
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
watch(() => dialogOptions.value, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
if (currentPage.value === currentPath) {
|
||||||
|
const option = deepClone(newVal)
|
||||||
|
dialog.show(option).then((res) => {
|
||||||
|
if (isFunction(option.success)) {
|
||||||
|
option.success(res)
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
if (isFunction(option.fail)) {
|
||||||
|
option.fail(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<wd-dialog v-if="hackAlipayVisible" selector="globalDialog" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<wd-dialog selector="globalDialog" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const { loadingOptions, currentPage } = storeToRefs(useGlobalLoading())
|
||||||
|
|
||||||
|
const { close: closeGlobalLoading } = useGlobalLoading()
|
||||||
|
|
||||||
|
const loading = useToast('globalLoading')
|
||||||
|
const currentPath = getCurrentPath()
|
||||||
|
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
const hackAlipayVisible = ref(false)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hackAlipayVisible.value = true
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
watch(() => loadingOptions.value, (newVal) => {
|
||||||
|
if (newVal && newVal.show) {
|
||||||
|
if (currentPage.value === currentPath) {
|
||||||
|
loading.loading(loadingOptions.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loading.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<wd-toast v-if="hackAlipayVisible" selector="globalLoading" :closed="closeGlobalLoading" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<wd-toast selector="globalLoading" :closed="closeGlobalLoading" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { GlobalMessageOptions } from '@/composables/useGlobalMessage'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useGlobalMessage } from '@/composables/useGlobalMessage'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { messageOptions, currentPage } = storeToRefs(useGlobalMessage())
|
||||||
|
const currentPath = getCurrentPath()
|
||||||
|
|
||||||
|
watch(() => messageOptions.value, (newVal) => {
|
||||||
|
if (newVal && currentPage.value === currentPath) {
|
||||||
|
const option: GlobalMessageOptions = { ...newVal }
|
||||||
|
uni.showModal({
|
||||||
|
title: option.title || '',
|
||||||
|
content: option.content || '',
|
||||||
|
showCancel: option.showCancel ?? (option.type === 'confirm'),
|
||||||
|
confirmText: option.confirmText || t('common.confirm'),
|
||||||
|
cancelText: option.cancelText || t('common.cancel'),
|
||||||
|
success: (res) => {
|
||||||
|
option.success?.({ confirm: res.confirm, cancel: res.cancel })
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
option.fail?.(err)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const { toastOptions, currentPage } = storeToRefs(useGlobalToast())
|
||||||
|
|
||||||
|
const { close: closeGlobalToast } = useGlobalToast()
|
||||||
|
|
||||||
|
const toast = useToast('globalToast')
|
||||||
|
const currentPath = getCurrentPath()
|
||||||
|
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
const hackAlipayVisible = ref(false)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hackAlipayVisible.value = true
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
watch(() => toastOptions.value, (newVal) => {
|
||||||
|
if (newVal && newVal.show) {
|
||||||
|
if (currentPage.value === currentPath) {
|
||||||
|
toast.show(toastOptions.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<wd-toast v-if="hackAlipayVisible" selector="globalToast" :closed="closeGlobalToast" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<wd-toast selector="globalToast" :closed="closeGlobalToast" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { onBeforeMount, ref } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string // 标题
|
||||||
|
desc?: string // 描述
|
||||||
|
subDesc?: string // 字描述
|
||||||
|
protocol?: string // 协议名称
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
title: '用户隐私保护提示',
|
||||||
|
desc: '感谢您使用本应用,您使用本应用的服务之前请仔细阅读并同意',
|
||||||
|
subDesc: '。当您点击同意并开始时用产品服务时,即表示你已理解并同意该条款内容,该条款将对您产生法律约束力。如您拒绝,将无法使用相应服务。',
|
||||||
|
protocol: '《用户隐私保护指引》',
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['agree', 'disagree'])
|
||||||
|
const showPopup = ref<boolean>(false) // 是否展示popup
|
||||||
|
|
||||||
|
const privacyResolves = ref(new Set()) // onNeedPrivacyAuthorization的reslove
|
||||||
|
|
||||||
|
function privacyHandler(resolve: any) {
|
||||||
|
showPopup.value = true
|
||||||
|
privacyResolves.value.add(resolve)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// 注册监听
|
||||||
|
if (wx.onNeedPrivacyAuthorization) {
|
||||||
|
wx.onNeedPrivacyAuthorization((resolve: any) => {
|
||||||
|
if (typeof privacyHandler === 'function') {
|
||||||
|
privacyHandler(resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同意隐私协议
|
||||||
|
*/
|
||||||
|
function handleAgree() {
|
||||||
|
showPopup.value = false
|
||||||
|
privacyResolves.value.forEach((resolve: any) => {
|
||||||
|
resolve({
|
||||||
|
event: 'agree',
|
||||||
|
buttonId: 'agree-btn',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
privacyResolves.value.clear()
|
||||||
|
emit('agree')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拒绝隐私协议
|
||||||
|
*/
|
||||||
|
function handleDisagree() {
|
||||||
|
showPopup.value = false
|
||||||
|
privacyResolves.value.forEach((resolve: any) => {
|
||||||
|
resolve({
|
||||||
|
event: 'disagree',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
privacyResolves.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开隐私协议
|
||||||
|
*/
|
||||||
|
function openPrivacyContract() {
|
||||||
|
wx.openPrivacyContract({})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出框关闭时清空
|
||||||
|
*/
|
||||||
|
function handleClose() {
|
||||||
|
privacyResolves.value.clear()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-popup v-model="showPopup" :close-on-click-modal="false" custom-class="wd-privacy-popup" @close="handleClose">
|
||||||
|
<view class="wd-privacy-popup__header">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="wd-picker__title">
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="wd-privacy-popup__container">
|
||||||
|
<wd-text :text="desc" />
|
||||||
|
<wd-text class="wd-privacy-popup__container-protocol" :text="protocol" @click="openPrivacyContract" />
|
||||||
|
<wd-text :text="subDesc" />
|
||||||
|
</view>
|
||||||
|
<view class="wd-privacy-popup__footer">
|
||||||
|
<button id="disagree-btn" class="is-block is-round is-medium is-plain wd-privacy-popup__footer-disagree wd-button" @click="handleDisagree">
|
||||||
|
拒绝
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="agree-btn"
|
||||||
|
class="wd-button is-block is-round is-medium is-primary wd-privacy-popup__footer-agree"
|
||||||
|
open-type="agreePrivacyAuthorization"
|
||||||
|
@agreeprivacyauthorization="handleAgree"
|
||||||
|
>
|
||||||
|
同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
// 非 scoped:wd-popup 通过 custom-class 传入根类名,弹窗挂载层级不受组件作用域约束,
|
||||||
|
// :deep() 在小程序端对 virtualHost 组件不可靠,改用全局类名直接命中(H5/MP 均生效)
|
||||||
|
.wd-privacy-popup {
|
||||||
|
width: 600rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wd-privacy-popup {
|
||||||
|
&__header {
|
||||||
|
width: 100%;
|
||||||
|
height: 128rpx;
|
||||||
|
line-height: 128rpx;
|
||||||
|
@apply wot-text-text-main;
|
||||||
|
font-size: 30rpx;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.8;
|
||||||
|
@apply wot-text-text-secondary;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 550;
|
||||||
|
&-protocol {
|
||||||
|
@apply wot-text-primary-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 36rpx;
|
||||||
|
|
||||||
|
/* 组件 wxss 不允许标签选择器(微信小程序限制),改用类选择器 */
|
||||||
|
&-disagree,
|
||||||
|
&-agree {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 页面骨架屏组件
|
||||||
|
* 在数据加载时展示占位内容,提升感知性能
|
||||||
|
* 基于 wd-skeleton 封装
|
||||||
|
*/
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
/** 显示行数(列表行) */
|
||||||
|
rows?: number
|
||||||
|
/** 是否显示搜索栏骨架 */
|
||||||
|
search?: boolean
|
||||||
|
/** 是否显示操作栏骨架 */
|
||||||
|
action?: boolean
|
||||||
|
}>(), {
|
||||||
|
rows: 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 搜索栏占位行 */
|
||||||
|
const searchRowCol = [{ type: 'rect' as const, height: '64rpx', borderRadius: '12rpx' }]
|
||||||
|
|
||||||
|
/** 列表行占位:圆形头像 + 文本块 + 右侧徽章 */
|
||||||
|
const listRowCol = [
|
||||||
|
[
|
||||||
|
{ type: 'circle' as const, size: '64rpx' },
|
||||||
|
{ type: 'rect' as const, width: '200rpx', height: '28rpx', marginLeft: '24rpx' },
|
||||||
|
{ type: 'rect' as const, width: '80rpx', height: '40rpx', borderRadius: '999rpx' },
|
||||||
|
],
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="p-sm">
|
||||||
|
<!-- 搜索栏骨架 -->
|
||||||
|
<view v-if="search" class="admin-card mb-md p-md">
|
||||||
|
<wd-skeleton :row-col="searchRowCol" :loading="true" animation="gradient" />
|
||||||
|
</view>
|
||||||
|
<!-- 多行列表骨架 -->
|
||||||
|
<view class="admin-card p-sm">
|
||||||
|
<view v-for="i in props.rows" :key="i" class="gap-md p-md">
|
||||||
|
<wd-skeleton :row-col="listRowCol" :loading="true" animation="gradient" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 状态徽标组件
|
||||||
|
* 用于统一展示启用/禁用/草稿/发布等状态
|
||||||
|
*/
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
status?: string | boolean | number
|
||||||
|
/** 自定义映射: { '0': 'enabled', '1': 'disabled', true: 'enabled', false: 'disabled' } */
|
||||||
|
map?: Record<string, string>
|
||||||
|
/** 自定义标签文本 */
|
||||||
|
label?: string
|
||||||
|
/** 仅显示圆点 */
|
||||||
|
dot?: boolean
|
||||||
|
}>(), {
|
||||||
|
status: '',
|
||||||
|
label: '',
|
||||||
|
dot: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const statusMap: Record<string, { labelKey: string, cls: string }> = {
|
||||||
|
enabled: { labelKey: 'common.status.enabled', cls: 'status-badge--enabled' },
|
||||||
|
disabled: { labelKey: 'common.status.disabled', cls: 'status-badge--disabled' },
|
||||||
|
draft: { labelKey: 'common.status.draft', cls: 'status-badge--draft' },
|
||||||
|
published: { labelKey: 'common.status.published', cls: 'status-badge--primary' },
|
||||||
|
archived: { labelKey: 'common.status.archived', cls: 'status-badge--disabled' },
|
||||||
|
active: { labelKey: 'common.status.active', cls: 'status-badge--primary' },
|
||||||
|
success: { labelKey: 'common.status.success', cls: 'status-badge--enabled' },
|
||||||
|
failed: { labelKey: 'common.status.failed', cls: 'status-badge--danger' },
|
||||||
|
pending: { labelKey: 'common.status.pending', cls: 'status-badge--draft' },
|
||||||
|
processing: { labelKey: 'common.status.processing', cls: 'status-badge--primary' },
|
||||||
|
completed: { labelKey: 'common.status.completed', cls: 'status-badge--enabled' },
|
||||||
|
closed: { labelKey: 'common.status.closed', cls: 'status-badge--disabled' },
|
||||||
|
deprecated: { labelKey: 'common.status.deprecated', cls: 'status-badge--danger' },
|
||||||
|
expired: { labelKey: 'common.status.expired', cls: 'status-badge--danger' },
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(input: string | boolean | number): { label: string, cls: string } {
|
||||||
|
// 1. 自定义 map 优先(业务状态:如 notices 0=草稿/1=已发布、tickets 0=待处理/1=处理中)
|
||||||
|
const mappedKey = props.map?.[String(input)]
|
||||||
|
if (mappedKey && statusMap[mappedKey])
|
||||||
|
return { label: t(statusMap[mappedKey].labelKey), cls: statusMap[mappedKey].cls }
|
||||||
|
// 2. 后端通用规范: 0=启用, 1=禁用
|
||||||
|
if (input === true || input === 'true' || input === '1' || input === 1 || input === '0' || input === 0 || input === false || input === 'false') {
|
||||||
|
const isEnabled = input === 0 || input === '0' || input === true || input === 'true'
|
||||||
|
return isEnabled
|
||||||
|
? { label: t('common.status.enabled'), cls: 'status-badge--enabled' }
|
||||||
|
: { label: t('common.status.disabled'), cls: 'status-badge--disabled' }
|
||||||
|
}
|
||||||
|
// 3. 内置状态映射兜底
|
||||||
|
const entry = statusMap[String(input)]
|
||||||
|
return entry ? { label: t(entry.labelKey), cls: entry.cls } : { label: String(input), cls: 'status-badge--disabled' }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<text v-if="dot" class="status-badge--dot" :class="resolve(status).cls" />
|
||||||
|
<text v-else class="status-badge" :class="resolve(status).cls">
|
||||||
|
<text class="status-badge--dot" :class="resolve(status).cls" />
|
||||||
|
{{ label || resolve(status).label }}
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 状态徽标全局样式(非 scoped,供页面复用类名)
|
||||||
|
* 颜色使用 wot-ui 语义变量,自动适配亮/暗主题 */
|
||||||
|
.status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
padding: 4rpx 14rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 10rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 各状态仅定义文字色,背景色由带外层 .status-badge 的组合规则提供,
|
||||||
|
* 使纯圆点模式只显示纯色圆点 */
|
||||||
|
.status-badge--enabled {
|
||||||
|
@apply wot-text-success-main;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--enabled {
|
||||||
|
@apply wot-bg-success-surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--disabled {
|
||||||
|
@apply wot-text-text-auxiliary;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--disabled {
|
||||||
|
@apply wot-bg-filled-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--primary {
|
||||||
|
@apply wot-text-primary-6;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--primary {
|
||||||
|
@apply wot-bg-primary-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--danger {
|
||||||
|
@apply wot-text-danger-main;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--danger {
|
||||||
|
@apply wot-bg-danger-surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* failed 为 danger 的别名(menus 页菜单类型使用) */
|
||||||
|
.status-badge--failed {
|
||||||
|
@apply wot-text-danger-main;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--failed {
|
||||||
|
@apply wot-bg-danger-surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--draft {
|
||||||
|
@apply wot-text-text-secondary;
|
||||||
|
}
|
||||||
|
.status-badge.status-badge--draft {
|
||||||
|
@apply wot-bg-filled-content;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2025-09-02 09:42:36
|
||||||
|
* @LastEditTime: 2026-04-10 11:01:37
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-starter/src/composables/types/theme.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ConfigProviderThemeVars } from '@wot-ui/ui'
|
||||||
|
|
||||||
|
export type PrimaryShadeKey
|
||||||
|
= | 'primary1'
|
||||||
|
| 'primary2'
|
||||||
|
| 'primary3'
|
||||||
|
| 'primary4'
|
||||||
|
| 'primary5'
|
||||||
|
| 'primary6'
|
||||||
|
| 'primary7'
|
||||||
|
| 'primary8'
|
||||||
|
| 'primary9'
|
||||||
|
| 'primary10'
|
||||||
|
|
||||||
|
export type ThemePrimaryShades = Record<PrimaryShadeKey, string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题色选项接口
|
||||||
|
*/
|
||||||
|
export interface ThemeColorOption {
|
||||||
|
/** i18n key(如 theme.colors.blue) */
|
||||||
|
labelKey: string
|
||||||
|
value: string
|
||||||
|
// 主色 6,用于列表和当前主题色圆点展示
|
||||||
|
primary: string
|
||||||
|
// 完整主色阶,用于注入 ConfigProvider 主题变量
|
||||||
|
primaryShades: ThemePrimaryShades
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题类型
|
||||||
|
*/
|
||||||
|
export type ThemeMode = 'light' | 'dark'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题状态接口
|
||||||
|
*/
|
||||||
|
export interface ThemeState {
|
||||||
|
theme: ThemeMode
|
||||||
|
followSystem: boolean
|
||||||
|
hasUserSet: boolean
|
||||||
|
currentThemeColor: ThemeColorOption
|
||||||
|
themeVars: ConfigProviderThemeVars
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预定义的主题色选项
|
||||||
|
*/
|
||||||
|
export const themeColorOptions: ThemeColorOption[] = [
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.blue',
|
||||||
|
value: 'blue',
|
||||||
|
primary: '#1C64FD',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#F5F8FF',
|
||||||
|
primary2: '#E5EDFF',
|
||||||
|
primary3: '#B8CFFF',
|
||||||
|
primary4: '#7CA4FF',
|
||||||
|
primary5: '#4480FF',
|
||||||
|
primary6: '#1C64FD',
|
||||||
|
primary7: '#164ED1',
|
||||||
|
primary8: '#1341AD',
|
||||||
|
primary9: '#0F3285',
|
||||||
|
primary10: '#0A235C',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.orange',
|
||||||
|
value: 'orange',
|
||||||
|
primary: '#FF7D00',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#FFF7F0',
|
||||||
|
primary2: '#FFEAD6',
|
||||||
|
primary3: '#FFD0A8',
|
||||||
|
primary4: '#FFB06E',
|
||||||
|
primary5: '#FF9338',
|
||||||
|
primary6: '#FF7D00',
|
||||||
|
primary7: '#D96800',
|
||||||
|
primary8: '#B35600',
|
||||||
|
primary9: '#8A4200',
|
||||||
|
primary10: '#5F2D00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.green',
|
||||||
|
value: 'green',
|
||||||
|
primary: '#07C160',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#F1FCF6',
|
||||||
|
primary2: '#DCF7E8',
|
||||||
|
primary3: '#B4EFD0',
|
||||||
|
primary4: '#7FE2AF',
|
||||||
|
primary5: '#42D28A',
|
||||||
|
primary6: '#07C160',
|
||||||
|
primary7: '#049F4F',
|
||||||
|
primary8: '#028241',
|
||||||
|
primary9: '#016532',
|
||||||
|
primary10: '#014825',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.pink',
|
||||||
|
value: 'pink',
|
||||||
|
primary: '#FF69B4',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#FFF2F9',
|
||||||
|
primary2: '#FFE2F1',
|
||||||
|
primary3: '#FFC2E3',
|
||||||
|
primary4: '#FF99D1',
|
||||||
|
primary5: '#FF7FC2',
|
||||||
|
primary6: '#FF69B4',
|
||||||
|
primary7: '#E84E9F',
|
||||||
|
primary8: '#CC3F8A',
|
||||||
|
primary9: '#A9336F',
|
||||||
|
primary10: '#7A2450',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.purple',
|
||||||
|
value: 'purple',
|
||||||
|
primary: '#8A2BE2',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#F7F2FF',
|
||||||
|
primary2: '#EEE2FF',
|
||||||
|
primary3: '#D9BDFF',
|
||||||
|
primary4: '#BC8DFF',
|
||||||
|
primary5: '#A05EFF',
|
||||||
|
primary6: '#8A2BE2',
|
||||||
|
primary7: '#7423BF',
|
||||||
|
primary8: '#5E1D9C',
|
||||||
|
primary9: '#491679',
|
||||||
|
primary10: '#321056',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'theme.colors.red',
|
||||||
|
value: 'red',
|
||||||
|
primary: '#FF4757',
|
||||||
|
primaryShades: {
|
||||||
|
primary1: '#FFF3F4',
|
||||||
|
primary2: '#FFE3E5',
|
||||||
|
primary3: '#FFC0C6',
|
||||||
|
primary4: '#FF909A',
|
||||||
|
primary5: '#FF6672',
|
||||||
|
primary6: '#FF4757',
|
||||||
|
primary7: '#DB3445',
|
||||||
|
primary8: '#B7293A',
|
||||||
|
primary9: '#931E2E',
|
||||||
|
primary10: '#6F1421',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
|
||||||
|
export interface AiChatStreamHandlers {
|
||||||
|
/** 收到内容分片(追加到当前 AI 消息) */
|
||||||
|
onChunk?: (text: string) => void
|
||||||
|
/** 生成结束([DONE] / [STOPPED]) */
|
||||||
|
onDone?: () => void
|
||||||
|
/** 连接或生成错误 */
|
||||||
|
onError?: (message: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建 AI 对话 WebSocket 地址:优先 VITE_APP_WS_ENDPOINT,否则从 API 域名推导(http→ws) */
|
||||||
|
function buildChatWsUrl(): string {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const token = userStore.getAccessToken() || ''
|
||||||
|
let wsBase = import.meta.env.VITE_APP_WS_ENDPOINT || ''
|
||||||
|
if (!wsBase) {
|
||||||
|
const apiBase = import.meta.env.VITE_API_BASE_URL || ''
|
||||||
|
wsBase = apiBase.replace(/^http/, 'ws')
|
||||||
|
}
|
||||||
|
const apiPrefix = import.meta.env.VITE_APP_BASE_API || '/api/v1'
|
||||||
|
// token 走 query(小程序 WebSocket 不支持自定义 subprotocol,后端已兼容 ?token=)
|
||||||
|
return `${wsBase}${apiPrefix}/ai/chat/ws?token=${encodeURIComponent(token)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 对话流式输出(WebSocket)
|
||||||
|
* 协议(对应后端 /ai/chat/ws):
|
||||||
|
* - 发送:{"message": "...", "session_id": "..."} | {"action": "stop"}
|
||||||
|
* - 接收:内容分片(纯文本)→ [DONE] 结束 / [STOPPED] 停止确认
|
||||||
|
*/
|
||||||
|
export function useAiChat() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const isStreaming = ref(false)
|
||||||
|
let socketTask: ReturnType<typeof uni.connectSocket> | null = null
|
||||||
|
let pendingOpen: Promise<void> | null = null
|
||||||
|
let resolveOpen: (() => void) | null = null
|
||||||
|
let closedByUser = false
|
||||||
|
let handlers: AiChatStreamHandlers = {}
|
||||||
|
|
||||||
|
function ensureConnected(): Promise<void> {
|
||||||
|
if (socketTask)
|
||||||
|
return Promise.resolve()
|
||||||
|
closedByUser = false
|
||||||
|
pendingOpen = new Promise<void>((resolve) => {
|
||||||
|
resolveOpen = resolve
|
||||||
|
})
|
||||||
|
const task = uni.connectSocket({ url: buildChatWsUrl(), complete: () => {} })
|
||||||
|
socketTask = task
|
||||||
|
task.onOpen(() => resolveOpen?.())
|
||||||
|
task.onMessage((res) => {
|
||||||
|
const text = typeof res.data === 'string' ? res.data : ''
|
||||||
|
if (text === '[DONE]' || text === '[STOPPED]') {
|
||||||
|
isStreaming.value = false
|
||||||
|
handlers.onDone?.()
|
||||||
|
}
|
||||||
|
else if (text) {
|
||||||
|
handlers.onChunk?.(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
task.onError((err) => {
|
||||||
|
resolveOpen?.()
|
||||||
|
isStreaming.value = false
|
||||||
|
handlers.onError?.(err?.errMsg || t('chat.wsConnectFailed'))
|
||||||
|
})
|
||||||
|
task.onClose(() => {
|
||||||
|
socketTask = null
|
||||||
|
if (!closedByUser) {
|
||||||
|
isStreaming.value = false
|
||||||
|
handlers.onError?.(t('chat.wsDisconnected'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return pendingOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发送一条流式对话消息(自动建立/复用连接) */
|
||||||
|
async function sendMessage(
|
||||||
|
payload: { message: string, session_id?: string | null },
|
||||||
|
handler: AiChatStreamHandlers,
|
||||||
|
): Promise<void> {
|
||||||
|
handlers = handler
|
||||||
|
isStreaming.value = true
|
||||||
|
try {
|
||||||
|
await ensureConnected()
|
||||||
|
socketTask?.send({
|
||||||
|
data: JSON.stringify({ message: payload.message, session_id: payload.session_id || undefined }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
isStreaming.value = false
|
||||||
|
handlers.onError?.(t('chat.wsSendFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 停止当前生成(后端停止后返回 [STOPPED]) */
|
||||||
|
function stop() {
|
||||||
|
if (!isStreaming.value)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
socketTask?.send({ data: JSON.stringify({ action: 'stop' }) })
|
||||||
|
}
|
||||||
|
catch { /* 忽略发送失败 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭连接(页面卸载时调用) */
|
||||||
|
function close() {
|
||||||
|
closedByUser = true
|
||||||
|
isStreaming.value = false
|
||||||
|
if (socketTask) {
|
||||||
|
socketTask.close({ code: 1000, reason: 'page unload' })
|
||||||
|
socketTask = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isStreaming, sendMessage, stop, close }
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import type { DialogOptions, DialogResult } from '@wot-ui/ui/components/wd-dialog/types'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export type GlobalDialogOptions = DialogOptions & {
|
||||||
|
success?: (res: DialogResult) => void
|
||||||
|
fail?: (res: DialogResult) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GlobalDialog {
|
||||||
|
dialogOptions: GlobalDialogOptions | null
|
||||||
|
currentPage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogType = NonNullable<DialogOptions['type']>
|
||||||
|
|
||||||
|
function isButtonPropsObject(value: unknown): value is Record<string, any> {
|
||||||
|
return value !== null && CommonUtil.isObj(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeButtonProps(props: unknown, text?: string) {
|
||||||
|
if (props === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isButtonPropsObject(props)) {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
...(text ? { text } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CommonUtil.isString(props) || text) {
|
||||||
|
return {
|
||||||
|
text: text || props,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props === undefined) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDefaultTypeOptions(option: GlobalDialogOptions, type?: DialogType): GlobalDialogOptions {
|
||||||
|
const next: GlobalDialogOptions = {
|
||||||
|
...option,
|
||||||
|
...(type ? { type } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.showCancelButton === undefined) {
|
||||||
|
if (next.type === 'alert') {
|
||||||
|
next.showCancelButton = false
|
||||||
|
}
|
||||||
|
else if (next.type === 'confirm' || next.type === 'prompt') {
|
||||||
|
next.showCancelButton = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDialogOptions(option: GlobalDialogOptions, type?: DialogType): GlobalDialogOptions {
|
||||||
|
const next = withDefaultTypeOptions(option, type)
|
||||||
|
|
||||||
|
next.confirmButtonProps = normalizeButtonProps(next.confirmButtonProps, next.confirmButtonText) as DialogOptions['confirmButtonProps']
|
||||||
|
|
||||||
|
if (next.showCancelButton === false) {
|
||||||
|
next.cancelButtonProps = null
|
||||||
|
}
|
||||||
|
else if (next.showCancelButton === true || next.cancelButtonProps !== undefined || next.cancelButtonText) {
|
||||||
|
next.cancelButtonProps = normalizeButtonProps(next.cancelButtonProps, next.cancelButtonText) as DialogOptions['cancelButtonProps']
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOption(option: GlobalDialogOptions | string, type?: DialogType): GlobalDialogOptions {
|
||||||
|
return normalizeDialogOptions(CommonUtil.isString(option) ? { title: option } : option, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGlobalDialog = defineStore('global-Dialog', {
|
||||||
|
state: (): GlobalDialog => ({
|
||||||
|
dialogOptions: null,
|
||||||
|
currentPage: '',
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
show(option: GlobalDialogOptions | string, type?: DialogType) {
|
||||||
|
this.currentPage = getCurrentPath()
|
||||||
|
this.dialogOptions = normalizeOption(option, type)
|
||||||
|
},
|
||||||
|
alert(option: GlobalDialogOptions | string) {
|
||||||
|
this.show(option, 'alert')
|
||||||
|
},
|
||||||
|
confirm(option: GlobalDialogOptions | string) {
|
||||||
|
this.show(option, 'confirm')
|
||||||
|
},
|
||||||
|
prompt(option: GlobalDialogOptions | string) {
|
||||||
|
this.show(option, 'prompt')
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.dialogOptions = null
|
||||||
|
this.currentPage = ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ToastOptions } from '@wot-ui/ui/components/wd-toast/types'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
interface GlobalLoading {
|
||||||
|
loadingOptions: ToastOptions
|
||||||
|
currentPage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: ToastOptions = {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
export const useGlobalLoading = defineStore('global-loading', {
|
||||||
|
state: (): GlobalLoading => ({
|
||||||
|
loadingOptions: defaultOptions,
|
||||||
|
currentPage: '',
|
||||||
|
}),
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
// 加载提示
|
||||||
|
loading(option: ToastOptions | string) {
|
||||||
|
this.currentPage = getCurrentPath()
|
||||||
|
this.loadingOptions = CommonUtil.deepMerge({
|
||||||
|
iconName: 'loading',
|
||||||
|
duration: 0,
|
||||||
|
cover: true,
|
||||||
|
position: 'middle',
|
||||||
|
show: true,
|
||||||
|
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions
|
||||||
|
},
|
||||||
|
// 关闭Toast
|
||||||
|
close() {
|
||||||
|
this.loadingOptions = defaultOptions
|
||||||
|
this.currentPage = ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export interface GlobalMessageOptions {
|
||||||
|
title?: string
|
||||||
|
content?: string
|
||||||
|
type?: 'alert' | 'confirm'
|
||||||
|
showCancel?: boolean
|
||||||
|
confirmText?: string
|
||||||
|
cancelText?: string
|
||||||
|
success?: (res: { confirm: boolean, cancel: boolean }) => void
|
||||||
|
fail?: (err: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局消息 store:GlobalMessage 组件监听 messageOptions,
|
||||||
|
* 仅当触发页面与 currentPage 一致时展示,避免跨页误弹
|
||||||
|
*/
|
||||||
|
export const useGlobalMessage = defineStore('global-message', {
|
||||||
|
state: () => ({
|
||||||
|
messageOptions: null as GlobalMessageOptions | null,
|
||||||
|
currentPage: '',
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
show(options: GlobalMessageOptions) {
|
||||||
|
this.currentPage = getCurrentPath()
|
||||||
|
this.messageOptions = options
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.messageOptions = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { ToastOptions } from '@wot-ui/ui/components/wd-toast/types'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
interface GlobalToast {
|
||||||
|
toastOptions: ToastOptions
|
||||||
|
currentPage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: ToastOptions = {
|
||||||
|
duration: 2000,
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
export const useGlobalToast = defineStore('global-toast', {
|
||||||
|
state: (): GlobalToast => ({
|
||||||
|
toastOptions: defaultOptions,
|
||||||
|
currentPage: '',
|
||||||
|
}),
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
// 打开Toast
|
||||||
|
show(option: ToastOptions | string) {
|
||||||
|
this.currentPage = getCurrentPath()
|
||||||
|
const options = CommonUtil.deepMerge(defaultOptions, typeof option === 'string' ? { msg: option } : option) as ToastOptions
|
||||||
|
this.toastOptions = CommonUtil.deepMerge(options, {
|
||||||
|
show: true,
|
||||||
|
position: options.position || 'middle',
|
||||||
|
}) as ToastOptions
|
||||||
|
},
|
||||||
|
// 成功提示
|
||||||
|
success(option: ToastOptions | string) {
|
||||||
|
this.show(CommonUtil.deepMerge({
|
||||||
|
iconName: 'success',
|
||||||
|
iconColor: '#07c160',
|
||||||
|
duration: 1500,
|
||||||
|
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
|
||||||
|
},
|
||||||
|
// 关闭提示
|
||||||
|
error(option: ToastOptions | string) {
|
||||||
|
this.show(CommonUtil.deepMerge({
|
||||||
|
iconName: 'error',
|
||||||
|
iconColor: '#fa5151',
|
||||||
|
direction: 'vertical',
|
||||||
|
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
|
||||||
|
},
|
||||||
|
// 常规提示
|
||||||
|
info(option: ToastOptions | string) {
|
||||||
|
this.show(CommonUtil.deepMerge({
|
||||||
|
iconName: 'info',
|
||||||
|
iconColor: '#10aeff',
|
||||||
|
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
|
||||||
|
},
|
||||||
|
// 警告提示
|
||||||
|
warning(option: ToastOptions | string) {
|
||||||
|
this.show(CommonUtil.deepMerge({
|
||||||
|
iconName: 'warning',
|
||||||
|
iconColor: '#ff9f0a',
|
||||||
|
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
|
||||||
|
},
|
||||||
|
// 关闭Toast
|
||||||
|
close() {
|
||||||
|
this.toastOptions = defaultOptions
|
||||||
|
this.currentPage = ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面导航栏标题国际化
|
||||||
|
* definePage 的 navigationBarTitleText 是编译期静态文案,
|
||||||
|
* 这里在 onShow 时按当前语言动态覆盖标题,语言切换时也能即时刷新。
|
||||||
|
*/
|
||||||
|
export function useI18nNavTitle(key: string) {
|
||||||
|
const { t, locale } = useI18n()
|
||||||
|
function apply() {
|
||||||
|
uni.setNavigationBarTitle({ title: t(key) })
|
||||||
|
}
|
||||||
|
onShow(apply)
|
||||||
|
watch(locale, apply)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import type { AlovaGenerics, Method } from 'alova'
|
||||||
|
import { usePagination } from 'alova/client'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
export interface ListPageParams {
|
||||||
|
page_no: number
|
||||||
|
page_size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListPageOptions<AG extends AlovaGenerics = AlovaGenerics> {
|
||||||
|
fetcher: (params: ListPageParams) => Method<AG>
|
||||||
|
pageSize?: number
|
||||||
|
onError?: (error: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用列表分页逻辑(基于 alova usePagination 封装)
|
||||||
|
* 底层使用 alova 状态管理,统一暴露 list/total/loading/error 三态与翻页动作
|
||||||
|
* 说明:fetcher 需返回 alova Method(http 层 get/post 均为 Method 实例),
|
||||||
|
* 请求发送与错误处理完全交给 alova hook 管理
|
||||||
|
*/
|
||||||
|
export function useListPage<T, AG extends AlovaGenerics = AlovaGenerics>(options: ListPageOptions<AG>) {
|
||||||
|
const { fetcher, pageSize = 10, onError } = options
|
||||||
|
|
||||||
|
const pageParams = ref<ListPageParams>({ page_no: 1, page_size: pageSize })
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
total,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
send,
|
||||||
|
onError: onPageError,
|
||||||
|
} = usePagination<AG, T[], any[]>(
|
||||||
|
(pageNo: number, pageSizeNo: number) => fetcher({ page_no: pageNo, page_size: pageSizeNo }),
|
||||||
|
{
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: pageSize,
|
||||||
|
// 由页面手动触发(onLoad/搜索/翻页),禁用自动监听与预加载
|
||||||
|
immediate: false,
|
||||||
|
watchingStates: [],
|
||||||
|
preloadNextPage: false,
|
||||||
|
preloadPreviousPage: false,
|
||||||
|
// 列表页总是请求最新数据,禁用 alova 响应缓存
|
||||||
|
force: true,
|
||||||
|
// 响应已由 http 层 responded 解包为业务结构 { list, total }
|
||||||
|
data: res => (res as PageResult<T>).list ?? [],
|
||||||
|
total: res => (res as PageResult<T>).total ?? 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
onPageError(({ error: e }) => {
|
||||||
|
onError?.(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 加载当前页数据 */
|
||||||
|
async function loadData() {
|
||||||
|
try {
|
||||||
|
await send(pageParams.value.page_no, pageParams.value.page_size)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 错误已由 onPageError → onError 统一处理,避免未捕获 Promise 告警
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// 收起下拉刷新指示器(onPullDownRefresh → loadData 场景;非刷新场景调用无害)
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 上一页 */
|
||||||
|
async function loadPrev() {
|
||||||
|
if (pageParams.value.page_no <= 1)
|
||||||
|
return
|
||||||
|
pageParams.value.page_no -= 1
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下一页 */
|
||||||
|
async function loadNext() {
|
||||||
|
pageParams.value.page_no += 1
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳回第一页(搜索/重置时使用) */
|
||||||
|
async function toFirst() {
|
||||||
|
pageParams.value.page_no = 1
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: computed<T[]>(() => data.value ?? []),
|
||||||
|
total: computed<number>(() => total.value ?? 0),
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
pageParams,
|
||||||
|
loadData,
|
||||||
|
loadPrev,
|
||||||
|
loadNext,
|
||||||
|
toFirst,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
/** 分享配置 */
|
||||||
|
interface ShareConfig {
|
||||||
|
/** 分享标题 */
|
||||||
|
title: string
|
||||||
|
/** 分享路径(带 / 开头,例如 /pages/index/index) */
|
||||||
|
path?: string
|
||||||
|
/** 分享图片 URL(可选,不传则使用页面截图) */
|
||||||
|
imageUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 默认分享路径(全局兜底) */
|
||||||
|
const DEFAULT_SHARE_PATH = '/pages/index/index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局分享 composable
|
||||||
|
*
|
||||||
|
* 在页面 setup 中调用即可注册 onShareAppMessage + onShareTimeline。
|
||||||
|
* 微信小程序中,页面必须调用此 composable 才能开启右上角"分享"按钮。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // 静态分享
|
||||||
|
* useShare({ title: '工作台', path: '/pages/work/index' })
|
||||||
|
*
|
||||||
|
* // 动态分享(标题随数据变化)
|
||||||
|
* const shareTitle = computed(() => `${userInfo.value?.name || '管理员'} 邀请你使用`)
|
||||||
|
* useShare(() => ({ title: shareTitle.value, path: '/pages/index/index' }))
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useShare(configOrGetter?: ShareConfig | (() => ShareConfig)) {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const getConfig = (): ShareConfig => {
|
||||||
|
// 未指定分享标题时使用全局默认(随当前语言)
|
||||||
|
const defaults: ShareConfig = { title: t('common.shareDefaultTitle'), path: DEFAULT_SHARE_PATH }
|
||||||
|
if (typeof configOrGetter === 'function')
|
||||||
|
return { ...defaults, ...configOrGetter() }
|
||||||
|
if (configOrGetter)
|
||||||
|
return { ...defaults, ...configOrGetter }
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
onShareAppMessage(() => {
|
||||||
|
const cfg = getConfig()
|
||||||
|
return {
|
||||||
|
title: cfg.title,
|
||||||
|
path: cfg.path || DEFAULT_SHARE_PATH,
|
||||||
|
imageUrl: cfg.imageUrl,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onShareTimeline(() => {
|
||||||
|
const cfg = getConfig()
|
||||||
|
return {
|
||||||
|
title: cfg.title,
|
||||||
|
query: '', // 朋友圈分享不支持 path,用 query 传参
|
||||||
|
imageUrl: cfg.imageUrl,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信订阅消息 composable
|
||||||
|
*
|
||||||
|
* 封装 wx.requestSubscribeMessage,在用户操作时请求订阅消息授权。
|
||||||
|
* 常见场景:工单状态变更、通知公告发布、系统到期提醒等。
|
||||||
|
*
|
||||||
|
* 使用前提:
|
||||||
|
* 1. 在微信公众平台配置订阅消息模板
|
||||||
|
* 2. 将模板 ID 填入此处常量
|
||||||
|
* 3. 仅微信小程序端有效
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const { subscribe } = useSubscribeMessage()
|
||||||
|
*
|
||||||
|
* // 提交工单时订阅"工单状态变更"通知
|
||||||
|
* async function submitTicket() {
|
||||||
|
* await subscribe([TEMPLATE_IDS.TICKET_STATUS])
|
||||||
|
* // ...提交工单
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 订阅消息模板 ID(在微信公众平台 → 订阅消息中配置) */
|
||||||
|
export const TEMPLATE_IDS = {
|
||||||
|
/** 工单状态变更通知 */
|
||||||
|
TICKET_STATUS: '',
|
||||||
|
/** 通知公告发布提醒 */
|
||||||
|
NOTICE_PUBLISH: '',
|
||||||
|
/** 系统到期提醒 */
|
||||||
|
SYSTEM_EXPIRY: '',
|
||||||
|
/** 工单回复通知 */
|
||||||
|
TICKET_REPLY: '',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 订阅消息模板 ID 类型 */
|
||||||
|
export type TemplateId = keyof typeof TEMPLATE_IDS
|
||||||
|
|
||||||
|
/** 订阅结果 */
|
||||||
|
interface SubscribeResult {
|
||||||
|
/** 模板 ID */
|
||||||
|
templateId: string
|
||||||
|
/** 订阅状态:accept=同意, reject=拒绝, ban=被禁止 */
|
||||||
|
status: 'accept' | 'reject' | 'ban' | 'filter'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅消息 composable
|
||||||
|
*/
|
||||||
|
export function useSubscribeMessage() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
/**
|
||||||
|
* 请求订阅消息授权
|
||||||
|
*
|
||||||
|
* @param templateIds 模板 ID 列表(支持传 key 名称或完整 ID)
|
||||||
|
* @returns 订阅结果数组,每项包含模板 ID 和用户选择状态
|
||||||
|
*/
|
||||||
|
async function subscribe(templateIds: Array<TemplateId | string>): Promise<SubscribeResult[]> {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 将 key 名称解析为实际模板 ID
|
||||||
|
const ids = templateIds.map((id) => {
|
||||||
|
if (id in TEMPLATE_IDS)
|
||||||
|
return TEMPLATE_IDS[id as TemplateId]
|
||||||
|
return id
|
||||||
|
}).filter(Boolean)
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
console.warn('[useSubscribeMessage] 未配置有效的模板 ID')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await uni.requestSubscribeMessage({
|
||||||
|
tmplIds: ids,
|
||||||
|
}) as unknown as Record<string, string>
|
||||||
|
|
||||||
|
// 微信返回格式: { [templateId]: 'accept' | 'reject' | 'ban' | 'filter' }
|
||||||
|
const results: SubscribeResult[] = ids.map(templateId => ({
|
||||||
|
templateId,
|
||||||
|
status: res[templateId] as SubscribeResult['status'],
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 记录用户选择,可用于后续引导
|
||||||
|
const accepted = results.filter(r => r.status === 'accept')
|
||||||
|
if (accepted.length > 0)
|
||||||
|
console.log(`[useSubscribeMessage] 用户已订阅 ${accepted.length} 个模板`)
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('[useSubscribeMessage] 订阅失败', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
return []
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查订阅消息设置状态
|
||||||
|
* 用户可能在设置中关闭了订阅消息,此方法检测并引导用户开启
|
||||||
|
*
|
||||||
|
* @returns 是否可以发送订阅消息
|
||||||
|
*/
|
||||||
|
async function checkSubscriptionStatus(): Promise<boolean> {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
try {
|
||||||
|
const settings = await uni.getSetting({})
|
||||||
|
const subscription = (settings as any).authSetting?.subscribeMessage
|
||||||
|
// 如果用户曾经拒绝过,引导去设置页开启
|
||||||
|
if (subscription === false) {
|
||||||
|
uni.showModal({
|
||||||
|
title: t('common.subscribeTitle'),
|
||||||
|
content: t('common.subscribeContent'),
|
||||||
|
confirmText: t('common.subscribeGoSetting'),
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm)
|
||||||
|
uni.openSetting({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
return false
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
checkSubscriptionStatus,
|
||||||
|
TEMPLATE_IDS,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
export interface TabbarItem {
|
||||||
|
name: string
|
||||||
|
value?: number
|
||||||
|
active: boolean
|
||||||
|
titleKey: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabbarItems = ref<TabbarItem[]>([
|
||||||
|
{ name: 'home', active: true, titleKey: 'common.tab.home', icon: 'home' },
|
||||||
|
{ name: 'work', active: false, titleKey: 'common.tab.work', icon: 'apps' },
|
||||||
|
{ name: 'mine', active: false, titleKey: 'common.tab.mine', icon: 'user' },
|
||||||
|
])
|
||||||
|
|
||||||
|
export function useTabbar() {
|
||||||
|
const tabbarList = computed(() => tabbarItems.value)
|
||||||
|
|
||||||
|
const activeTabbar = computed(() => {
|
||||||
|
const item = tabbarItems.value.find(item => item.active)
|
||||||
|
return item || tabbarItems.value[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const getTabbarItemValue = (name: string) => {
|
||||||
|
const item = tabbarItems.value.find(item => item.name === name)
|
||||||
|
return item?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTabbarItem = (name: string, value: number) => {
|
||||||
|
const tabbarItem = tabbarItems.value.find(item => item.name === name)
|
||||||
|
if (tabbarItem) {
|
||||||
|
tabbarItem.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTabbarItemActive = (name: string) => {
|
||||||
|
tabbarItems.value.forEach((item) => {
|
||||||
|
if (item.name === name) {
|
||||||
|
item.active = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item.active = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabbarList,
|
||||||
|
activeTabbar,
|
||||||
|
getTabbarItemValue,
|
||||||
|
setTabbarItem,
|
||||||
|
setTabbarItemActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tab 页 onShow 时同步底部导航高亮。
|
||||||
|
* 通过 getCurrentPages 校验当前路由,避免从子页面返回时误触发
|
||||||
|
* (子页面返回的中间态路由是子页面自身,不会 emit 到错误 tab)。
|
||||||
|
*/
|
||||||
|
export function useTabbarActive(route: string, tabKey: string, onActive?: () => void) {
|
||||||
|
onShow(() => {
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
if (pages.length > 0 && pages[pages.length - 1].route === route) {
|
||||||
|
uni.$emit('updateTabbar', tabKey)
|
||||||
|
onActive?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import type { ThemeColorOption, ThemeMode } from '@/composables/types/theme'
|
||||||
|
import { themeColorOptions } from '@/composables/types/theme'
|
||||||
|
import { initializeThemeOnce, subscribeSystemThemeChange } from '@/utils/systemTheme'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题管理组合式API(统一入口)
|
||||||
|
*
|
||||||
|
* 功能特性:
|
||||||
|
* - 支持手动切换暗黑模式
|
||||||
|
* - 支持主题色选择
|
||||||
|
* - 支持跟随系统主题
|
||||||
|
* - 自动同步导航栏颜色
|
||||||
|
* - 持久化用户设置
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```vue
|
||||||
|
* <script setup>
|
||||||
|
* const {
|
||||||
|
* theme,
|
||||||
|
* isDark,
|
||||||
|
* toggleTheme,
|
||||||
|
* openThemeColorPicker,
|
||||||
|
* currentThemeColor,
|
||||||
|
* themeVars
|
||||||
|
* } = useTheme()
|
||||||
|
* </script>
|
||||||
|
*
|
||||||
|
* <template>
|
||||||
|
* <wd-config-provider :theme="theme" :theme-vars="themeVars">
|
||||||
|
* <view :class="{ 'dark-mode': isDark }">
|
||||||
|
* <wd-button @click="toggleTheme">切换主题</wd-button>
|
||||||
|
* <wd-button @click="openThemeColorPicker">选择主题色</wd-button>
|
||||||
|
* </view>
|
||||||
|
* </wd-config-provider>
|
||||||
|
* </template>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useTheme() {
|
||||||
|
const store = useThemeStore()
|
||||||
|
const showThemeColorSheet = ref(false)
|
||||||
|
let stopThemeChangeListener: (() => void) | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换暗黑模式
|
||||||
|
* @param mode 指定主题模式,不传则自动切换
|
||||||
|
* @param isFollowSystem 是否跟随系统
|
||||||
|
*/
|
||||||
|
function toggleTheme(mode?: ThemeMode, isFollowSystem: boolean = false) {
|
||||||
|
store.toggleTheme(mode, isFollowSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开主题色选择器
|
||||||
|
*/
|
||||||
|
function openThemeColorPicker() {
|
||||||
|
showThemeColorSheet.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭主题色选择器
|
||||||
|
*/
|
||||||
|
function closeThemeColorPicker() {
|
||||||
|
showThemeColorSheet.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择主题色
|
||||||
|
* @param option 主题色选项
|
||||||
|
*/
|
||||||
|
function selectThemeColor(option: ThemeColorOption) {
|
||||||
|
store.setCurrentThemeColor(option)
|
||||||
|
closeThemeColorPicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化主题
|
||||||
|
*/
|
||||||
|
function initTheme() {
|
||||||
|
store.initTheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载前初始化主题
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initializeThemeOnce(store, initTheme)
|
||||||
|
stopThemeChangeListener = subscribeSystemThemeChange(store, (res) => {
|
||||||
|
if (store.followSystem) {
|
||||||
|
store.toggleTheme(res.theme, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面显示时更新导航栏颜色,确保每次切换页面时导航栏颜色都是正确的
|
||||||
|
onShow(() => {
|
||||||
|
store.setNavigationBarColor()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理监听
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopThemeChangeListener?.()
|
||||||
|
stopThemeChangeListener = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
theme: computed(() => store.theme),
|
||||||
|
isDark: computed(() => store.isDark),
|
||||||
|
followSystem: computed(() => store.followSystem),
|
||||||
|
hasUserSet: computed(() => store.hasUserSet),
|
||||||
|
currentThemeColor: computed(() => store.currentThemeColor),
|
||||||
|
themeVars: computed(() => store.themeVars),
|
||||||
|
showThemeColorSheet,
|
||||||
|
|
||||||
|
// 常量
|
||||||
|
themeColorOptions,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
initTheme,
|
||||||
|
toggleTheme,
|
||||||
|
setFollowSystem: store.setFollowSystem,
|
||||||
|
openThemeColorPicker,
|
||||||
|
closeThemeColorPicker,
|
||||||
|
selectThemeColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出类型和常量供外部使用
|
||||||
|
export type { ThemeColorOption, ThemeMode }
|
||||||
|
export { themeColorOptions }
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { TicketAPI } from '@/api/module_system/ticket'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单统计(后端聚合接口 + alova 30 秒缓存),work/mine 两页共用
|
||||||
|
*/
|
||||||
|
export function useTicketStats() {
|
||||||
|
const pendingTickets = ref<number | null>(null)
|
||||||
|
const processingTickets = ref<number | null>(null)
|
||||||
|
const doneTickets = ref<number | null>(null)
|
||||||
|
|
||||||
|
async function loadTicketStats() {
|
||||||
|
try {
|
||||||
|
const stats = await TicketAPI.getStats()
|
||||||
|
pendingTickets.value = stats.pending
|
||||||
|
processingTickets.value = stats.processing
|
||||||
|
doneTickets.value = stats.done
|
||||||
|
}
|
||||||
|
catch { /* silent */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { pendingTickets, processingTickets, doneTickets, loadTicketStats }
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { computed, onUnmounted, ref } from 'vue'
|
||||||
|
import { WATERMARK_KEY } from '@/constants'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
import { Storage } from '@/utils/storage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序水印逻辑(配合 wd-watermark 使用)
|
||||||
|
* - 显示条件:本地偏好开启(默认开,由设置页"水印"开关控制)&& 已登录
|
||||||
|
* - 水印内容为当前登录用户名(username 优先,回退 name)
|
||||||
|
* - 响应式:登录用户 / 设置页开关变更后自动更新(layout 常驻不重挂载,经 uni.$on 同步)
|
||||||
|
*/
|
||||||
|
export function useWatermark() {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const themeStore = useThemeStore()
|
||||||
|
// 兼容历史脏对象 { value },统一兜底为布尔(wd-switch change 事件参数为对象)
|
||||||
|
const stored = Storage.get<boolean | { value: boolean }>(WATERMARK_KEY)
|
||||||
|
const localSwitch = ref(typeof stored === 'object' && stored ? Boolean(stored.value) : (stored ?? true))
|
||||||
|
|
||||||
|
const content = computed(() => userStore.userInfo?.username || userStore.userInfo?.name || '')
|
||||||
|
const enabled = computed(() => localSwitch.value && !!content.value)
|
||||||
|
// 水印文字使用当前主题色,主题色切换后经 wd-watermark 的 props deep watch 自动重绘
|
||||||
|
const color = computed(() => themeStore.currentThemeColor.primary)
|
||||||
|
|
||||||
|
// 设置页切换水印开关时同步本地状态(uni.$on 在 H5 / 小程序均可用)
|
||||||
|
const onSwitchChange = (value: boolean) => {
|
||||||
|
localSwitch.value = value
|
||||||
|
}
|
||||||
|
uni.$on('updateWatermarkSwitch', onSwitchChange)
|
||||||
|
onUnmounted(() => {
|
||||||
|
uni.$off('updateWatermarkSwitch', onSwitchChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { enabled, content, color }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序登录 composable
|
||||||
|
*
|
||||||
|
* 封装 uni.login → 后端换 token 的完整流程。
|
||||||
|
* 仅在微信小程序端生效(#ifdef MP-WEIXIN)。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const { wxLogin, wxPhoneLogin } = useWxLogin()
|
||||||
|
*
|
||||||
|
* // 微信一键登录
|
||||||
|
* async function handleWxLogin() {
|
||||||
|
* await wxLogin()
|
||||||
|
* uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 手机号登录(配合 <button open-type="getPhoneNumber">)
|
||||||
|
* async function handlePhoneLogin(e) {
|
||||||
|
* if (e.detail.errMsg !== 'getPhoneNumber:ok') return
|
||||||
|
* await wxPhoneLogin(e.detail.code)
|
||||||
|
* uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useWxLogin() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信一键登录
|
||||||
|
* 1. uni.login 获取 code
|
||||||
|
* 2. 可选获取用户头像昵称(需用户授权)
|
||||||
|
* 3. 发送 code 给后端换取 JWT
|
||||||
|
*/
|
||||||
|
async function wxLogin(): Promise<void> {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const { code } = await uni.login({ provider: 'weixin' })
|
||||||
|
if (!code)
|
||||||
|
throw new Error(t('login.wxAuthFailed'))
|
||||||
|
|
||||||
|
await userStore.wxLogin({ code })
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.showToast({ title: t('login.wxOnly'), icon: 'none' })
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信手机号快速登录
|
||||||
|
* 配合 <button open-type="getPhoneNumber"> 使用
|
||||||
|
* 使用 2023+ 新方案:仅需 getPhoneNumber 回调返回的 code,无需 encryptedData/iv
|
||||||
|
*
|
||||||
|
* @param phoneCode getPhoneNumber 回调返回的动态令牌(e.detail.code)
|
||||||
|
*/
|
||||||
|
async function wxPhoneLogin(phoneCode: string): Promise<void> {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
if (!phoneCode)
|
||||||
|
throw new Error(t('login.phoneCodeFailed'))
|
||||||
|
|
||||||
|
await userStore.wxPhoneLogin({ code: phoneCode })
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.showToast({ title: t('login.wxOnly'), icon: 'none' })
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
wxLogin,
|
||||||
|
wxPhoneLogin,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* 常量统一导出
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 存储相关常量
|
||||||
|
export * from './storage.constant'
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* markdown 渲染公共配置(配合 mp-html 组件使用)
|
||||||
|
* 中性 rgba 色可自适应亮暗主题;使用 wot 语义色可随品牌主题联动
|
||||||
|
*/
|
||||||
|
export const MARKDOWN_TAG_STYLE: Record<string, string> = {
|
||||||
|
h1: 'font-size:17px;font-weight:700;margin:10px 0 6px;line-height:1.5;',
|
||||||
|
h2: 'font-size:16px;font-weight:700;margin:9px 0 5px;line-height:1.5;',
|
||||||
|
h3: 'font-size:15px;font-weight:700;margin:8px 0 4px;line-height:1.5;',
|
||||||
|
p: 'margin:6px 0;line-height:1.7;',
|
||||||
|
ul: 'margin:6px 0;padding-left:18px;',
|
||||||
|
ol: 'margin:6px 0;padding-left:18px;',
|
||||||
|
li: 'margin:3px 0;line-height:1.7;',
|
||||||
|
code: 'font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:85%;background:rgba(127,127,127,.14);padding:2px 5px;border-radius:4px;',
|
||||||
|
pre: 'background:rgba(127,127,127,.10);padding:10px 12px;border-radius:8px;overflow-x:auto;margin:8px 0;',
|
||||||
|
blockquote: 'margin:8px 0;padding:2px 12px;border-left:3px solid rgba(127,127,127,.35);color:rgba(127,127,127,.9);',
|
||||||
|
table: 'width:100%;border-collapse:collapse;margin:8px 0;',
|
||||||
|
th: 'border:1px solid rgba(127,127,127,.25);padding:6px 10px;text-align:left;font-weight:600;',
|
||||||
|
td: 'border:1px solid rgba(127,127,127,.25);padding:6px 10px;text-align:left;',
|
||||||
|
a: 'color:#4F8CFF;',
|
||||||
|
img: 'max-width:100%;border-radius:8px;',
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 存储键名常量
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 统一管理所有 uni.storage 的键名,与 web 端保持一致
|
||||||
|
* 命名规则:{分类}_{具体名称}(snake_case)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 🔐 用户认证相关
|
||||||
|
export const ACCESS_TOKEN_KEY = 'access_token'
|
||||||
|
export const REFRESH_TOKEN_KEY = 'refresh_token'
|
||||||
|
export const REMEMBER_ME_KEY = 'remember_me'
|
||||||
|
|
||||||
|
// 📊 数据缓存相关
|
||||||
|
export const DICT_CACHE_KEY = 'dict_cache'
|
||||||
|
|
||||||
|
// 🎨 系统设置相关
|
||||||
|
export const THEME_KEY = 'theme'
|
||||||
|
export const THEME_COLOR_KEY = 'themeColor'
|
||||||
|
|
||||||
|
// 🛡️ 功能开关相关
|
||||||
|
export const WATERMARK_KEY = 'watermark_switch'
|
||||||
|
|
||||||
|
// 🌍 国际化相关
|
||||||
|
export const LANG_KEY = 'lang'
|
||||||
|
|
||||||
|
// 🎯 角色常量
|
||||||
|
export const ROLE_ROOT = 'ADMIN'
|
||||||
|
|
||||||
|
// 📌 认证相关键集合
|
||||||
|
export const AUTH_KEYS = {
|
||||||
|
ACCESS_TOKEN: ACCESS_TOKEN_KEY,
|
||||||
|
REFRESH_TOKEN: REFRESH_TOKEN_KEY,
|
||||||
|
REMEMBER_ME: REMEMBER_ME_KEY,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// 📦 缓存相关键集合
|
||||||
|
export const CACHE_KEYS = {
|
||||||
|
DICT_CACHE: DICT_CACHE_KEY,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// 🎨 设置相关键集合
|
||||||
|
export const SETTINGS_KEYS = {
|
||||||
|
THEME: THEME_KEY,
|
||||||
|
THEME_COLOR: THEME_COLOR_KEY,
|
||||||
|
WATERMARK: WATERMARK_KEY,
|
||||||
|
LANG: LANG_KEY,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// 📦 所有存储键的统一集合
|
||||||
|
export const ALL_STORAGE_KEYS = {
|
||||||
|
...AUTH_KEYS,
|
||||||
|
...CACHE_KEYS,
|
||||||
|
...SETTINGS_KEYS,
|
||||||
|
} as const
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<view></view>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
Component({
|
||||||
|
data: {},
|
||||||
|
methods: {},
|
||||||
|
})
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import type { Method } from 'alova'
|
||||||
|
import type { UniappNormalResponse } from '../types'
|
||||||
|
import AdapterUniapp from '@alova/adapter-uniapp'
|
||||||
|
import { createAlova } from 'alova'
|
||||||
|
import VueHook from 'alova/vue'
|
||||||
|
import AuthAPI from '@/api/module_system/auth'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
import { toLoginPage } from '@/utils/toLoginPage'
|
||||||
|
import { ContentTypeEnum, HttpStatus, ResultEnum, ShowMessage } from '../tools/enum'
|
||||||
|
|
||||||
|
// 配置动态Tag
|
||||||
|
export const API_DOMAINS = {
|
||||||
|
DEFAULT: import.meta.env.VITE_API_BASE_URL || '',
|
||||||
|
SECONDARY: import.meta.env.VITE_SERVER_BASEURL_SECONDARY || '',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从响应中提取错误消息:优先后端 msg,其次按状态码映射 */
|
||||||
|
function getErrorMessage(statusCode: number, rawData: any): string {
|
||||||
|
return rawData?.msg || rawData?.message || rawData?.error || ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 登录态失效:清空凭据与用户信息后回登录页(不调用后端注销接口,避免 token 失效后再触发 401) */
|
||||||
|
function handleAuthExpired(userStore: ReturnType<typeof useUserStore>) {
|
||||||
|
userStore.clearAll()
|
||||||
|
toLoginPage({ mode: 'reLaunch' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 未登录重定向防抖标记:并发请求只跳一次登录页 */
|
||||||
|
let redirectingToLogin = false
|
||||||
|
|
||||||
|
// ===== 401 处理(仿 web 端 utils/http/index.ts):刷新 token + 并发队列重放 =====
|
||||||
|
|
||||||
|
/** token 刷新进行中标记,避免并发 401 触发多次 refresh 请求 */
|
||||||
|
let isRefreshing = false
|
||||||
|
let pendingRequests: Array<{
|
||||||
|
method: Method
|
||||||
|
resolve: (value: unknown) => void
|
||||||
|
reject: (reason?: unknown) => void
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
/** 刷新成功:重放等待队列(重放时 beforeRequest 会自动注入新 token) */
|
||||||
|
function onRefreshed() {
|
||||||
|
const list = pendingRequests
|
||||||
|
pendingRequests = []
|
||||||
|
list.forEach(({ method, resolve }) => resolve(method.send()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 刷新失败:拒绝等待队列 */
|
||||||
|
function onRefreshFailed() {
|
||||||
|
pendingRequests.forEach(({ reject }) => reject(new Error('[认证失效]:登录已过期')))
|
||||||
|
pendingRequests = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 401 统一处理:
|
||||||
|
* - refresh 请求自身 401:抛错交给刷新发起方(避免死循环)
|
||||||
|
* - login/visitor 公开请求 401:按普通错误提示
|
||||||
|
* - 业务请求 401:首次刷新,并发入队;刷新成功重放,失败清凭据跳登录
|
||||||
|
*/
|
||||||
|
async function handleUnauthorized(method: Method, rawData: any): Promise<unknown> {
|
||||||
|
const { config } = method
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const role = config.meta?.authRole
|
||||||
|
const message = getErrorMessage(HttpStatus.UNAUTHORIZED, rawData)
|
||||||
|
|
||||||
|
if (role === 'refreshToken')
|
||||||
|
throw new Error(message)
|
||||||
|
if (role === 'login' || role === 'visitor')
|
||||||
|
return throwApiError(message, config)
|
||||||
|
if (method.url?.includes('/auth/logout')) {
|
||||||
|
handleAuthExpired(userStore)
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRefreshing) {
|
||||||
|
isRefreshing = true
|
||||||
|
try {
|
||||||
|
const refreshToken = userStore.getRefreshToken()
|
||||||
|
if (!refreshToken)
|
||||||
|
throw new Error('[认证失效]:请重新登录')
|
||||||
|
const res = await AuthAPI.refreshToken({ refresh_token: refreshToken })
|
||||||
|
if (!res?.access_token)
|
||||||
|
throw new Error('[认证失效]:登录已过期')
|
||||||
|
userStore.setAccessToken(res.access_token)
|
||||||
|
if (res.refresh_token)
|
||||||
|
userStore.setRefreshToken(res.refresh_token)
|
||||||
|
isRefreshing = false
|
||||||
|
onRefreshed()
|
||||||
|
return method.send()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
isRefreshing = false
|
||||||
|
onRefreshFailed()
|
||||||
|
handleAuthExpired(userStore)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
pendingRequests.push({ method, resolve, reject })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alova 请求实例
|
||||||
|
*/
|
||||||
|
const alovaInstance = createAlova({
|
||||||
|
baseURL: `${API_DOMAINS.DEFAULT}${import.meta.env.VITE_APP_BASE_API || ''}`,
|
||||||
|
...AdapterUniapp(),
|
||||||
|
timeout: 10000,
|
||||||
|
statesHook: VueHook,
|
||||||
|
|
||||||
|
beforeRequest(method) {
|
||||||
|
// 设置默认 Content-Type
|
||||||
|
method.config.headers = {
|
||||||
|
ContentType: ContentTypeEnum.JSON,
|
||||||
|
Accept: 'application/json, text/plain, */*',
|
||||||
|
...method.config.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { config } = method
|
||||||
|
// 处理动态域名
|
||||||
|
if (config.meta?.domain)
|
||||||
|
method.baseURL = config.meta.domain
|
||||||
|
|
||||||
|
// 免认证请求(验证码/登录/刷新令牌等)通过 meta.ignoreAuth 标记,统一跳过鉴权
|
||||||
|
if (config.meta?.ignoreAuth)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 需要认证的请求:校验 token 并注入 Authorization 头
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const token = userStore.getAccessToken()
|
||||||
|
if (!token) {
|
||||||
|
// 未登录:清空残留状态并引导到登录页(防抖,避免并发请求重复重定向),并终止请求
|
||||||
|
if (!redirectingToLogin) {
|
||||||
|
redirectingToLogin = true
|
||||||
|
handleAuthExpired(userStore)
|
||||||
|
setTimeout(() => {
|
||||||
|
redirectingToLogin = false
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
throw new Error('[请求错误]:未登录')
|
||||||
|
}
|
||||||
|
method.config.headers.Authorization = `Bearer ${token}`
|
||||||
|
},
|
||||||
|
|
||||||
|
responded: {
|
||||||
|
// 成功响应拦截:统一处理 HTTP 状态码与业务码,错误统一提示并抛出
|
||||||
|
onSuccess: async (response, method) => {
|
||||||
|
const { config } = method
|
||||||
|
if (config.requestType === 'upload' || config.requestType === 'download')
|
||||||
|
return response
|
||||||
|
|
||||||
|
// 普通请求:uniapp adapter 响应结构为 { statusCode, data }(上传/下载已提前返回,此处必为普通请求)
|
||||||
|
const { statusCode, data: rawData } = response as unknown as UniappNormalResponse
|
||||||
|
|
||||||
|
// 401:uniapp adapter 对 401 也走成功回调,需在此手动刷新 + 重放
|
||||||
|
if (statusCode === HttpStatus.UNAUTHORIZED)
|
||||||
|
return handleUnauthorized(method, rawData)
|
||||||
|
if (statusCode !== 200)
|
||||||
|
return throwApiError(getErrorMessage(statusCode, rawData), config)
|
||||||
|
|
||||||
|
const { code, message, msg, data } = rawData
|
||||||
|
if (code !== ResultEnum.SUCCESS) {
|
||||||
|
return throwApiError(msg || message || '请求错误', config)
|
||||||
|
}
|
||||||
|
// 处理成功响应,返回业务数据
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
// 错误响应拦截:覆盖网络/超时等未在 onSuccess 提示过的错误
|
||||||
|
onError: (error, method) => {
|
||||||
|
const { config } = method
|
||||||
|
if (!(error as Error & { __toasted?: boolean })?.__toasted && !config.meta?.silent) {
|
||||||
|
if (error.name === 'NetworkError')
|
||||||
|
notifyError('网络错误,请检查您的网络连接')
|
||||||
|
else if (error.name === 'TimeoutError')
|
||||||
|
notifyError('请求超时,请重试')
|
||||||
|
else
|
||||||
|
notifyError((error as Error)?.message || '请求错误')
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 统一错误提示:非组件上下文使用全局 toast(wot-ui wd-toast,挂载于 App.ku.vue) */
|
||||||
|
function notifyError(message: string) {
|
||||||
|
useGlobalToast().error(message || '请求错误')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抛出接口错误并统一全局提示
|
||||||
|
* 页面无需再单独提取/展示错误信息;meta.silent 可用于内联处理场景(如聊天内联展示)
|
||||||
|
*/
|
||||||
|
function throwApiError(message: string, config: { meta?: Record<string, any> }) {
|
||||||
|
const err = new Error(message) as Error & { __toasted?: boolean }
|
||||||
|
if (!config.meta?.silent) {
|
||||||
|
err.__toasted = true
|
||||||
|
notifyError(message)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
export const http = alovaInstance
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 导出类型
|
||||||
|
import type { CustomRequestOptions, IResponse } from './types'
|
||||||
|
// 导出请求适配器
|
||||||
|
import { http as alovaHttp } from './adapters/alova'
|
||||||
|
|
||||||
|
// 导出默认请求实例
|
||||||
|
export const http = alovaHttp
|
||||||
|
|
||||||
|
// 导出所有类型
|
||||||
|
export type { CustomRequestOptions, IResponse }
|
||||||
|
|
||||||
|
// 导出请求适配器
|
||||||
|
export { alovaHttp }
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* API 结果枚举 - 与后端 ResultEnum 对应
|
||||||
|
*
|
||||||
|
* 仅包含业务状态码,HTTP 状态码请使用 {@link HttpStatus}
|
||||||
|
*/
|
||||||
|
export enum ResultEnum {
|
||||||
|
/** 成功 */
|
||||||
|
SUCCESS = 0,
|
||||||
|
/** 错误 */
|
||||||
|
ERROR = 1,
|
||||||
|
/** 异常 */
|
||||||
|
EXCEPTION = -1,
|
||||||
|
/** 未授权访问 */
|
||||||
|
UNAUTHORIZED = 10403,
|
||||||
|
/** 令牌已过期 */
|
||||||
|
TOKEN_EXPIRED = 10401,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 状态码枚举(用于 error.response.status 判断)
|
||||||
|
*/
|
||||||
|
export enum HttpStatus {
|
||||||
|
SUCCESS = 200,
|
||||||
|
BAD_REQUEST = 400,
|
||||||
|
UNAUTHORIZED = 401,
|
||||||
|
FORBIDDEN = 403,
|
||||||
|
NOT_FOUND = 404,
|
||||||
|
METHOD_NOT_ALLOWED = 405,
|
||||||
|
REQUEST_TIMEOUT = 408,
|
||||||
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
NOT_IMPLEMENTED = 501,
|
||||||
|
BAD_GATEWAY = 502,
|
||||||
|
SERVICE_UNAVAILABLE = 503,
|
||||||
|
GATEWAY_TIMEOUT = 504,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容类型枚举
|
||||||
|
*/
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
JSON = 'application/json;charset=UTF-8',
|
||||||
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据状态码,生成对应的错误信息
|
||||||
|
* @param status 状态码
|
||||||
|
* @returns 错误信息
|
||||||
|
*/
|
||||||
|
export function ShowMessage(status: number | string): string {
|
||||||
|
let message: string
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
message = '请求错误(400)'
|
||||||
|
break
|
||||||
|
case 401:
|
||||||
|
message = '未授权,请重新登录(401)'
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
message = '拒绝访问(403)'
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
message = '请求出错(404)'
|
||||||
|
break
|
||||||
|
case 408:
|
||||||
|
message = '请求超时(408)'
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
message = '服务器错误(500)'
|
||||||
|
break
|
||||||
|
case 501:
|
||||||
|
message = '服务未实现(501)'
|
||||||
|
break
|
||||||
|
case 502:
|
||||||
|
message = '网络错误(502)'
|
||||||
|
break
|
||||||
|
case 503:
|
||||||
|
message = '服务不可用(503)'
|
||||||
|
break
|
||||||
|
case 504:
|
||||||
|
message = '网络超时(504)'
|
||||||
|
break
|
||||||
|
case 505:
|
||||||
|
message = 'HTTP版本不受支持(505)'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
message = `连接出错(${status})!`
|
||||||
|
}
|
||||||
|
return `${message},请检查网络或联系管理员!`
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 将对象序列化为URL查询字符串,用于替代第三方的 qs 库,节省宝贵的体积
|
||||||
|
* 支持基本类型值和数组,不支持嵌套对象
|
||||||
|
* @param obj 要序列化的对象
|
||||||
|
* @returns 序列化后的查询字符串
|
||||||
|
*/
|
||||||
|
export function stringifyQuery(obj: Record<string, any>): string {
|
||||||
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return Object.entries(obj)
|
||||||
|
.filter(([, value]) => value !== undefined && value !== null)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
// 对键进行编码
|
||||||
|
const encodedKey = encodeURIComponent(key)
|
||||||
|
|
||||||
|
// 处理数组类型
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
.filter(item => item !== undefined && item !== null)
|
||||||
|
.map(item => `${encodedKey}=${encodeURIComponent(item)}`)
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理基本类型
|
||||||
|
return `${encodedKey}=${encodeURIComponent(value)}`
|
||||||
|
})
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 在 uniapp 的 RequestOptions 和 UploadFileOption 基础上,添加自定义参数
|
||||||
|
*/
|
||||||
|
export type CustomRequestOptions = UniApp.RequestOptions & {
|
||||||
|
query?: Record<string, any>
|
||||||
|
/** 出错时是否隐藏错误提示 */
|
||||||
|
hideErrorToast?: boolean
|
||||||
|
} & UniApp.UploadFileOption // 添加uni.uploadFile参数类型
|
||||||
|
|
||||||
|
/** 主要提供给 openapi-ts-request 生成的代码使用 */
|
||||||
|
export type CustomRequestOptions_ = Omit<CustomRequestOptions, 'url'>
|
||||||
|
|
||||||
|
export interface HttpRequestResult<T> {
|
||||||
|
promise: Promise<T>
|
||||||
|
requestTask: UniApp.RequestTask
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用响应格式(兼容 msg + message 字段)
|
||||||
|
export interface IResponse<T = any> {
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
msg: string
|
||||||
|
message?: string
|
||||||
|
status_code: number
|
||||||
|
success: boolean
|
||||||
|
[key: string]: any // 允许额外属性
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页请求参数
|
||||||
|
export interface PageParams {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uniapp adapter 普通请求(非上传/下载)的响应结构
|
||||||
|
* 对应 UniNamespace.RequestSuccessCallbackResult
|
||||||
|
*/
|
||||||
|
export interface UniappNormalResponse {
|
||||||
|
statusCode: number
|
||||||
|
data: IResponse
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const { enabled: watermarkEnabled, content: watermarkContent, color: watermarkColor } = useWatermark()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
<wd-watermark
|
||||||
|
v-if="watermarkEnabled"
|
||||||
|
:content="watermarkContent"
|
||||||
|
:color="watermarkColor"
|
||||||
|
:width="130"
|
||||||
|
:height="140"
|
||||||
|
:opacity="0.4"
|
||||||
|
layout="grid"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { activeTabbar, getTabbarItemValue, setTabbarItemActive, tabbarList } = useTabbar()
|
||||||
|
|
||||||
|
const { enabled: watermarkEnabled, content: watermarkContent, color: watermarkColor } = useWatermark()
|
||||||
|
|
||||||
|
function handleTabbarChange({ value }: { value: string }) {
|
||||||
|
setTabbarItemActive(value)
|
||||||
|
router.pushTab({ name: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// #ifdef APP
|
||||||
|
uni.hideTabBar()
|
||||||
|
// #endif
|
||||||
|
nextTick(() => {
|
||||||
|
if (route.name && route.name !== activeTabbar.value.name) {
|
||||||
|
setTabbarItemActive(route.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
<wd-gap
|
||||||
|
safe-area-bottom
|
||||||
|
height="var(--wot-tabbar-height, 50px)"
|
||||||
|
custom-class="tabbar-gap"
|
||||||
|
/>
|
||||||
|
<wd-tabbar
|
||||||
|
:model-value="activeTabbar.name" bordered safe-area-inset-bottom fixed
|
||||||
|
@change="handleTabbarChange"
|
||||||
|
>
|
||||||
|
<wd-tabbar-item
|
||||||
|
v-for="(item, index) in tabbarList" :key="index" :name="item.name"
|
||||||
|
:value="getTabbarItemValue(item.name)" :title="t(item.titleKey)" :icon="item.icon"
|
||||||
|
/>
|
||||||
|
</wd-tabbar>
|
||||||
|
<wd-watermark
|
||||||
|
v-if="watermarkEnabled"
|
||||||
|
:content="watermarkContent"
|
||||||
|
:color="watermarkColor"
|
||||||
|
:width="130"
|
||||||
|
:height="140"
|
||||||
|
:opacity="0.4"
|
||||||
|
layout="grid"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import { LANG_KEY } from '@/constants'
|
||||||
|
import { Storage } from '@/utils/storage'
|
||||||
|
import enUS from './langs/en.json'
|
||||||
|
import zhCN from './langs/zh.json'
|
||||||
|
|
||||||
|
/** 支持的语言列表 */
|
||||||
|
export const SUPPORT_LOCALES = ['zh-CN', 'en-US'] as const
|
||||||
|
export type SupportLocale = typeof SUPPORT_LOCALES[number]
|
||||||
|
|
||||||
|
/** 默认语言 */
|
||||||
|
export const DEFAULT_LOCALE: SupportLocale = 'zh-CN'
|
||||||
|
|
||||||
|
/** 从本地存储读取语言偏好(非法值回退默认语言) */
|
||||||
|
function resolveLocale(): SupportLocale {
|
||||||
|
const saved = Storage.get<string>(LANG_KEY)
|
||||||
|
return (SUPPORT_LOCALES as readonly string[]).includes(saved || '') ? saved as SupportLocale : DEFAULT_LOCALE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vue-i18n 实例
|
||||||
|
* - legacy: false 使用 Composition API 模式
|
||||||
|
* - globalInjection: true 全局注入 $t/$d 等,模板可直接使用 $t(否则会报 Property "$t" is not defined)
|
||||||
|
* - 语言偏好持久化于 LANG_KEY,切换后由设置页写入
|
||||||
|
*/
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: resolveLocale(),
|
||||||
|
legacy: false,
|
||||||
|
globalInjection: true,
|
||||||
|
fallbackLocale: DEFAULT_LOCALE,
|
||||||
|
messages: {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en-US': enUS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@@ -0,0 +1,377 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"title": "Tip",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"send": "Send",
|
||||||
|
"delete": "Delete",
|
||||||
|
"new": "New",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"loadFailed": "Failed to load",
|
||||||
|
"operationFailed": "Operation failed",
|
||||||
|
"deleteSuccess": "Deleted",
|
||||||
|
"deleteFailed": "Delete failed",
|
||||||
|
"deleteConfirm": "Confirm delete?",
|
||||||
|
"updateSuccess": "Updated",
|
||||||
|
"createSuccess": "Created",
|
||||||
|
"saveSuccess": "Saved",
|
||||||
|
"noMore": "No more",
|
||||||
|
"placeholder": "Please enter",
|
||||||
|
"selectPlaceholder": "Please select",
|
||||||
|
"all": "All",
|
||||||
|
"admin": "Admin",
|
||||||
|
"docs": "Documentation",
|
||||||
|
"github": "GitHub Repository",
|
||||||
|
"aboutUs": "About Us",
|
||||||
|
"settings": "Settings",
|
||||||
|
"profile": "Profile",
|
||||||
|
"field": {
|
||||||
|
"status": "Status",
|
||||||
|
"remark": "Remark"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"usernamePlaceholder": "Enter username",
|
||||||
|
"usernameRequired": "Please enter a username",
|
||||||
|
"usernameFormat": "Username must start with a letter, 3-32 characters, only letters, digits, _ . -",
|
||||||
|
"passwordPlaceholder": "Enter password",
|
||||||
|
"passwordRequired": "Please enter a password",
|
||||||
|
"passwordLength": "Password must be at least 6 characters",
|
||||||
|
"newPasswordPlaceholder": "Enter new password",
|
||||||
|
"newPasswordRequired": "Please enter a new password",
|
||||||
|
"confirmPlaceholder": "Confirm password",
|
||||||
|
"confirmRequired": "Please confirm the password",
|
||||||
|
"confirmNewPlaceholder": "Confirm new password",
|
||||||
|
"confirmNewRequired": "Please confirm the new password",
|
||||||
|
"mismatch": "The two passwords do not match",
|
||||||
|
"nicknamePlaceholder": "Please enter a nickname",
|
||||||
|
"nicknameRequired": "Please enter a nickname"
|
||||||
|
},
|
||||||
|
"shareDefaultTitle": "FastapiAdmin - Efficient enterprise management mobile platform",
|
||||||
|
"subscribeTitle": "Enable Message Notifications",
|
||||||
|
"subscribeContent": "You have disabled message notifications. Go to settings to enable?",
|
||||||
|
"subscribeGoSetting": "Go to Settings",
|
||||||
|
"tab": { "home": "Home", "work": "Workbench", "mine": "Me" },
|
||||||
|
"nav": {
|
||||||
|
"notices": "Notices",
|
||||||
|
"tickets": "Tickets",
|
||||||
|
"aiChat": "AI Chat",
|
||||||
|
"aiModels": "AI Models"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"draft": "Draft",
|
||||||
|
"published": "Published",
|
||||||
|
"archived": "Archived",
|
||||||
|
"active": "Active",
|
||||||
|
"success": "Success",
|
||||||
|
"failed": "Failed",
|
||||||
|
"pending": "Pending",
|
||||||
|
"processing": "Processing",
|
||||||
|
"completed": "Completed",
|
||||||
|
"closed": "Closed",
|
||||||
|
"deprecated": "Deprecated",
|
||||||
|
"expired": "Expired",
|
||||||
|
"unknown": "Unknown"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"suggestion": "Suggestion",
|
||||||
|
"bug": "Bug",
|
||||||
|
"optimize": "Optimization",
|
||||||
|
"other": "Other"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"navTitle": "Login",
|
||||||
|
"cardTitle": "Account Login",
|
||||||
|
"remember": "Remember me",
|
||||||
|
"forgot": "Forgot password?",
|
||||||
|
"submit": "Sign In",
|
||||||
|
"submitting": "Signing in...",
|
||||||
|
"thirdParty": "Third-party Login",
|
||||||
|
"wechat": "WeChat",
|
||||||
|
"github": "GitHub",
|
||||||
|
"gitee": "Gitee",
|
||||||
|
"qq": "QQ",
|
||||||
|
"noAccount": "Don't have an account?",
|
||||||
|
"toRegister": "Register",
|
||||||
|
"sliderText": "Slide to verify",
|
||||||
|
"sliderSuccessText": "Verified",
|
||||||
|
"sliderRequired": "Please complete the slider verification first",
|
||||||
|
"captchaExpired": "Captcha expired, please refresh",
|
||||||
|
"sliderFailed": "Verification failed, please retry",
|
||||||
|
"usernameLength": "Username must be 3-20 characters",
|
||||||
|
"passwordLength": "Password must be 6-20 characters",
|
||||||
|
"oauthCopyTip": "Link copied, please open in browser",
|
||||||
|
"oauthFailed": "OAuth login failed",
|
||||||
|
"loginFailed": "Login failed, please check your credentials",
|
||||||
|
"wxLogin": "WeChat One-click Login",
|
||||||
|
"phoneLogin": "Quick Phone Login",
|
||||||
|
"orAccount": "or login with account",
|
||||||
|
"wxLoginFailed": "WeChat login failed, please retry",
|
||||||
|
"phoneAuthCancel": "Phone number authorization cancelled",
|
||||||
|
"phoneLoginFailed": "Phone number login failed, please retry",
|
||||||
|
"brandSubtitle": "Efficient enterprise management mobile platform",
|
||||||
|
"wxOnly": "This feature is only available in the WeChat Mini Program",
|
||||||
|
"wxAuthFailed": "WeChat login authorization failed",
|
||||||
|
"phoneCodeFailed": "Failed to get phone number authorization code"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"navTitle": "Register",
|
||||||
|
"title": "Create Account",
|
||||||
|
"submit": "Register",
|
||||||
|
"submitting": "Registering...",
|
||||||
|
"hasAccount": "Already have an account?",
|
||||||
|
"toLogin": "Login",
|
||||||
|
"agree": "I have read and agree to the",
|
||||||
|
"userAgreement": "User Agreement",
|
||||||
|
"agreeRequired": "Please read and agree to the user agreement first",
|
||||||
|
"success": "Registration successful"
|
||||||
|
},
|
||||||
|
"forget": {
|
||||||
|
"navTitle": "Forgot Password",
|
||||||
|
"title": "Forgot Password",
|
||||||
|
"submit": "Reset Password",
|
||||||
|
"submitting": "Submitting...",
|
||||||
|
"toLogin": "Back to Login",
|
||||||
|
"success": "Password reset, please log in again"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"navTitle": "Settings",
|
||||||
|
"basic": "Basic",
|
||||||
|
"darkMode": "Dark Mode",
|
||||||
|
"followSystem": "Follow System",
|
||||||
|
"themeColor": "Theme Color",
|
||||||
|
"watermark": "Watermark",
|
||||||
|
"links": "Links",
|
||||||
|
"general": "General",
|
||||||
|
"version": "Version",
|
||||||
|
"clearCache": "Clear Cache",
|
||||||
|
"clearCacheMsg": "Clear local cache? You will be logged out.",
|
||||||
|
"clearCacheConfirm": "Clear",
|
||||||
|
"language": "Language",
|
||||||
|
"copied": "{url} copied to clipboard",
|
||||||
|
"brandDesc": "Providing secure, efficient and scalable management solutions",
|
||||||
|
"langZh": "简体中文",
|
||||||
|
"langEn": "English"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"navTitle": "About",
|
||||||
|
"currentVersion": "Version v{version}",
|
||||||
|
"moreInfo": "More Info",
|
||||||
|
"copied": "Link copied to clipboard"
|
||||||
|
},
|
||||||
|
"mine": {
|
||||||
|
"navTitle": "Me",
|
||||||
|
"changePassword": "Change Password",
|
||||||
|
"logout": "Logout",
|
||||||
|
"logoutMsg": "Are you sure you want to log out?",
|
||||||
|
"logoutConfirm": "Logout",
|
||||||
|
"pendingTickets": "Pending",
|
||||||
|
"processingTickets": "Processing",
|
||||||
|
"doneTickets": "Done",
|
||||||
|
"shareTitle": "{name} invites you to join FastapiAdmin",
|
||||||
|
"roleFallback": "Super Admin · Tech Dept"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"navTitle": "Profile",
|
||||||
|
"avatar": "Avatar",
|
||||||
|
"nickname": "Nickname",
|
||||||
|
"gender": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"username": "Username",
|
||||||
|
"mobile": "Mobile",
|
||||||
|
"email": "Email",
|
||||||
|
"dept": "Department",
|
||||||
|
"roles": "Roles",
|
||||||
|
"editNickname": "Edit Nickname",
|
||||||
|
"selectGender": "Select Gender",
|
||||||
|
"notSet": "Not set",
|
||||||
|
"notBound": "Not bound",
|
||||||
|
"uploadFailed": "Avatar upload failed",
|
||||||
|
"nicknameRequired": "Please enter a nickname"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"password": "Account Password",
|
||||||
|
"passwordTip": "Changing your password regularly helps protect your account",
|
||||||
|
"modify": "Change",
|
||||||
|
"oldPassword": "Current Password",
|
||||||
|
"newPassword": "New Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"oldPlaceholder": "Enter current password",
|
||||||
|
"submit": "Submit",
|
||||||
|
"success": "Password changed",
|
||||||
|
"oldRequired": "Please enter current password",
|
||||||
|
"newLength": "New password must be at least 6 characters"
|
||||||
|
},
|
||||||
|
"tickets": {
|
||||||
|
"title": "Ticket Management",
|
||||||
|
"searchPlaceholder": "Search ticket title",
|
||||||
|
"typeFilter": "Type",
|
||||||
|
"allType": "All Types",
|
||||||
|
"emptyWithFilter": "No matching tickets",
|
||||||
|
"empty": "No tickets",
|
||||||
|
"titleRequired": "Please enter a ticket title",
|
||||||
|
"formTitle": "Ticket Title",
|
||||||
|
"content": "Content",
|
||||||
|
"type": "Type",
|
||||||
|
"create": "New"
|
||||||
|
},
|
||||||
|
"notices": {
|
||||||
|
"title": "Notices",
|
||||||
|
"searchPlaceholder": "Search notice title",
|
||||||
|
"count": "{count} in total",
|
||||||
|
"emptyWithFilter": "No matching notices",
|
||||||
|
"empty": "No notices",
|
||||||
|
"createTitle": "New Notice",
|
||||||
|
"editTitle": "Edit Notice",
|
||||||
|
"detailFailed": "Failed to load detail",
|
||||||
|
"formTitle": "Notice Title",
|
||||||
|
"content": "Notice Content",
|
||||||
|
"contentPlaceholder": "Please enter content",
|
||||||
|
"type": "Notice Type",
|
||||||
|
"notice": "Notice",
|
||||||
|
"announce": "Announcement"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "AI Assistant",
|
||||||
|
"shareTitle": "FastapiAdmin AI Assistant - Smart Chat, Anytime",
|
||||||
|
"sessionList": "Sessions",
|
||||||
|
"new": "New",
|
||||||
|
"newSession": "New Chat",
|
||||||
|
"newSessionSuccess": "New session created",
|
||||||
|
"createFailed": "Failed to create session",
|
||||||
|
"loadSessionsFailed": "Failed to load sessions",
|
||||||
|
"loadMessagesFailed": "Failed to load messages",
|
||||||
|
"deleteConfirm": "Delete this session?",
|
||||||
|
"deleted": "Deleted",
|
||||||
|
"deleteFailed": "Delete failed",
|
||||||
|
"sessionId": "Session {id}",
|
||||||
|
"noReply": "(No reply)",
|
||||||
|
"requestFailed": "Request failed, please retry later.",
|
||||||
|
"welcome": "Hi! I'm the FastapiAdmin AI Assistant. How can I help you?",
|
||||||
|
"empty": "No sessions, tap + to create one",
|
||||||
|
"loadMore": "Load earlier messages ({count})",
|
||||||
|
"inputPlaceholder": "Type a message...",
|
||||||
|
"stop": "Stop",
|
||||||
|
"wsConnectFailed": "WebSocket connection failed",
|
||||||
|
"wsDisconnected": "Connection lost, please retry",
|
||||||
|
"wsSendFailed": "Connection failed, please retry later"
|
||||||
|
},
|
||||||
|
"aiModels": {
|
||||||
|
"title": "AI Model Config",
|
||||||
|
"createTitle": "New Model Config",
|
||||||
|
"editTitle": "Edit Model Config",
|
||||||
|
"loadFailed": "Failed to load models",
|
||||||
|
"nameRequired": "Please enter a config name",
|
||||||
|
"baseUrlRequired": "Please enter the API base URL",
|
||||||
|
"modelIdRequired": "Please enter a model ID",
|
||||||
|
"saveFailed": "Save failed",
|
||||||
|
"deleteConfirm": "Delete this model config?",
|
||||||
|
"activateConfirm": "Switch to this model config?",
|
||||||
|
"activated": "Model activated",
|
||||||
|
"activateFailed": "Switch failed",
|
||||||
|
"empty": "No model configs",
|
||||||
|
"groupTitle": "Model Config",
|
||||||
|
"count": "{count} in total",
|
||||||
|
"unnamed": "Unnamed",
|
||||||
|
"temperature": "Temp",
|
||||||
|
"inUse": "In Use",
|
||||||
|
"activate": "Activate",
|
||||||
|
"basic": "Basic",
|
||||||
|
"name": "Config Name",
|
||||||
|
"namePlaceholder": "e.g. Production GPT-4",
|
||||||
|
"modelId": "Model ID",
|
||||||
|
"modelIdPlaceholder": "e.g. gpt-4o / deepseek-chat",
|
||||||
|
"baseUrl": "API Base URL",
|
||||||
|
"advanced": "Security & Params",
|
||||||
|
"temperatureLabel": "Temperature (0-2)",
|
||||||
|
"update": "Update",
|
||||||
|
"create": "Create"
|
||||||
|
},
|
||||||
|
"ticketDetail": {
|
||||||
|
"title": "Ticket Detail",
|
||||||
|
"shareTitle": "Ticket: {title}",
|
||||||
|
"loadFailed": "Failed to load ticket detail",
|
||||||
|
"commentRequired": "Please enter a comment",
|
||||||
|
"commentSuccess": "Commented",
|
||||||
|
"commentFailed": "Comment failed",
|
||||||
|
"unnamed": "Untitled Ticket",
|
||||||
|
"progress": "Progress",
|
||||||
|
"createdAt": "Created At",
|
||||||
|
"handler": "Handler",
|
||||||
|
"unassigned": "Unassigned",
|
||||||
|
"updatedAt": "Updated At",
|
||||||
|
"content": "Ticket Content",
|
||||||
|
"attachments": "Attachments",
|
||||||
|
"replies": "Replies",
|
||||||
|
"comments": "Comments ({count})",
|
||||||
|
"emptyComments": "No comments yet",
|
||||||
|
"anonymous": "Anonymous",
|
||||||
|
"loadMore": "Load more",
|
||||||
|
"notFound": "Ticket not found or deleted",
|
||||||
|
"commentPlaceholder": "Write a comment..."
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"navTitle": "Workbench",
|
||||||
|
"shareTitle": "{greeting}! {name} invites you to use FastapiAdmin",
|
||||||
|
"welcomeDefault": "Welcome to FastapiAdmin",
|
||||||
|
"today": "Today",
|
||||||
|
"welcomeBack": "Welcome back, have a productive day",
|
||||||
|
"overview": "Overview",
|
||||||
|
"todo": "To-do",
|
||||||
|
"pendingCount": "{count} pending tickets",
|
||||||
|
"allDone": "All tickets handled",
|
||||||
|
"todoSubtitle": "Tap to handle now to keep business on track",
|
||||||
|
"doneSubtitle": "Great job, keep it up",
|
||||||
|
"goHandle": "Handle",
|
||||||
|
"viewTickets": "View Tickets",
|
||||||
|
"ticketDesc": "Tap to view ticket list",
|
||||||
|
"data": "Data",
|
||||||
|
"dataTitle": "Overview",
|
||||||
|
"dataSubtitle": "System status & trend analysis",
|
||||||
|
"dataDesc": "Users {total} · Online {online} · Logins today {today}",
|
||||||
|
"viewDetail": "View Details",
|
||||||
|
"loginCount": "Logins",
|
||||||
|
"visitor": "Unique Visitors",
|
||||||
|
"newUser": "New Users",
|
||||||
|
"night": "It's late",
|
||||||
|
"morning": "Good morning",
|
||||||
|
"forenoon": "Good morning",
|
||||||
|
"noon": "Good noon",
|
||||||
|
"afternoon": "Good afternoon",
|
||||||
|
"evening": "Good evening",
|
||||||
|
"opsOverview": "Operation Overview",
|
||||||
|
"registered": "Registered Users",
|
||||||
|
"weekNew": "{count} this week",
|
||||||
|
"online": "Online Users",
|
||||||
|
"currentOnline": "Currently online",
|
||||||
|
"todayLogin": "Logins Today",
|
||||||
|
"independentUser": "{count} unique users",
|
||||||
|
"sysMonitor": "System Monitor",
|
||||||
|
"loginTrend": "Login Trend (7 days)",
|
||||||
|
"latestNotice": "Latest Notices"
|
||||||
|
},
|
||||||
|
"work": {
|
||||||
|
"navTitle": "Workbench",
|
||||||
|
"shareTitle": "FastapiAdmin Workbench - Notices / Tickets / AI",
|
||||||
|
"businessCenter": "Business Center",
|
||||||
|
"devTools": "Developer Tools",
|
||||||
|
"nameFallback": "FastapiAdmin",
|
||||||
|
"roleFallback": "Super Admin",
|
||||||
|
"searchPlaceholder": "Search modules",
|
||||||
|
"emptyTip": "No matching module, try another keyword"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"colors": {
|
||||||
|
"blue": "Default Blue",
|
||||||
|
"orange": "Vibrant Orange",
|
||||||
|
"green": "Mint Green",
|
||||||
|
"pink": "Sakura Pink",
|
||||||
|
"purple": "Violet",
|
||||||
|
"red": "Vermilion Red"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,377 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"title": "提示",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"send": "发送",
|
||||||
|
"delete": "删除",
|
||||||
|
"new": "新建",
|
||||||
|
"confirm": "确定",
|
||||||
|
"loadFailed": "加载失败",
|
||||||
|
"operationFailed": "操作失败",
|
||||||
|
"deleteSuccess": "删除成功",
|
||||||
|
"deleteFailed": "删除失败",
|
||||||
|
"deleteConfirm": "确认删除?",
|
||||||
|
"updateSuccess": "更新成功",
|
||||||
|
"createSuccess": "创建成功",
|
||||||
|
"saveSuccess": "保存成功",
|
||||||
|
"noMore": "已经到底啦",
|
||||||
|
"placeholder": "请输入",
|
||||||
|
"selectPlaceholder": "请选择",
|
||||||
|
"all": "全部",
|
||||||
|
"admin": "管理员",
|
||||||
|
"docs": "使用文档",
|
||||||
|
"github": "GitHub 仓库",
|
||||||
|
"aboutUs": "关于我们",
|
||||||
|
"settings": "设置",
|
||||||
|
"profile": "个人资料",
|
||||||
|
"field": {
|
||||||
|
"status": "状态",
|
||||||
|
"remark": "备注"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"usernamePlaceholder": "请输入用户名",
|
||||||
|
"usernameRequired": "请输入用户名",
|
||||||
|
"usernameFormat": "账号需以字母开头,3-32 位,仅允许字母、数字、_ . -",
|
||||||
|
"passwordPlaceholder": "请输入密码",
|
||||||
|
"passwordRequired": "请输入密码",
|
||||||
|
"passwordLength": "密码长度不能少于 6 位",
|
||||||
|
"newPasswordPlaceholder": "请输入新密码",
|
||||||
|
"newPasswordRequired": "请输入新密码",
|
||||||
|
"confirmPlaceholder": "请确认密码",
|
||||||
|
"confirmRequired": "请确认密码",
|
||||||
|
"confirmNewPlaceholder": "请确认新密码",
|
||||||
|
"confirmNewRequired": "请确认新密码",
|
||||||
|
"mismatch": "两次输入的密码不一致",
|
||||||
|
"nicknamePlaceholder": "请输入昵称",
|
||||||
|
"nicknameRequired": "请输入昵称"
|
||||||
|
},
|
||||||
|
"shareDefaultTitle": "FastapiAdmin - 高效的企业管理移动平台",
|
||||||
|
"subscribeTitle": "开启消息通知",
|
||||||
|
"subscribeContent": "您已关闭消息通知,是否前往设置开启?",
|
||||||
|
"subscribeGoSetting": "去设置",
|
||||||
|
"tab": { "home": "首页", "work": "工作台", "mine": "我的" },
|
||||||
|
"nav": {
|
||||||
|
"notices": "通知公告",
|
||||||
|
"tickets": "工单管理",
|
||||||
|
"aiChat": "AI 助手",
|
||||||
|
"aiModels": "AI 模型"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "启用",
|
||||||
|
"disabled": "禁用",
|
||||||
|
"draft": "草稿",
|
||||||
|
"published": "已发布",
|
||||||
|
"archived": "已归档",
|
||||||
|
"active": "活跃",
|
||||||
|
"success": "成功",
|
||||||
|
"failed": "失败",
|
||||||
|
"pending": "待处理",
|
||||||
|
"processing": "处理中",
|
||||||
|
"completed": "已完成",
|
||||||
|
"closed": "已关闭",
|
||||||
|
"deprecated": "已废弃",
|
||||||
|
"expired": "过期",
|
||||||
|
"unknown": "未知"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"suggestion": "建议",
|
||||||
|
"bug": "缺陷",
|
||||||
|
"optimize": "优化",
|
||||||
|
"other": "其他"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"navTitle": "登录",
|
||||||
|
"cardTitle": "账号登录",
|
||||||
|
"remember": "记住密码",
|
||||||
|
"forgot": "忘记密码?",
|
||||||
|
"submit": "登 录",
|
||||||
|
"submitting": "登录中...",
|
||||||
|
"thirdParty": "第三方登录",
|
||||||
|
"wechat": "微信",
|
||||||
|
"github": "GitHub",
|
||||||
|
"gitee": "Gitee",
|
||||||
|
"qq": "QQ",
|
||||||
|
"noAccount": "没有账号?",
|
||||||
|
"toRegister": "立即注册",
|
||||||
|
"sliderText": "向右滑动滑块验证",
|
||||||
|
"sliderSuccessText": "验证成功",
|
||||||
|
"sliderRequired": "请先完成滑块验证",
|
||||||
|
"captchaExpired": "验证码已过期,请刷新",
|
||||||
|
"sliderFailed": "验证失败,请重试",
|
||||||
|
"usernameLength": "用户名长度为 3-20 个字符",
|
||||||
|
"passwordLength": "密码长度为 6-20 个字符",
|
||||||
|
"oauthCopyTip": "链接已复制,请在浏览器中打开",
|
||||||
|
"oauthFailed": "OAuth登录失败",
|
||||||
|
"loginFailed": "登录失败,请检查账号密码",
|
||||||
|
"wxLogin": "微信一键登录",
|
||||||
|
"phoneLogin": "手机号快速登录",
|
||||||
|
"orAccount": "或账号密码登录",
|
||||||
|
"wxLoginFailed": "微信登录失败,请重试",
|
||||||
|
"phoneAuthCancel": "已取消手机号授权",
|
||||||
|
"phoneLoginFailed": "手机号登录失败,请重试",
|
||||||
|
"brandSubtitle": "高效的企业管理移动平台",
|
||||||
|
"wxOnly": "请在微信小程序中使用此功能",
|
||||||
|
"wxAuthFailed": "微信登录授权失败",
|
||||||
|
"phoneCodeFailed": "手机号授权码获取失败"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"navTitle": "注册",
|
||||||
|
"title": "注册账号",
|
||||||
|
"submit": "注 册",
|
||||||
|
"submitting": "注册中...",
|
||||||
|
"hasAccount": "已有账号?",
|
||||||
|
"toLogin": "去登录",
|
||||||
|
"agree": "我已阅读并同意",
|
||||||
|
"userAgreement": "《用户协议》",
|
||||||
|
"agreeRequired": "请先阅读并同意用户协议",
|
||||||
|
"success": "注册成功"
|
||||||
|
},
|
||||||
|
"forget": {
|
||||||
|
"navTitle": "忘记密码",
|
||||||
|
"title": "忘记密码",
|
||||||
|
"submit": "重置密码",
|
||||||
|
"submitting": "提交中...",
|
||||||
|
"toLogin": "返回登录",
|
||||||
|
"success": "密码重置成功,请重新登录"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"navTitle": "设置",
|
||||||
|
"basic": "基础设置",
|
||||||
|
"darkMode": "暗黑模式",
|
||||||
|
"followSystem": "跟随系统",
|
||||||
|
"themeColor": "选择主题色",
|
||||||
|
"watermark": "水印",
|
||||||
|
"links": "相关链接",
|
||||||
|
"general": "通用",
|
||||||
|
"version": "版本信息",
|
||||||
|
"clearCache": "清除缓存",
|
||||||
|
"clearCacheMsg": "确认清除本地缓存吗?清除后将退出登录状态",
|
||||||
|
"clearCacheConfirm": "清除",
|
||||||
|
"language": "语言",
|
||||||
|
"copied": "{url} 已复制到剪贴板",
|
||||||
|
"brandDesc": "提供安全、高效、可扩展的管理解决方案",
|
||||||
|
"langZh": "简体中文",
|
||||||
|
"langEn": "English"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"navTitle": "关于",
|
||||||
|
"currentVersion": "当前版本 v{version}",
|
||||||
|
"moreInfo": "更多信息",
|
||||||
|
"copied": "链接已复制到剪贴板"
|
||||||
|
},
|
||||||
|
"mine": {
|
||||||
|
"navTitle": "我的",
|
||||||
|
"changePassword": "修改密码",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"logoutMsg": "确认退出登录吗?",
|
||||||
|
"logoutConfirm": "退出",
|
||||||
|
"pendingTickets": "待处理工单",
|
||||||
|
"processingTickets": "处理中工单",
|
||||||
|
"doneTickets": "已完成工单",
|
||||||
|
"shareTitle": "{name} 邀请你加入 FastapiAdmin",
|
||||||
|
"roleFallback": "超级管理员 · 技术部"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"navTitle": "个人资料",
|
||||||
|
"avatar": "头像",
|
||||||
|
"nickname": "昵称",
|
||||||
|
"gender": "性别",
|
||||||
|
"male": "男",
|
||||||
|
"female": "女",
|
||||||
|
"unknown": "未知",
|
||||||
|
"username": "用户名",
|
||||||
|
"mobile": "手机号",
|
||||||
|
"email": "邮箱",
|
||||||
|
"dept": "部门",
|
||||||
|
"roles": "角色",
|
||||||
|
"editNickname": "编辑昵称",
|
||||||
|
"selectGender": "选择性别",
|
||||||
|
"notSet": "未设置",
|
||||||
|
"notBound": "未绑定",
|
||||||
|
"uploadFailed": "头像上传失败",
|
||||||
|
"nicknameRequired": "请填写昵称"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"password": "账户密码",
|
||||||
|
"passwordTip": "定期修改密码有助于保护账户安全",
|
||||||
|
"modify": "修改",
|
||||||
|
"oldPassword": "原密码",
|
||||||
|
"newPassword": "新密码",
|
||||||
|
"confirmPassword": "确认密码",
|
||||||
|
"oldPlaceholder": "请输入原密码",
|
||||||
|
"submit": "提交",
|
||||||
|
"success": "密码修改成功",
|
||||||
|
"oldRequired": "请输入原密码",
|
||||||
|
"newLength": "新密码至少 6 位"
|
||||||
|
},
|
||||||
|
"tickets": {
|
||||||
|
"title": "工单管理",
|
||||||
|
"searchPlaceholder": "搜索工单标题",
|
||||||
|
"typeFilter": "类型",
|
||||||
|
"allType": "全部类型",
|
||||||
|
"emptyWithFilter": "未找到匹配工单",
|
||||||
|
"empty": "暂无工单",
|
||||||
|
"titleRequired": "请输入工单标题",
|
||||||
|
"formTitle": "工单标题",
|
||||||
|
"content": "内容",
|
||||||
|
"type": "类型",
|
||||||
|
"create": "新增"
|
||||||
|
},
|
||||||
|
"notices": {
|
||||||
|
"title": "通知公告",
|
||||||
|
"searchPlaceholder": "搜索公告标题",
|
||||||
|
"count": "共 {count} 条",
|
||||||
|
"emptyWithFilter": "未找到匹配公告",
|
||||||
|
"empty": "暂无公告",
|
||||||
|
"createTitle": "新增公告",
|
||||||
|
"editTitle": "编辑公告",
|
||||||
|
"detailFailed": "获取详情失败",
|
||||||
|
"formTitle": "公告标题",
|
||||||
|
"content": "公告内容",
|
||||||
|
"contentPlaceholder": "请输入内容",
|
||||||
|
"type": "公告类型",
|
||||||
|
"notice": "通知",
|
||||||
|
"announce": "公告"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "AI 助手",
|
||||||
|
"shareTitle": "FastapiAdmin AI 助手 - 智能对话,随时答疑",
|
||||||
|
"sessionList": "会话列表",
|
||||||
|
"new": "新建",
|
||||||
|
"newSession": "新对话",
|
||||||
|
"newSessionSuccess": "新会话已创建",
|
||||||
|
"createFailed": "创建会话失败",
|
||||||
|
"loadSessionsFailed": "加载会话列表失败",
|
||||||
|
"loadMessagesFailed": "加载会话消息失败",
|
||||||
|
"deleteConfirm": "确认删除该会话?",
|
||||||
|
"deleted": "已删除",
|
||||||
|
"deleteFailed": "删除失败",
|
||||||
|
"sessionId": "会话 {id}",
|
||||||
|
"noReply": "(无回复内容)",
|
||||||
|
"requestFailed": "请求失败,请稍后重试。",
|
||||||
|
"welcome": "你好!我是 FastapiAdmin AI 助手,有什么可以帮助你的?",
|
||||||
|
"empty": "暂无会话,点击右上角新建",
|
||||||
|
"loadMore": "加载更早消息({count} 条)",
|
||||||
|
"inputPlaceholder": "输入消息...",
|
||||||
|
"stop": "停止",
|
||||||
|
"wsConnectFailed": "WebSocket 连接失败",
|
||||||
|
"wsDisconnected": "连接已断开,请重试",
|
||||||
|
"wsSendFailed": "连接失败,请稍后重试"
|
||||||
|
},
|
||||||
|
"aiModels": {
|
||||||
|
"title": "AI 模型配置",
|
||||||
|
"createTitle": "新建模型配置",
|
||||||
|
"editTitle": "编辑模型配置",
|
||||||
|
"loadFailed": "加载模型列表失败",
|
||||||
|
"nameRequired": "请输入配置名称",
|
||||||
|
"baseUrlRequired": "请输入 API 地址",
|
||||||
|
"modelIdRequired": "请输入模型 ID",
|
||||||
|
"saveFailed": "保存失败",
|
||||||
|
"deleteConfirm": "确认删除此模型配置?",
|
||||||
|
"activateConfirm": "确认切换到此模型配置?",
|
||||||
|
"activated": "已切换激活模型",
|
||||||
|
"activateFailed": "切换失败",
|
||||||
|
"empty": "暂无模型配置",
|
||||||
|
"groupTitle": "模型配置",
|
||||||
|
"count": "共 {count} 条",
|
||||||
|
"unnamed": "未命名",
|
||||||
|
"temperature": "温度",
|
||||||
|
"inUse": "使用中",
|
||||||
|
"activate": "激活",
|
||||||
|
"basic": "基础配置",
|
||||||
|
"name": "配置名称",
|
||||||
|
"namePlaceholder": "如:生产环境 GPT-4",
|
||||||
|
"modelId": "模型 ID",
|
||||||
|
"modelIdPlaceholder": "如:gpt-4o / deepseek-chat",
|
||||||
|
"baseUrl": "API 地址",
|
||||||
|
"advanced": "安全与参数",
|
||||||
|
"temperatureLabel": "温度 (0-2)",
|
||||||
|
"update": "更新",
|
||||||
|
"create": "创建"
|
||||||
|
},
|
||||||
|
"ticketDetail": {
|
||||||
|
"title": "工单详情",
|
||||||
|
"shareTitle": "工单:{title}",
|
||||||
|
"loadFailed": "加载工单详情失败",
|
||||||
|
"commentRequired": "请输入评论内容",
|
||||||
|
"commentSuccess": "评论成功",
|
||||||
|
"commentFailed": "评论失败",
|
||||||
|
"unnamed": "未命名工单",
|
||||||
|
"progress": "处理进度",
|
||||||
|
"createdAt": "创建时间",
|
||||||
|
"handler": "处理人",
|
||||||
|
"unassigned": "未分配",
|
||||||
|
"updatedAt": "更新时间",
|
||||||
|
"content": "工单内容",
|
||||||
|
"attachments": "附件图片",
|
||||||
|
"replies": "处理回复",
|
||||||
|
"comments": "评论 ({count})",
|
||||||
|
"emptyComments": "暂无评论,快来抢沙发",
|
||||||
|
"anonymous": "匿名用户",
|
||||||
|
"loadMore": "加载更多",
|
||||||
|
"notFound": "工单不存在或已删除",
|
||||||
|
"commentPlaceholder": "写下你的评论..."
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"navTitle": "工作台概览",
|
||||||
|
"shareTitle": "{greeting}!{name} 邀你使用 FastapiAdmin",
|
||||||
|
"welcomeDefault": "欢迎使用 FastapiAdmin",
|
||||||
|
"today": "今日",
|
||||||
|
"welcomeBack": "欢迎回来,开启高效的一天",
|
||||||
|
"overview": "数据概览",
|
||||||
|
"todo": "待办",
|
||||||
|
"pendingCount": "{count} 个待处理工单",
|
||||||
|
"allDone": "工单已全部处理",
|
||||||
|
"todoSubtitle": "点击立即处理,避免影响业务",
|
||||||
|
"doneSubtitle": "处理及时,继续保持",
|
||||||
|
"goHandle": "去处理",
|
||||||
|
"viewTickets": "查看工单",
|
||||||
|
"ticketDesc": "点击进入工单列表",
|
||||||
|
"data": "数据",
|
||||||
|
"dataTitle": "数据概览",
|
||||||
|
"dataSubtitle": "系统运行状态与趋势分析",
|
||||||
|
"dataDesc": "总用户 {total} · 在线 {online} · 今日登录 {today}",
|
||||||
|
"viewDetail": "查看详情",
|
||||||
|
"loginCount": "登录次数",
|
||||||
|
"visitor": "独立访客",
|
||||||
|
"newUser": "新增用户",
|
||||||
|
"night": "夜深了",
|
||||||
|
"morning": "早上好",
|
||||||
|
"forenoon": "上午好",
|
||||||
|
"noon": "中午好",
|
||||||
|
"afternoon": "下午好",
|
||||||
|
"evening": "晚上好",
|
||||||
|
"opsOverview": "运营概览",
|
||||||
|
"registered": "注册用户",
|
||||||
|
"weekNew": "本周新增 {count}",
|
||||||
|
"online": "在线用户",
|
||||||
|
"currentOnline": "当前在线",
|
||||||
|
"todayLogin": "今日登录",
|
||||||
|
"independentUser": "{count} 独立用户",
|
||||||
|
"sysMonitor": "系统监控",
|
||||||
|
"loginTrend": "登录趋势 (近7天)",
|
||||||
|
"latestNotice": "最新公告"
|
||||||
|
},
|
||||||
|
"work": {
|
||||||
|
"navTitle": "工作台",
|
||||||
|
"shareTitle": "FastapiAdmin 工作台 - 通知公告 / 工单管理 / AI 助手",
|
||||||
|
"businessCenter": "业务中心",
|
||||||
|
"devTools": "开发者工具",
|
||||||
|
"nameFallback": "FastapiAdmin",
|
||||||
|
"roleFallback": "超级管理员",
|
||||||
|
"searchPlaceholder": "搜索模块功能",
|
||||||
|
"emptyTip": "未找到相关模块,换个关键词试试"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"colors": {
|
||||||
|
"blue": "默认蓝",
|
||||||
|
"orange": "活力橙",
|
||||||
|
"green": "薄荷绿",
|
||||||
|
"pink": "樱花粉",
|
||||||
|
"purple": "紫罗兰",
|
||||||
|
"red": "朱砂红"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import i18n from './locales'
|
||||||
|
import router from './router/index.js'
|
||||||
|
import 'uno.css'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(persistPlugin)
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(i18n)
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"name": "wot-starter",
|
||||||
|
"appid": "__UNI__1208592",
|
||||||
|
"description": "",
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"versionCode": "100",
|
||||||
|
"transformPx": false,
|
||||||
|
"app-plus": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"nvueStyleCompiler": "uni-app",
|
||||||
|
"compilerVersion": 3,
|
||||||
|
"splashscreen": {
|
||||||
|
"alwaysShowBeforeRender": true,
|
||||||
|
"waiting": true,
|
||||||
|
"autoclose": true,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"modules": {},
|
||||||
|
"distribute": {
|
||||||
|
"android": {
|
||||||
|
"permissions": [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ios": {},
|
||||||
|
"sdkConfigs": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app-harmony": {
|
||||||
|
"distribute": {}
|
||||||
|
},
|
||||||
|
"mp-harmony": {
|
||||||
|
"distribute": {}
|
||||||
|
},
|
||||||
|
"quickapp": {},
|
||||||
|
"mp-weixin": {
|
||||||
|
"appid": "wxf306aa91619d2e1c",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false
|
||||||
|
},
|
||||||
|
"usingComponents": true,
|
||||||
|
"optimization": {
|
||||||
|
"subPackages": true
|
||||||
|
},
|
||||||
|
"darkmode": true,
|
||||||
|
"themeLocation": "theme.json"
|
||||||
|
},
|
||||||
|
"mp-alipay": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"compileOptions": {
|
||||||
|
"globalObjectMode": "enable",
|
||||||
|
"treeShaking": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mp-baidu": {
|
||||||
|
"usingComponents": true
|
||||||
|
},
|
||||||
|
"mp-toutiao": {
|
||||||
|
"usingComponents": true
|
||||||
|
},
|
||||||
|
"uniStatistics": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"vueVersion": "3",
|
||||||
|
"h5": {
|
||||||
|
"darkmode": true,
|
||||||
|
"themeLocation": "theme.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"format": 2,
|
||||||
|
"compileOptions": {
|
||||||
|
"globalObjectMode": "enable"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
{
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarBackgroundColor": "@navBgColor",
|
||||||
|
"navigationBarTextStyle": "@navTxtStyle",
|
||||||
|
"navigationBarTitleText": "FastapiAdmin",
|
||||||
|
"backgroundColor": "@bgColor",
|
||||||
|
"backgroundTextStyle": "@bgTxtStyle",
|
||||||
|
"backgroundColorTop": "@bgColorTop",
|
||||||
|
"backgroundColorBottom": "@bgColorBottom",
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"onReachBottomDistance": 50,
|
||||||
|
"animationType": "pop-in",
|
||||||
|
"animationDuration": 300
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
// #ifdef APP
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"type": "home",
|
||||||
|
"name": "home",
|
||||||
|
"layout": "tabbar",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "首页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"type": "home",
|
||||||
|
"name": "home",
|
||||||
|
"layout": "tabbar",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工作台概览"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP
|
||||||
|
{
|
||||||
|
"path": "pages/about/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "about",
|
||||||
|
"layout": "tabbar",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关于"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "login",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "mine",
|
||||||
|
"layout": "tabbar",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/work/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work",
|
||||||
|
"layout": "tabbar",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工作台"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/forget/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "forget",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "忘记密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/register/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "注册"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subPackages": [
|
||||||
|
{
|
||||||
|
"root": "subPages",
|
||||||
|
"pages": [
|
||||||
|
// #ifdef H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"path": "about/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "about",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关于"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"path": "setting/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "setting",
|
||||||
|
"layout": "default",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"path": "module_ai/chat/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work-chat",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "AI 助手"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"path": "module_system/profile/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "profile",
|
||||||
|
"layout": "default",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
{
|
||||||
|
"path": "module_ai/ai-models/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work-ai-models",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "AI 模型配置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "module_system/notices/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work-notices",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "通知公告"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "module_system/ticket-detail/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work-ticket-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工单详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "module_system/tickets/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "work-tickets",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工单管理"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tabBar": {
|
||||||
|
"custom": true,
|
||||||
|
"height": "0",
|
||||||
|
"color": "@tabColor",
|
||||||
|
"selectedColor": "@tabSelectedColor",
|
||||||
|
"backgroundColor": "@tabBgColor",
|
||||||
|
"borderStyle": "@tabBorderStyle",
|
||||||
|
"list": [
|
||||||
|
// #ifdef APP || H5 || MP-WEIXIN
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index/index"
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP
|
||||||
|
{
|
||||||
|
"pagePath": "pages/about/index"
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
{
|
||||||
|
"pagePath": "pages/work/index"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/mine/index"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user