|
@@ -0,0 +1,1205 @@
|
|
|
|
|
+var _a, _b;
|
|
|
|
|
+import { reactive, ref } from "vue";
|
|
|
|
|
+class Router {
|
|
|
|
|
+ // route: (options?: string | RouterConfig, params?: Record<string, any>) => Promise<void>;
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.config = {
|
|
|
|
|
+ type: "navigateTo",
|
|
|
|
|
+ url: "",
|
|
|
|
|
+ delta: 1,
|
|
|
|
|
+ // navigateBack页面后退时,回退的层数
|
|
|
|
|
+ params: {},
|
|
|
|
|
+ // 传递的参数
|
|
|
|
|
+ animationType: "pop-in",
|
|
|
|
|
+ // 窗口动画,只在APP有效
|
|
|
|
|
+ animationDuration: 300,
|
|
|
|
|
+ // 窗口动画持续时间,单位毫秒,只在APP有效
|
|
|
|
|
+ intercept: false
|
|
|
|
|
+ // 是否需要拦截
|
|
|
|
|
+ };
|
|
|
|
|
+ this.route = this.route.bind(this);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 判断url前面是否有"/",如果没有则加上,否则无法跳转
|
|
|
|
|
+ addRootPath(url) {
|
|
|
|
|
+ return url[0] === "/" ? url : `/${url}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 整合路由参数
|
|
|
|
|
+ mixinParam(url, params) {
|
|
|
|
|
+ url = url && this.addRootPath(url);
|
|
|
|
|
+ let query = "";
|
|
|
|
|
+ if (/.*\/.*\?.*=.*/.test(url)) {
|
|
|
|
|
+ query = uni.$u.queryParams(params, false);
|
|
|
|
|
+ return url + "&" + query;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ query = uni.$u.queryParams(params);
|
|
|
|
|
+ return url + query;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 路由跳转主方法
|
|
|
|
|
+ * @param options 跳转配置或url字符串
|
|
|
|
|
+ * @param params 跳转参数
|
|
|
|
|
+ */
|
|
|
|
|
+ async route(options = {}, params = {}) {
|
|
|
|
|
+ let mergeConfig = {};
|
|
|
|
|
+ if (typeof options === "string") {
|
|
|
|
|
+ mergeConfig.url = this.mixinParam(options, params);
|
|
|
|
|
+ mergeConfig.type = "navigateTo";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ mergeConfig = uni.$u.deepMerge(this.config, options);
|
|
|
|
|
+ mergeConfig.url = this.mixinParam(options.url || "", options.params || {});
|
|
|
|
|
+ }
|
|
|
|
|
+ if (params.intercept) {
|
|
|
|
|
+ this.config.intercept = params.intercept;
|
|
|
|
|
+ }
|
|
|
|
|
+ mergeConfig.params = params;
|
|
|
|
|
+ mergeConfig = uni.$u.deepMerge(this.config, mergeConfig);
|
|
|
|
|
+ if (uni.$u.routeIntercept && typeof uni.$u.routeIntercept === "function") {
|
|
|
|
|
+ const isNext = await new Promise((resolve) => {
|
|
|
|
|
+ uni.$u.routeIntercept(mergeConfig, resolve);
|
|
|
|
|
+ });
|
|
|
|
|
+ isNext && this.openPage(mergeConfig);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.openPage(mergeConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 执行路由跳转
|
|
|
|
|
+ openPage(config2) {
|
|
|
|
|
+ const { url = "", type = "", delta = 1, animationDuration = 300 } = config2;
|
|
|
|
|
+ if (type == "navigateTo" || type == "to") {
|
|
|
|
|
+ uni.navigateTo({ url, animationDuration });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (type == "redirectTo" || type == "redirect") {
|
|
|
|
|
+ uni.redirectTo({ url });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (type == "switchTab" || type == "tab") {
|
|
|
|
|
+ uni.switchTab({ url });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (type == "reLaunch" || type == "launch") {
|
|
|
|
|
+ uni.reLaunch({ url });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (type == "navigateBack" || type == "back") {
|
|
|
|
|
+ uni.navigateBack({ delta });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+new Router().route;
|
|
|
|
|
+if (!String.prototype.padStart) {
|
|
|
|
|
+ String.prototype.padStart = function(maxLength, fillString = " ") {
|
|
|
|
|
+ if (Object.prototype.toString.call(fillString) !== "[object String]")
|
|
|
|
|
+ throw new TypeError("fillString must be String");
|
|
|
|
|
+ let str = this;
|
|
|
|
|
+ if (str.length >= maxLength)
|
|
|
|
|
+ return String(str);
|
|
|
|
|
+ let fillLength = maxLength - str.length, times = Math.ceil(fillLength / fillString.length);
|
|
|
|
|
+ while (times >>= 1) {
|
|
|
|
|
+ fillString += fillString;
|
|
|
|
|
+ if (times === 1) {
|
|
|
|
|
+ fillString += fillString;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return fillString.slice(0, fillLength) + str;
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+const version = "0.5.7";
|
|
|
|
|
+const config = reactive({
|
|
|
|
|
+ v: version,
|
|
|
|
|
+ version,
|
|
|
|
|
+ // 主题名称
|
|
|
|
|
+ type: ["primary", "success", "info", "error", "warning"],
|
|
|
|
|
+ // 默认为官方主题名称
|
|
|
|
|
+ defaultTheme: "uviewpro",
|
|
|
|
|
+ // 默认为亮色模式
|
|
|
|
|
+ defaultDarkMode: "light",
|
|
|
|
|
+ // 默认为中文
|
|
|
|
|
+ defaultLocale: "zh-CN"
|
|
|
|
|
+});
|
|
|
|
|
+const lightPalette = {
|
|
|
|
|
+ primary: "#2979ff",
|
|
|
|
|
+ primaryDark: "#2b85e4",
|
|
|
|
|
+ primaryDisabled: "#a0cfff",
|
|
|
|
|
+ primaryLight: "#ecf5ff",
|
|
|
|
|
+ success: "#19be6b",
|
|
|
|
|
+ successDark: "#18b566",
|
|
|
|
|
+ successDisabled: "#71d5a1",
|
|
|
|
|
+ successLight: "#dbf1e1",
|
|
|
|
|
+ warning: "#ff9900",
|
|
|
|
|
+ warningDark: "#f29100",
|
|
|
|
|
+ warningDisabled: "#fcbd71",
|
|
|
|
|
+ warningLight: "#fdf6ec",
|
|
|
|
|
+ error: "#fa3534",
|
|
|
|
|
+ errorDark: "#dd6161",
|
|
|
|
|
+ errorDisabled: "#fab6b6",
|
|
|
|
|
+ errorLight: "#fef0f0",
|
|
|
|
|
+ info: "#909399",
|
|
|
|
|
+ infoDark: "#82848a",
|
|
|
|
|
+ infoDisabled: "#c8c9cc",
|
|
|
|
|
+ infoLight: "#f4f4f5",
|
|
|
|
|
+ whiteColor: "#ffffff",
|
|
|
|
|
+ blackColor: "#000000",
|
|
|
|
|
+ mainColor: "#303133",
|
|
|
|
|
+ contentColor: "#606266",
|
|
|
|
|
+ tipsColor: "#909399",
|
|
|
|
|
+ lightColor: "#c0c4cc",
|
|
|
|
|
+ borderColor: "#dcdfe6",
|
|
|
|
|
+ dividerColor: "#e4e7ed",
|
|
|
|
|
+ maskColor: "rgba(0, 0, 0, 0.4)",
|
|
|
|
|
+ shadowColor: "rgba(0, 0, 0, 0.1)",
|
|
|
|
|
+ bgColor: "#f3f4f6",
|
|
|
|
|
+ bgWhite: "#ffffff",
|
|
|
|
|
+ bgGrayLight: "#f1f1f1",
|
|
|
|
|
+ bgGrayDark: "#2f343c",
|
|
|
|
|
+ bgBlack: "#000000"
|
|
|
|
|
+};
|
|
|
|
|
+const darkPalette = {
|
|
|
|
|
+ primary: "#8ab4ff",
|
|
|
|
|
+ primaryDark: "#5f8dff",
|
|
|
|
|
+ primaryDisabled: "#3d4f74",
|
|
|
|
|
+ primaryLight: "#1d273f",
|
|
|
|
|
+ success: "#4ade80",
|
|
|
|
|
+ successDark: "#1f9d57",
|
|
|
|
|
+ successDisabled: "#2f4d3d",
|
|
|
|
|
+ successLight: "#10291f",
|
|
|
|
|
+ warning: "#fbbf24",
|
|
|
|
|
+ warningDark: "#c88f00",
|
|
|
|
|
+ warningDisabled: "#4a3b17",
|
|
|
|
|
+ warningLight: "#2b1f05",
|
|
|
|
|
+ error: "#ff6b6b",
|
|
|
|
|
+ errorDark: "#d83a3a",
|
|
|
|
|
+ errorDisabled: "#4f2323",
|
|
|
|
|
+ errorLight: "#2d1414",
|
|
|
|
|
+ info: "#a0a7b8",
|
|
|
|
|
+ infoDark: "#7c8394",
|
|
|
|
|
+ infoDisabled: "#3b3f4c",
|
|
|
|
|
+ infoLight: "#1d2029",
|
|
|
|
|
+ whiteColor: "#f5f6f7",
|
|
|
|
|
+ blackColor: "#f5f6f7",
|
|
|
|
|
+ mainColor: "#f5f6f7",
|
|
|
|
|
+ contentColor: "#cfd3dc",
|
|
|
|
|
+ tipsColor: "#9aa1af",
|
|
|
|
|
+ lightColor: "#6b7082",
|
|
|
|
|
+ borderColor: "#3a4251",
|
|
|
|
|
+ dividerColor: "#3a4251",
|
|
|
|
|
+ maskColor: "rgba(0, 0, 0, 0.6)",
|
|
|
|
|
+ shadowColor: "rgba(0, 0, 0, 0.3)",
|
|
|
|
|
+ bgColor: "#111827",
|
|
|
|
|
+ bgWhite: "#000000",
|
|
|
|
|
+ bgGrayLight: "#1a1a1a",
|
|
|
|
|
+ bgGrayDark: "#f5f7fa",
|
|
|
|
|
+ bgBlack: "#ffffff"
|
|
|
|
|
+};
|
|
|
|
|
+const lightCss = {
|
|
|
|
|
+ "--u-background": "#ffffff",
|
|
|
|
|
+ "--u-surface": "#f7f8fa",
|
|
|
|
|
+ "--u-text": "#303133"
|
|
|
|
|
+};
|
|
|
|
|
+const darkCss = {
|
|
|
|
|
+ "--u-background": "#0f1115",
|
|
|
|
|
+ "--u-surface": "#1c2233",
|
|
|
|
|
+ "--u-text": "#f5f6f7"
|
|
|
|
|
+};
|
|
|
|
|
+const defaultThemes = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: config.defaultTheme,
|
|
|
|
|
+ label: "默认蓝",
|
|
|
|
|
+ description: "uView Pro 默认主题,支持亮色与暗黑模式",
|
|
|
|
|
+ color: lightPalette,
|
|
|
|
|
+ darkColor: darkPalette,
|
|
|
|
|
+ css: lightCss,
|
|
|
|
|
+ darkCss
|
|
|
|
|
+ }
|
|
|
|
|
+];
|
|
|
|
|
+const color = reactive({ ...lightPalette });
|
|
|
|
|
+function getSystemDarkMode() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof uni !== "undefined" && typeof uni.getSystemInfoSync === "function") {
|
|
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
|
|
+ const theme = systemInfo.osTheme || systemInfo.theme || "light";
|
|
|
|
|
+ if (theme === "dark") {
|
|
|
|
|
+ return "dark";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (theme === "light") {
|
|
|
|
|
+ return "light";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn("[system-theme] getSystemDarkMode failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return "light";
|
|
|
|
|
+}
|
|
|
|
|
+const zhCN = {
|
|
|
|
|
+ name: "zh-CN",
|
|
|
|
|
+ label: "简体中文",
|
|
|
|
|
+ locale: "zh-Hans",
|
|
|
|
|
+ uActionSheet: {
|
|
|
|
|
+ cancelText: "取消"
|
|
|
|
|
+ },
|
|
|
|
|
+ uUpload: {
|
|
|
|
|
+ uploadText: "选择图片",
|
|
|
|
|
+ retry: "点击重试",
|
|
|
|
|
+ overSize: "超出允许的文件大小",
|
|
|
|
|
+ overMaxCount: "超出最大允许的文件个数",
|
|
|
|
|
+ reUpload: "重新上传",
|
|
|
|
|
+ uploadFailed: "上传失败,请重试",
|
|
|
|
|
+ modalTitle: "提示",
|
|
|
|
|
+ deleteConfirm: "您确定要删除此项吗?",
|
|
|
|
|
+ terminatedRemove: "已终止移除",
|
|
|
|
|
+ removeSuccess: "移除成功",
|
|
|
|
|
+ previewFailed: "预览图片失败",
|
|
|
|
|
+ notAllowedExt: "不允许选择{ext}格式的文件",
|
|
|
|
|
+ noAction: "请配置上传地址"
|
|
|
|
|
+ },
|
|
|
|
|
+ uVerificationCode: {
|
|
|
|
|
+ startText: "获取验证码",
|
|
|
|
|
+ changeText: "X秒重新获取",
|
|
|
|
|
+ endText: "重新获取"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSection: {
|
|
|
|
|
+ subTitle: "更多"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSelect: {
|
|
|
|
|
+ cancelText: "取消",
|
|
|
|
|
+ confirmText: "确认"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSearch: {
|
|
|
|
|
+ placeholder: "请输入关键字",
|
|
|
|
|
+ actionText: "搜索"
|
|
|
|
|
+ },
|
|
|
|
|
+ uNoNetwork: {
|
|
|
|
|
+ tips: "哎呀,网络信号丢失",
|
|
|
|
|
+ checkNetwork: "请检查网络,或前往",
|
|
|
|
|
+ setting: "设置",
|
|
|
|
|
+ retry: "重试",
|
|
|
|
|
+ noConnection: "无网络连接",
|
|
|
|
|
+ connected: "网络已连接"
|
|
|
|
|
+ },
|
|
|
|
|
+ uReadMore: {
|
|
|
|
|
+ closeText: "展开阅读全文",
|
|
|
|
|
+ openText: "收起"
|
|
|
|
|
+ },
|
|
|
|
|
+ uPagination: {
|
|
|
|
|
+ prevText: "上一页",
|
|
|
|
|
+ nextText: "下一页"
|
|
|
|
|
+ },
|
|
|
|
|
+ uPicker: {
|
|
|
|
|
+ cancelText: "取消",
|
|
|
|
|
+ confirmText: "确认"
|
|
|
|
|
+ },
|
|
|
|
|
+ uModal: {
|
|
|
|
|
+ title: "提示",
|
|
|
|
|
+ content: "内容",
|
|
|
|
|
+ confirmText: "确认",
|
|
|
|
|
+ cancelText: "取消"
|
|
|
|
|
+ },
|
|
|
|
|
+ uLoadmore: {
|
|
|
|
|
+ loadmore: "加载更多",
|
|
|
|
|
+ loading: "正在加载...",
|
|
|
|
|
+ nomore: "没有更多了"
|
|
|
|
|
+ },
|
|
|
|
|
+ uLink: {
|
|
|
|
|
+ mpTips: "链接已复制,请在浏览器打开"
|
|
|
|
|
+ },
|
|
|
|
|
+ uKeyboard: {
|
|
|
|
|
+ cancelText: "取消",
|
|
|
|
|
+ confirmText: "确认",
|
|
|
|
|
+ number: "数字键盘",
|
|
|
|
|
+ idCard: "身份证键盘",
|
|
|
|
|
+ plate: "车牌号键盘"
|
|
|
|
|
+ },
|
|
|
|
|
+ uInput: {
|
|
|
|
|
+ placeholder: "请输入内容"
|
|
|
|
|
+ },
|
|
|
|
|
+ uCalendar: {
|
|
|
|
|
+ startText: "开始",
|
|
|
|
|
+ endText: "结束",
|
|
|
|
|
+ toolTip: "选择日期",
|
|
|
|
|
+ outOfRange: "日期超出范围啦~",
|
|
|
|
|
+ year: "年",
|
|
|
|
|
+ month: "月",
|
|
|
|
|
+ sun: "日",
|
|
|
|
|
+ mon: "一",
|
|
|
|
|
+ tue: "二",
|
|
|
|
|
+ wed: "三",
|
|
|
|
|
+ thu: "四",
|
|
|
|
|
+ fri: "五",
|
|
|
|
|
+ sat: "六",
|
|
|
|
|
+ confirmText: "确定",
|
|
|
|
|
+ to: "至"
|
|
|
|
|
+ },
|
|
|
|
|
+ uEmpty: {
|
|
|
|
|
+ car: "购物车为空",
|
|
|
|
|
+ page: "页面不存在",
|
|
|
|
|
+ search: "没有搜索结果",
|
|
|
|
|
+ address: "没有收货地址",
|
|
|
|
|
+ wifi: "没有WiFi",
|
|
|
|
|
+ order: "订单为空",
|
|
|
|
|
+ coupon: "没有优惠券",
|
|
|
|
|
+ favor: "暂无收藏",
|
|
|
|
|
+ permission: "无权限",
|
|
|
|
|
+ history: "无历史记录",
|
|
|
|
|
+ news: "无新闻列表",
|
|
|
|
|
+ message: "消息列表为空",
|
|
|
|
|
+ list: "列表为空",
|
|
|
|
|
+ data: "数据为空"
|
|
|
|
|
+ },
|
|
|
|
|
+ uCountDown: {
|
|
|
|
|
+ day: "天",
|
|
|
|
|
+ hour: "时",
|
|
|
|
|
+ minute: "分",
|
|
|
|
|
+ second: "秒"
|
|
|
|
|
+ },
|
|
|
|
|
+ uFullScreen: {
|
|
|
|
|
+ title: "发现新版本",
|
|
|
|
|
+ upgrade: "升级"
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+const enUS = {
|
|
|
|
|
+ name: "en-US",
|
|
|
|
|
+ label: "English",
|
|
|
|
|
+ locale: "en",
|
|
|
|
|
+ uActionSheet: {
|
|
|
|
|
+ cancelText: "Cancel"
|
|
|
|
|
+ },
|
|
|
|
|
+ uUpload: {
|
|
|
|
|
+ uploadText: "Select Image",
|
|
|
|
|
+ retry: "Retry",
|
|
|
|
|
+ overSize: "File size exceeds allowed limit",
|
|
|
|
|
+ overMaxCount: "Exceeds maximum allowed number of files",
|
|
|
|
|
+ reUpload: "Re-upload",
|
|
|
|
|
+ uploadFailed: "Upload failed, please try again",
|
|
|
|
|
+ modalTitle: "Notice",
|
|
|
|
|
+ deleteConfirm: "Are you sure you want to delete this item?",
|
|
|
|
|
+ terminatedRemove: "Removal cancelled",
|
|
|
|
|
+ removeSuccess: "Removed successfully",
|
|
|
|
|
+ previewFailed: "Failed to preview image",
|
|
|
|
|
+ notAllowedExt: "Files with {ext} format are not allowed",
|
|
|
|
|
+ noAction: "Please configure upload address"
|
|
|
|
|
+ },
|
|
|
|
|
+ uVerificationCode: {
|
|
|
|
|
+ startText: "Get Code",
|
|
|
|
|
+ changeText: "Retry in Xs",
|
|
|
|
|
+ endText: "Retry"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSection: {
|
|
|
|
|
+ subTitle: "More"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSelect: {
|
|
|
|
|
+ cancelText: "Cancel",
|
|
|
|
|
+ confirmText: "Confirm"
|
|
|
|
|
+ },
|
|
|
|
|
+ uSearch: {
|
|
|
|
|
+ placeholder: "Please enter keywords",
|
|
|
|
|
+ actionText: "Search"
|
|
|
|
|
+ },
|
|
|
|
|
+ uNoNetwork: {
|
|
|
|
|
+ tips: "Ooops, network disconnected",
|
|
|
|
|
+ checkNetwork: "Please check network or go to",
|
|
|
|
|
+ setting: "Settings",
|
|
|
|
|
+ retry: "Retry",
|
|
|
|
|
+ noConnection: "No network connection",
|
|
|
|
|
+ connected: "Network connected"
|
|
|
|
|
+ },
|
|
|
|
|
+ uReadMore: {
|
|
|
|
|
+ closeText: "Read More",
|
|
|
|
|
+ openText: "Collapse"
|
|
|
|
|
+ },
|
|
|
|
|
+ uPagination: {
|
|
|
|
|
+ prevText: "Prev",
|
|
|
|
|
+ nextText: "Next"
|
|
|
|
|
+ },
|
|
|
|
|
+ uPicker: {
|
|
|
|
|
+ cancelText: "Cancel",
|
|
|
|
|
+ confirmText: "Confirm"
|
|
|
|
|
+ },
|
|
|
|
|
+ uModal: {
|
|
|
|
|
+ title: "Notice",
|
|
|
|
|
+ content: "Content",
|
|
|
|
|
+ confirmText: "Confirm",
|
|
|
|
|
+ cancelText: "Cancel"
|
|
|
|
|
+ },
|
|
|
|
|
+ uLoadmore: {
|
|
|
|
|
+ loadmore: "Load more",
|
|
|
|
|
+ loading: "Loading...",
|
|
|
|
|
+ nomore: "No more"
|
|
|
|
|
+ },
|
|
|
|
|
+ uLink: {
|
|
|
|
|
+ mpTips: "Link copied, please open it in browser"
|
|
|
|
|
+ },
|
|
|
|
|
+ uKeyboard: {
|
|
|
|
|
+ cancelText: "Cancel",
|
|
|
|
|
+ confirmText: "Confirm",
|
|
|
|
|
+ number: "Number Keyboard",
|
|
|
|
|
+ idCard: "ID Card Keyboard",
|
|
|
|
|
+ plate: "Plate Keyboard"
|
|
|
|
|
+ },
|
|
|
|
|
+ uInput: {
|
|
|
|
|
+ placeholder: "Please enter"
|
|
|
|
|
+ },
|
|
|
|
|
+ uCalendar: {
|
|
|
|
|
+ startText: "Start",
|
|
|
|
|
+ endText: "End",
|
|
|
|
|
+ toolTip: "Select date",
|
|
|
|
|
+ outOfRange: "Date out of range",
|
|
|
|
|
+ year: "",
|
|
|
|
|
+ month: "",
|
|
|
|
|
+ sun: "Sun",
|
|
|
|
|
+ mon: "Mon",
|
|
|
|
|
+ tue: "Tue",
|
|
|
|
|
+ wed: "Wed",
|
|
|
|
|
+ thu: "Thu",
|
|
|
|
|
+ fri: "Fri",
|
|
|
|
|
+ sat: "Sat",
|
|
|
|
|
+ confirmText: "Confirm",
|
|
|
|
|
+ to: " to "
|
|
|
|
|
+ },
|
|
|
|
|
+ uEmpty: {
|
|
|
|
|
+ car: "Shopping cart is empty",
|
|
|
|
|
+ page: "Page not found",
|
|
|
|
|
+ search: "No search results",
|
|
|
|
|
+ address: "No shipping address",
|
|
|
|
|
+ wifi: "No WiFi",
|
|
|
|
|
+ order: "No orders",
|
|
|
|
|
+ coupon: "No coupons",
|
|
|
|
|
+ favor: "No favorites",
|
|
|
|
|
+ permission: "No permission",
|
|
|
|
|
+ history: "No history",
|
|
|
|
|
+ news: "No news",
|
|
|
|
|
+ message: "No messages",
|
|
|
|
|
+ list: "No list",
|
|
|
|
|
+ data: "No data"
|
|
|
|
|
+ },
|
|
|
|
|
+ uCountDown: {
|
|
|
|
|
+ day: "days",
|
|
|
|
|
+ hour: "hours",
|
|
|
|
|
+ minute: "minutes",
|
|
|
|
|
+ second: "Second"
|
|
|
|
|
+ },
|
|
|
|
|
+ uFullScreen: {
|
|
|
|
|
+ title: "New Version Available",
|
|
|
|
|
+ upgrade: "Upgrade"
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+const localePack = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
|
|
|
+ __proto__: null,
|
|
|
|
|
+ enUS,
|
|
|
|
|
+ zhCN
|
|
|
|
|
+}, Symbol.toStringTag, { value: "Module" }));
|
|
|
|
|
+const THEME_STORAGE_KEY = "uview-pro-theme";
|
|
|
|
|
+const DARK_MODE_STORAGE_KEY = "uview-pro-dark-mode";
|
|
|
|
|
+const LOCALE_STORAGE_KEY = "uview-pro-locale";
|
|
|
|
|
+const DEFAULT_LIGHT_TOKENS = ((_a = defaultThemes[0]) == null ? void 0 : _a.color) || {};
|
|
|
|
|
+const DEFAULT_DARK_TOKENS = ((_b = defaultThemes[0]) == null ? void 0 : _b.darkColor) || {};
|
|
|
|
|
+const STRUCTURAL_TOKENS = /* @__PURE__ */ new Set([
|
|
|
|
|
+ "bgColor",
|
|
|
|
|
+ "bgWhite",
|
|
|
|
|
+ "bgGrayLight",
|
|
|
|
|
+ "bgGrayDark",
|
|
|
|
|
+ "bgBlack",
|
|
|
|
|
+ "borderColor",
|
|
|
|
|
+ "lightColor",
|
|
|
|
|
+ "mainColor",
|
|
|
|
|
+ "contentColor",
|
|
|
|
|
+ "tipsColor",
|
|
|
|
|
+ "whiteColor",
|
|
|
|
|
+ "blackColor",
|
|
|
|
|
+ "dividerColor",
|
|
|
|
|
+ "maskColor",
|
|
|
|
|
+ "shadowColor"
|
|
|
|
|
+]);
|
|
|
|
|
+class ConfigProvider {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.themesRef = ref([]);
|
|
|
|
|
+ this.currentThemeRef = ref(null);
|
|
|
|
|
+ this.darkModeRef = ref(config.defaultDarkMode);
|
|
|
|
|
+ this.cssVarsRef = ref({});
|
|
|
|
|
+ this.localesRef = ref([]);
|
|
|
|
|
+ this.currentLocaleRef = ref(null);
|
|
|
|
|
+ this.baseColorTokens = DEFAULT_LIGHT_TOKENS;
|
|
|
|
|
+ this.baseDarkColorTokens = DEFAULT_DARK_TOKENS;
|
|
|
|
|
+ this.debug = false;
|
|
|
|
|
+ this.systemDarkModeMediaQuery = null;
|
|
|
|
|
+ this.lastAppliedCssKeys = [];
|
|
|
|
|
+ this.interval = 0;
|
|
|
|
|
+ this.initSystemDarkModeListener();
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化系统暗黑模式监听器
|
|
|
|
|
+ * 支持 H5、App、小程序等平台
|
|
|
|
|
+ */
|
|
|
|
|
+ initSystemDarkModeListener() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof window !== "undefined" && window.matchMedia) {
|
|
|
|
|
+ this.systemDarkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
|
|
|
+ const listener = () => {
|
|
|
|
|
+ if (this.darkModeRef.value === "auto") {
|
|
|
|
|
+ this.applyTheme(this.currentThemeRef.value);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ if (this.systemDarkModeMediaQuery.addEventListener) {
|
|
|
|
|
+ this.systemDarkModeMediaQuery.addEventListener("change", listener);
|
|
|
|
|
+ } else if (this.systemDarkModeMediaQuery.addListener) {
|
|
|
|
|
+ this.systemDarkModeMediaQuery.addListener(listener);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] H5 system dark mode listener failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof uni !== "undefined" && typeof uni.onThemeChange === "function") {
|
|
|
|
|
+ uni.onThemeChange((res) => {
|
|
|
|
|
+ console.log("[ConfigProvider] system theme changed", res);
|
|
|
|
|
+ if (this.darkModeRef.value === "auto") {
|
|
|
|
|
+ this.applyTheme(this.currentThemeRef.value);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] uni-app system dark mode listener failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.initAppEvent();
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * App 平台事件监听
|
|
|
|
|
+ * 经测试 uni.onThemeChange 在 App 平台目前没生效,暂时只能通过定时检查
|
|
|
|
|
+ */
|
|
|
|
|
+ initAppEvent() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.interval)
|
|
|
|
|
+ clearInterval(this.interval);
|
|
|
|
|
+ this.interval = setInterval(() => {
|
|
|
|
|
+ if (this.darkModeRef.value === "auto") {
|
|
|
|
|
+ this.applyTheme(this.currentThemeRef.value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 5e3);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] setInterval failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检测当前是否应该使用暗黑模式
|
|
|
|
|
+ */
|
|
|
|
|
+ isSystemDarkMode() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.systemDarkModeMediaQuery) {
|
|
|
|
|
+ return this.systemDarkModeMediaQuery.matches;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] matchMedia check failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return getSystemDarkMode() === "dark";
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] native system theme check failed", e);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化主题系统
|
|
|
|
|
+ * @param themes 可用主题数组
|
|
|
|
|
+ * @param defaultTheme 可选默认主题名
|
|
|
|
|
+ */
|
|
|
|
|
+ initTheme(themes, defaultConfig, isForce) {
|
|
|
|
|
+ const normalizedThemes = this.normalizeThemes(themes);
|
|
|
|
|
+ if (!normalizedThemes.length) {
|
|
|
|
|
+ console.warn("[ConfigProvider] init called with empty themes");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (defaultConfig) {
|
|
|
|
|
+ if (typeof defaultConfig === "string") {
|
|
|
|
|
+ config.defaultTheme = defaultConfig || config.defaultTheme;
|
|
|
|
|
+ } else if (typeof defaultConfig === "object") {
|
|
|
|
|
+ const { defaultTheme, defaultDarkMode } = defaultConfig;
|
|
|
|
|
+ config.defaultTheme = defaultTheme || config.defaultTheme;
|
|
|
|
|
+ config.defaultDarkMode = defaultDarkMode || config.defaultDarkMode;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.themesRef.value = normalizedThemes.slice();
|
|
|
|
|
+ const saved = this.readStorage(THEME_STORAGE_KEY);
|
|
|
|
|
+ let initialName = saved || config.defaultTheme || this.themesRef.value[0].name;
|
|
|
|
|
+ if (isForce && config.defaultTheme)
|
|
|
|
|
+ initialName = config.defaultTheme;
|
|
|
|
|
+ let found = this.themesRef.value.find((t) => t.name === initialName);
|
|
|
|
|
+ if (!found)
|
|
|
|
|
+ found = this.themesRef.value.find((t) => t.name === config.defaultTheme);
|
|
|
|
|
+ if (!found)
|
|
|
|
|
+ found = this.themesRef.value[0];
|
|
|
|
|
+ this.currentThemeRef.value = found;
|
|
|
|
|
+ this.initDarkMode(config.defaultDarkMode, isForce);
|
|
|
|
|
+ this.applyTheme(found);
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] initialized, theme=", found.name, "darkMode=", this.darkModeRef.value);
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化暗黑模式设置
|
|
|
|
|
+ * @param darkMode
|
|
|
|
|
+ */
|
|
|
|
|
+ initDarkMode(darkMode, isForce) {
|
|
|
|
|
+ const savedDarkMode = this.readStorage(DARK_MODE_STORAGE_KEY);
|
|
|
|
|
+ let darkModeValue = savedDarkMode || darkMode || config.defaultDarkMode;
|
|
|
|
|
+ if (isForce && darkMode)
|
|
|
|
|
+ darkModeValue = darkMode;
|
|
|
|
|
+ this.darkModeRef.value = darkModeValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化国际化数据
|
|
|
|
|
+ * @param locales 可选的 locale 列表(对象数组,包含 name 字段)
|
|
|
|
|
+ * @param defaultLocaleName 可选默认 locale 名称
|
|
|
|
|
+ */
|
|
|
|
|
+ initLocales(locales, defaultLocaleName, isForce) {
|
|
|
|
|
+ const normalized = this.normalizeLocales(locales);
|
|
|
|
|
+ if (!normalized.length) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] initLocales called with empty locales");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.localesRef.value = normalized.slice();
|
|
|
|
|
+ const saved = this.readStorage(LOCALE_STORAGE_KEY);
|
|
|
|
|
+ let initialName = saved || defaultLocaleName || config.defaultLocale;
|
|
|
|
|
+ if (isForce && defaultLocaleName)
|
|
|
|
|
+ initialName = defaultLocaleName;
|
|
|
|
|
+ let found = this.localesRef.value.find((l) => l.name === initialName);
|
|
|
|
|
+ if (!found)
|
|
|
|
|
+ found = this.localesRef.value.find((l) => l.name === config.defaultLocale);
|
|
|
|
|
+ if (!found)
|
|
|
|
|
+ found = this.localesRef.value[0];
|
|
|
|
|
+ this.currentLocaleRef.value = found;
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] locales initialized, locale=", found == null ? void 0 : found.name);
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 归一化 locale 配置,保证始终至少有一个默认 locale
|
|
|
|
|
+ */
|
|
|
|
|
+ normalizeLocales(locales) {
|
|
|
|
|
+ let builtinList = [];
|
|
|
|
|
+ try {
|
|
|
|
|
+ builtinList = Object.values(localePack || {}).filter((v) => v && typeof v === "object");
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] normalizeLocales read builtin failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!Array.isArray(locales) || !locales.length) {
|
|
|
|
|
+ return builtinList.slice();
|
|
|
|
|
+ }
|
|
|
|
|
+ const map = /* @__PURE__ */ new Map();
|
|
|
|
|
+ builtinList.forEach((item) => {
|
|
|
|
|
+ if (item && item.name) {
|
|
|
|
|
+ map.set(item.name, { ...item || {} });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ locales.forEach((loc) => {
|
|
|
|
|
+ if (!loc || !loc.name)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const existing = map.get(loc.name);
|
|
|
|
|
+ if (!existing) {
|
|
|
|
|
+ map.set(loc.name, { ...loc || {} });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const merged = { ...existing };
|
|
|
|
|
+ Object.keys(loc).forEach((k) => {
|
|
|
|
|
+ const v = loc[k];
|
|
|
|
|
+ if (v != null && typeof v === "object" && !Array.isArray(v) && typeof merged[k] === "object") {
|
|
|
|
|
+ merged[k] = { ...merged[k] || {}, ...v || {} };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ merged[k] = v;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ map.set(loc.name, merged);
|
|
|
|
|
+ });
|
|
|
|
|
+ return Array.from(map.values());
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取所有可用 locale
|
|
|
|
|
+ */
|
|
|
|
|
+ getLocales() {
|
|
|
|
|
+ return this.localesRef.value.slice();
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前 locale 对象
|
|
|
|
|
+ */
|
|
|
|
|
+ getCurrentLocale() {
|
|
|
|
|
+ return this.currentLocaleRef.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切换 locale 并持久化
|
|
|
|
|
+ */
|
|
|
|
|
+ setLocale(localeName) {
|
|
|
|
|
+ if (!this.localesRef.value || this.localesRef.value.length === 0) {
|
|
|
|
|
+ console.warn("[ConfigProvider] setLocale called but locales list empty");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const locale = this.localesRef.value.find((l) => l.name === localeName);
|
|
|
|
|
+ if (!locale) {
|
|
|
|
|
+ console.warn("[ConfigProvider] locale not found:", localeName);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.currentLocaleRef.value = locale;
|
|
|
|
|
+ this.writeStorage(LOCALE_STORAGE_KEY, localeName);
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] setLocale ->", localeName);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 翻译函数
|
|
|
|
|
+ * 支持 key 路径,例如 'calendar.placeholder'
|
|
|
|
|
+ * replacements 支持数组或对象替换占位符 {0} 或 {name}
|
|
|
|
|
+ */
|
|
|
|
|
+ t(key, replacements, localeName) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!Array.isArray(this.localesRef.value) || this.localesRef.value.length === 0) {
|
|
|
|
|
+ this.initLocales();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] lazy initLocales failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ const localeObj = localeName && this.localesRef.value.find((l) => l.name === localeName) || this.currentLocaleRef.value;
|
|
|
|
|
+ if (!localeObj)
|
|
|
|
|
+ return key;
|
|
|
|
|
+ const parts = key.split(".");
|
|
|
|
|
+ let cur = localeObj;
|
|
|
|
|
+ for (let i = 0; i < parts.length; i++) {
|
|
|
|
|
+ if (cur == null)
|
|
|
|
|
+ break;
|
|
|
|
|
+ cur = cur[parts[i]];
|
|
|
|
|
+ }
|
|
|
|
|
+ let text = typeof cur === "string" ? cur : key;
|
|
|
|
|
+ if (replacements != null) {
|
|
|
|
|
+ if (Array.isArray(replacements)) {
|
|
|
|
|
+ replacements.forEach((val, idx) => {
|
|
|
|
|
+ text = text.split(`{${idx}}`).join(String(val));
|
|
|
|
|
+ });
|
|
|
|
|
+ } else if (typeof replacements === "object") {
|
|
|
|
|
+ Object.keys(replacements).forEach((k) => {
|
|
|
|
|
+ text = text.split(`{${k}}`).join(String(replacements[k]));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return text;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取所有可用主题
|
|
|
|
|
+ */
|
|
|
|
|
+ getThemes() {
|
|
|
|
|
+ return this.themesRef.value.slice();
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前主题
|
|
|
|
|
+ */
|
|
|
|
|
+ getCurrentTheme() {
|
|
|
|
|
+ return this.currentThemeRef.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切换主题并持久化
|
|
|
|
|
+ */
|
|
|
|
|
+ setTheme(themeName) {
|
|
|
|
|
+ if (!this.themesRef.value || this.themesRef.value.length === 0) {
|
|
|
|
|
+ console.warn("[ConfigProvider] setTheme called but themes list empty");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const theme = this.themesRef.value.find((t) => t.name === themeName);
|
|
|
|
|
+ if (!theme) {
|
|
|
|
|
+ console.warn("[ConfigProvider] theme not found:", themeName);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.currentThemeRef.value = theme;
|
|
|
|
|
+ this.applyTheme(theme);
|
|
|
|
|
+ this.writeStorage(THEME_STORAGE_KEY, themeName);
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] setTheme ->", themeName);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 运行时更新当前主题颜色并应用(不持久化)
|
|
|
|
|
+ * @param colors 主题颜色键值,支持部分更新
|
|
|
|
|
+ */
|
|
|
|
|
+ setThemeColor(colors) {
|
|
|
|
|
+ if (!colors || Object.keys(colors).length === 0)
|
|
|
|
|
+ return;
|
|
|
|
|
+ if (!this.currentThemeRef.value) {
|
|
|
|
|
+ console.warn("[ConfigProvider] setThemeColor called but no current theme");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const mode = this.getActiveMode();
|
|
|
|
|
+ if (mode === "dark") {
|
|
|
|
|
+ const existing = this.currentThemeRef.value.darkColor || {};
|
|
|
|
|
+ this.currentThemeRef.value.darkColor = {
|
|
|
|
|
+ ...existing,
|
|
|
|
|
+ ...colors
|
|
|
|
|
+ };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const existing = this.currentThemeRef.value.color || {};
|
|
|
|
|
+ this.currentThemeRef.value.color = {
|
|
|
|
|
+ ...existing,
|
|
|
|
|
+ ...colors
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ this.applyTheme(this.currentThemeRef.value);
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] setThemeColor ->", colors);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前暗黑模式设置
|
|
|
|
|
+ */
|
|
|
|
|
+ getDarkMode() {
|
|
|
|
|
+ return this.darkModeRef.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置暗黑模式
|
|
|
|
|
+ * @param mode 'auto' (跟随系统) | 'light' (强制亮色) | 'dark' (强制暗黑)
|
|
|
|
|
+ */
|
|
|
|
|
+ setDarkMode(mode) {
|
|
|
|
|
+ this.darkModeRef.value = mode;
|
|
|
|
|
+ this.writeStorage(DARK_MODE_STORAGE_KEY, mode);
|
|
|
|
|
+ this.applyTheme(this.currentThemeRef.value);
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.log("[ConfigProvider] setDarkMode ->", mode);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查当前是否处于暗黑模式
|
|
|
|
|
+ */
|
|
|
|
|
+ isInDarkMode() {
|
|
|
|
|
+ const mode = this.darkModeRef.value;
|
|
|
|
|
+ if (mode === "dark")
|
|
|
|
|
+ return true;
|
|
|
|
|
+ if (mode === "light")
|
|
|
|
|
+ return false;
|
|
|
|
|
+ return this.isSystemDarkMode();
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 归一化主题配置,保证始终至少有一个默认主题
|
|
|
|
|
+ */
|
|
|
|
|
+ normalizeThemes(themes) {
|
|
|
|
|
+ if (Array.isArray(themes) && themes.length) {
|
|
|
|
|
+ return this.mergeThemes(defaultThemes, themes);
|
|
|
|
|
+ }
|
|
|
|
|
+ return defaultThemes.slice();
|
|
|
|
|
+ }
|
|
|
|
|
+ mergeThemes(...lists) {
|
|
|
|
|
+ const map = /* @__PURE__ */ new Map();
|
|
|
|
|
+ lists.filter((list) => Array.isArray(list) && list.length > 0).forEach((list) => {
|
|
|
|
|
+ list.forEach((theme) => {
|
|
|
|
|
+ const normalized = this.ensureDarkVariant({
|
|
|
|
|
+ ...theme,
|
|
|
|
|
+ color: this.applyColorFallbacks(theme.color),
|
|
|
|
|
+ darkColor: theme.darkColor ? { ...theme.darkColor } : void 0,
|
|
|
|
|
+ css: theme.css ? { ...theme.css } : void 0,
|
|
|
|
|
+ darkCss: theme.darkCss ? { ...theme.darkCss } : void 0
|
|
|
|
|
+ });
|
|
|
|
|
+ map.set(normalized.name, normalized);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ return Array.from(map.values());
|
|
|
|
|
+ }
|
|
|
|
|
+ ensureDarkVariant(theme) {
|
|
|
|
|
+ const finalDark = this.buildDarkPalette(theme);
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...theme,
|
|
|
|
|
+ darkColor: this.applyDarkFallbacks(finalDark)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ buildDarkPalette(theme) {
|
|
|
|
|
+ const provided = theme.darkColor || {};
|
|
|
|
|
+ const generated = this.generateDarkFromLight(theme.color || {}, provided);
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...generated,
|
|
|
|
|
+ ...provided
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 应用亮色主题
|
|
|
|
|
+ */
|
|
|
|
|
+ applyColorFallbacks(color2) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...this.baseColorTokens || {},
|
|
|
|
|
+ ...color2 || {}
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 应用暗黑主题
|
|
|
|
|
+ */
|
|
|
|
|
+ applyDarkFallbacks(color2) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...this.baseDarkColorTokens || {},
|
|
|
|
|
+ ...color2 || {}
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ filterNonStructuralTokens(palette) {
|
|
|
|
|
+ const result = {};
|
|
|
|
|
+ Object.entries(palette || {}).forEach(([key, value]) => {
|
|
|
|
|
+ if (!this.isStructuralToken(key)) {
|
|
|
|
|
+ result[key] = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ generateDarkFromLight(palette, provided) {
|
|
|
|
|
+ const result = {};
|
|
|
|
|
+ const nonStructural = this.filterNonStructuralTokens(palette);
|
|
|
|
|
+ Object.entries(nonStructural).forEach(([key, value]) => {
|
|
|
|
|
+ var _a2;
|
|
|
|
|
+ if (typeof value !== "string")
|
|
|
|
|
+ return;
|
|
|
|
|
+ if (provided && Object.prototype.hasOwnProperty.call(provided, key)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const fallback = (_a2 = this.baseDarkColorTokens) == null ? void 0 : _a2[key];
|
|
|
|
|
+ result[key] = this.createDarkVariantFromLight(value, fallback);
|
|
|
|
|
+ });
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ createDarkVariantFromLight(color2, fallback) {
|
|
|
|
|
+ const normalized = this.normalizeHex(color2);
|
|
|
|
|
+ const fallbackHex = fallback ? this.normalizeHex(fallback) : null;
|
|
|
|
|
+ if (normalized && fallbackHex) {
|
|
|
|
|
+ return this.mixHex(normalized, fallbackHex, 0.6);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (fallbackHex)
|
|
|
|
|
+ return fallbackHex;
|
|
|
|
|
+ return normalized || color2;
|
|
|
|
|
+ }
|
|
|
|
|
+ normalizeHex(color2) {
|
|
|
|
|
+ if (!color2)
|
|
|
|
|
+ return null;
|
|
|
|
|
+ const hex = color2.trim();
|
|
|
|
|
+ if (/^#([0-9a-fA-F]{6})$/.test(hex))
|
|
|
|
|
+ return hex.toLowerCase();
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ mixHex(fromHex, toHex, ratio) {
|
|
|
|
|
+ const from = this.hexToRgb(fromHex);
|
|
|
|
|
+ const to = this.hexToRgb(toHex);
|
|
|
|
|
+ if (!from || !to)
|
|
|
|
|
+ return toHex;
|
|
|
|
|
+ const clamp = (val) => Math.min(255, Math.max(0, Math.round(val)));
|
|
|
|
|
+ const r = clamp(from.r * (1 - ratio) + to.r * ratio);
|
|
|
|
|
+ const g = clamp(from.g * (1 - ratio) + to.g * ratio);
|
|
|
|
|
+ const b = clamp(from.b * (1 - ratio) + to.b * ratio);
|
|
|
|
|
+ return this.rgbToHex(r, g, b);
|
|
|
|
|
+ }
|
|
|
|
|
+ hexToRgb(hex) {
|
|
|
|
|
+ const match = /^#([0-9a-fA-F]{6})$/.exec(hex);
|
|
|
|
|
+ if (!match)
|
|
|
|
|
+ return null;
|
|
|
|
|
+ return {
|
|
|
|
|
+ r: parseInt(match[1].slice(0, 2), 16),
|
|
|
|
|
+ g: parseInt(match[1].slice(2, 4), 16),
|
|
|
|
|
+ b: parseInt(match[1].slice(4, 6), 16)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ rgbToHex(r, g, b) {
|
|
|
|
|
+ const toHex = (val) => val.toString(16).padStart(2, "0");
|
|
|
|
|
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ isStructuralToken(token) {
|
|
|
|
|
+ return STRUCTURAL_TOKENS.has(token);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 运行时同步主题颜色($u.color)
|
|
|
|
|
+ * 更新响应式 color 对象,确保所有使用 $u.color 的地方都能响应式更新
|
|
|
|
|
+ */
|
|
|
|
|
+ syncRuntimeTheme(palette) {
|
|
|
|
|
+ var _a2;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const defaultPalette = this.getActiveMode() === "dark" ? this.baseDarkColorTokens : this.baseColorTokens;
|
|
|
|
|
+ const mergedPalette = {
|
|
|
|
|
+ ...defaultPalette,
|
|
|
|
|
+ ...palette
|
|
|
|
|
+ };
|
|
|
|
|
+ Object.keys(mergedPalette).forEach((key) => {
|
|
|
|
|
+ const value = mergedPalette[key];
|
|
|
|
|
+ if (value != null) {
|
|
|
|
|
+ color[key] = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (typeof uni !== "undefined" && ((_a2 = uni == null ? void 0 : uni.$u) == null ? void 0 : _a2.color)) {
|
|
|
|
|
+ uni.$u.color = color;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] sync runtime theme failed", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前激活的模式
|
|
|
|
|
+ */
|
|
|
|
|
+ getActiveMode() {
|
|
|
|
|
+ return this.isInDarkMode() ? "dark" : "light";
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前主题的配色方案
|
|
|
|
|
+ */
|
|
|
|
|
+ getPaletteForMode(theme, mode) {
|
|
|
|
|
+ if (mode === "dark") {
|
|
|
|
|
+ return theme.darkColor && Object.keys(theme.darkColor).length ? theme.darkColor : theme.color || {};
|
|
|
|
|
+ }
|
|
|
|
|
+ return theme.color || {};
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前主题的CSS变量覆盖
|
|
|
|
|
+ */
|
|
|
|
|
+ getCssOverrides(theme, mode) {
|
|
|
|
|
+ if (mode === "dark") {
|
|
|
|
|
+ return (theme.darkCss && Object.keys(theme.darkCss).length ? theme.darkCss : theme.css) || {};
|
|
|
|
|
+ }
|
|
|
|
|
+ return theme.css || {};
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 读取Storage key
|
|
|
|
|
+ */
|
|
|
|
|
+ readStorage(key) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof uni === "undefined" || typeof uni.getStorageSync !== "function")
|
|
|
|
|
+ return null;
|
|
|
|
|
+ const value = uni.getStorageSync(key);
|
|
|
|
|
+ return value ?? null;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] failed to read storage", e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 写入Storage key value
|
|
|
|
|
+ */
|
|
|
|
|
+ writeStorage(key, value) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof uni === "undefined" || typeof uni.setStorageSync !== "function")
|
|
|
|
|
+ return;
|
|
|
|
|
+ uni.setStorageSync(key, value);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (this.debug)
|
|
|
|
|
+ console.warn("[ConfigProvider] failed to write storage", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 更新文档主题模式 H5
|
|
|
|
|
+ */
|
|
|
|
|
+ updateDocumentMode(mode) {
|
|
|
|
|
+ if (typeof document === "undefined" || !document.documentElement)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const root = document.documentElement;
|
|
|
|
|
+ root.dataset.uThemeMode = mode;
|
|
|
|
|
+ root.classList.remove("u-theme-light", "u-theme-dark");
|
|
|
|
|
+ root.classList.add(`u-theme-${mode}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 转换为 CSS 变量名称
|
|
|
|
|
+ */
|
|
|
|
|
+ toCssVarName(key, prefix = "--u") {
|
|
|
|
|
+ const types = config.type;
|
|
|
|
|
+ if (types.some((type) => key.startsWith(type))) {
|
|
|
|
|
+ prefix += "-type";
|
|
|
|
|
+ }
|
|
|
|
|
+ const kebab = key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
|
|
|
+ return `${prefix}-${kebab}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 添加 RGB 值
|
|
|
|
|
+ */
|
|
|
|
|
+ attachRgbVar(target, varName, value) {
|
|
|
|
|
+ if (typeof value !== "string")
|
|
|
|
|
+ return;
|
|
|
|
|
+ const hex = value.startsWith("#") ? value.slice(1) : "";
|
|
|
|
|
+ if (!/^[0-9a-fA-F]{6}$/.test(hex))
|
|
|
|
|
+ return;
|
|
|
|
|
+ const r = parseInt(hex.slice(0, 2), 16);
|
|
|
|
|
+ const g = parseInt(hex.slice(2, 4), 16);
|
|
|
|
|
+ const b = parseInt(hex.slice(4, 6), 16);
|
|
|
|
|
+ target[`${varName}-rgb`] = `${r}, ${g}, ${b}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建 CSS 变量映射表
|
|
|
|
|
+ * 生成格式:
|
|
|
|
|
+ */
|
|
|
|
|
+ buildCssVarMap(theme, mode) {
|
|
|
|
|
+ const map = {
|
|
|
|
|
+ "--u-theme-mode": mode
|
|
|
|
|
+ };
|
|
|
|
|
+ const palette = this.getPaletteForMode(theme, mode);
|
|
|
|
|
+ const cssOverrides = this.getCssOverrides(theme, mode);
|
|
|
|
|
+ const applyEntry = (key, value) => {
|
|
|
|
|
+ if (value == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const strValue = String(value);
|
|
|
|
|
+ if (key.startsWith("--")) {
|
|
|
|
|
+ map[key] = strValue;
|
|
|
|
|
+ this.attachRgbVar(map, key, strValue);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const cssVarName = this.toCssVarName(key);
|
|
|
|
|
+ map[cssVarName] = strValue;
|
|
|
|
|
+ this.attachRgbVar(map, cssVarName, strValue);
|
|
|
|
|
+ };
|
|
|
|
|
+ Object.entries(palette || {}).forEach(([key, value]) => applyEntry(key, value));
|
|
|
|
|
+ Object.entries(cssOverrides || {}).forEach(([key, value]) => applyEntry(key, value));
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 刷新 CSS 变量 H5
|
|
|
|
|
+ */
|
|
|
|
|
+ flushCssVars(vars) {
|
|
|
|
|
+ if (typeof document === "undefined" || !document.documentElement)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const root = document.documentElement;
|
|
|
|
|
+ this.lastAppliedCssKeys.forEach((key) => {
|
|
|
|
|
+ root.style.removeProperty(key);
|
|
|
|
|
+ });
|
|
|
|
|
+ Object.entries(vars).forEach(([key, value]) => {
|
|
|
|
|
+ root.style.setProperty(key, value);
|
|
|
|
|
+ });
|
|
|
|
|
+ this.lastAppliedCssKeys = Object.keys(vars);
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将主题应用到运行时:
|
|
|
|
|
+ * - 1) 调用 uni.$u.setColor(theme.color) 如果存在
|
|
|
|
|
+ * - 2) 在 H5 环境中,将 css map 注入到 document.documentElement 的 CSS 变量中
|
|
|
|
|
+ * - 3) 支持暗黑模式:根据 darkColor 或 color 应用相应的颜色
|
|
|
|
|
+ */
|
|
|
|
|
+ applyTheme(theme) {
|
|
|
|
|
+ if (!theme)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const mode = this.getActiveMode();
|
|
|
|
|
+ const palette = this.getPaletteForMode(theme, mode);
|
|
|
|
|
+ this.syncRuntimeTheme(palette);
|
|
|
|
|
+ const cssVarMap = this.buildCssVarMap(theme, mode);
|
|
|
|
|
+ this.cssVarsRef.value = cssVarMap;
|
|
|
|
|
+ this.flushCssVars(cssVarMap);
|
|
|
|
|
+ this.updateDocumentMode(mode);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const configProvider = new ConfigProvider();
|
|
|
|
|
+const originalConsole = {
|
|
|
|
|
+ log: console.log,
|
|
|
|
|
+ info: console.info,
|
|
|
|
|
+ warn: console.warn,
|
|
|
|
|
+ error: console.error,
|
|
|
|
|
+ debug: console.debug,
|
|
|
|
|
+ trace: console.trace,
|
|
|
|
|
+ table: console.table,
|
|
|
|
|
+ time: console.time,
|
|
|
|
|
+ timeEnd: console.timeEnd,
|
|
|
|
|
+ group: console.group,
|
|
|
|
|
+ groupEnd: console.groupEnd,
|
|
|
|
|
+ groupCollapsed: console.groupCollapsed,
|
|
|
|
|
+ assert: console.assert,
|
|
|
|
|
+ clear: console.clear,
|
|
|
|
|
+ count: console.count,
|
|
|
|
|
+ countReset: console.countReset
|
|
|
|
|
+};
|
|
|
|
|
+Object.keys(originalConsole).forEach((key) => {
|
|
|
|
|
+ const methodKey = key;
|
|
|
|
|
+ if (!originalConsole[methodKey]) {
|
|
|
|
|
+ originalConsole[methodKey] = () => {
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+configProvider.themesRef;
|
|
|
|
|
+configProvider.currentThemeRef;
|
|
|
|
|
+configProvider.darkModeRef;
|
|
|
|
|
+Promise.resolve("./app.css.js").then(() => {
|
|
|
|
|
+});
|