app.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  1. var _a, _b;
  2. import { reactive, ref } from "vue";
  3. class Router {
  4. // route: (options?: string | RouterConfig, params?: Record<string, any>) => Promise<void>;
  5. constructor() {
  6. this.config = {
  7. type: "navigateTo",
  8. url: "",
  9. delta: 1,
  10. // navigateBack页面后退时,回退的层数
  11. params: {},
  12. // 传递的参数
  13. animationType: "pop-in",
  14. // 窗口动画,只在APP有效
  15. animationDuration: 300,
  16. // 窗口动画持续时间,单位毫秒,只在APP有效
  17. intercept: false
  18. // 是否需要拦截
  19. };
  20. this.route = this.route.bind(this);
  21. }
  22. // 判断url前面是否有"/",如果没有则加上,否则无法跳转
  23. addRootPath(url) {
  24. return url[0] === "/" ? url : `/${url}`;
  25. }
  26. // 整合路由参数
  27. mixinParam(url, params) {
  28. url = url && this.addRootPath(url);
  29. let query = "";
  30. if (/.*\/.*\?.*=.*/.test(url)) {
  31. query = uni.$u.queryParams(params, false);
  32. return url + "&" + query;
  33. } else {
  34. query = uni.$u.queryParams(params);
  35. return url + query;
  36. }
  37. }
  38. /**
  39. * 路由跳转主方法
  40. * @param options 跳转配置或url字符串
  41. * @param params 跳转参数
  42. */
  43. async route(options = {}, params = {}) {
  44. let mergeConfig = {};
  45. if (typeof options === "string") {
  46. mergeConfig.url = this.mixinParam(options, params);
  47. mergeConfig.type = "navigateTo";
  48. } else {
  49. mergeConfig = uni.$u.deepMerge(this.config, options);
  50. mergeConfig.url = this.mixinParam(options.url || "", options.params || {});
  51. }
  52. if (params.intercept) {
  53. this.config.intercept = params.intercept;
  54. }
  55. mergeConfig.params = params;
  56. mergeConfig = uni.$u.deepMerge(this.config, mergeConfig);
  57. if (uni.$u.routeIntercept && typeof uni.$u.routeIntercept === "function") {
  58. const isNext = await new Promise((resolve) => {
  59. uni.$u.routeIntercept(mergeConfig, resolve);
  60. });
  61. isNext && this.openPage(mergeConfig);
  62. } else {
  63. this.openPage(mergeConfig);
  64. }
  65. }
  66. // 执行路由跳转
  67. openPage(config2) {
  68. const { url = "", type = "", delta = 1, animationDuration = 300 } = config2;
  69. if (type == "navigateTo" || type == "to") {
  70. uni.navigateTo({ url, animationDuration });
  71. }
  72. if (type == "redirectTo" || type == "redirect") {
  73. uni.redirectTo({ url });
  74. }
  75. if (type == "switchTab" || type == "tab") {
  76. uni.switchTab({ url });
  77. }
  78. if (type == "reLaunch" || type == "launch") {
  79. uni.reLaunch({ url });
  80. }
  81. if (type == "navigateBack" || type == "back") {
  82. uni.navigateBack({ delta });
  83. }
  84. }
  85. }
  86. new Router().route;
  87. if (!String.prototype.padStart) {
  88. String.prototype.padStart = function(maxLength, fillString = " ") {
  89. if (Object.prototype.toString.call(fillString) !== "[object String]")
  90. throw new TypeError("fillString must be String");
  91. let str = this;
  92. if (str.length >= maxLength)
  93. return String(str);
  94. let fillLength = maxLength - str.length, times = Math.ceil(fillLength / fillString.length);
  95. while (times >>= 1) {
  96. fillString += fillString;
  97. if (times === 1) {
  98. fillString += fillString;
  99. }
  100. }
  101. return fillString.slice(0, fillLength) + str;
  102. };
  103. }
  104. const version = "0.5.7";
  105. const config = reactive({
  106. v: version,
  107. version,
  108. // 主题名称
  109. type: ["primary", "success", "info", "error", "warning"],
  110. // 默认为官方主题名称
  111. defaultTheme: "uviewpro",
  112. // 默认为亮色模式
  113. defaultDarkMode: "light",
  114. // 默认为中文
  115. defaultLocale: "zh-CN"
  116. });
  117. const lightPalette = {
  118. primary: "#2979ff",
  119. primaryDark: "#2b85e4",
  120. primaryDisabled: "#a0cfff",
  121. primaryLight: "#ecf5ff",
  122. success: "#19be6b",
  123. successDark: "#18b566",
  124. successDisabled: "#71d5a1",
  125. successLight: "#dbf1e1",
  126. warning: "#ff9900",
  127. warningDark: "#f29100",
  128. warningDisabled: "#fcbd71",
  129. warningLight: "#fdf6ec",
  130. error: "#fa3534",
  131. errorDark: "#dd6161",
  132. errorDisabled: "#fab6b6",
  133. errorLight: "#fef0f0",
  134. info: "#909399",
  135. infoDark: "#82848a",
  136. infoDisabled: "#c8c9cc",
  137. infoLight: "#f4f4f5",
  138. whiteColor: "#ffffff",
  139. blackColor: "#000000",
  140. mainColor: "#303133",
  141. contentColor: "#606266",
  142. tipsColor: "#909399",
  143. lightColor: "#c0c4cc",
  144. borderColor: "#dcdfe6",
  145. dividerColor: "#e4e7ed",
  146. maskColor: "rgba(0, 0, 0, 0.4)",
  147. shadowColor: "rgba(0, 0, 0, 0.1)",
  148. bgColor: "#f3f4f6",
  149. bgWhite: "#ffffff",
  150. bgGrayLight: "#f1f1f1",
  151. bgGrayDark: "#2f343c",
  152. bgBlack: "#000000"
  153. };
  154. const darkPalette = {
  155. primary: "#8ab4ff",
  156. primaryDark: "#5f8dff",
  157. primaryDisabled: "#3d4f74",
  158. primaryLight: "#1d273f",
  159. success: "#4ade80",
  160. successDark: "#1f9d57",
  161. successDisabled: "#2f4d3d",
  162. successLight: "#10291f",
  163. warning: "#fbbf24",
  164. warningDark: "#c88f00",
  165. warningDisabled: "#4a3b17",
  166. warningLight: "#2b1f05",
  167. error: "#ff6b6b",
  168. errorDark: "#d83a3a",
  169. errorDisabled: "#4f2323",
  170. errorLight: "#2d1414",
  171. info: "#a0a7b8",
  172. infoDark: "#7c8394",
  173. infoDisabled: "#3b3f4c",
  174. infoLight: "#1d2029",
  175. whiteColor: "#f5f6f7",
  176. blackColor: "#f5f6f7",
  177. mainColor: "#f5f6f7",
  178. contentColor: "#cfd3dc",
  179. tipsColor: "#9aa1af",
  180. lightColor: "#6b7082",
  181. borderColor: "#3a4251",
  182. dividerColor: "#3a4251",
  183. maskColor: "rgba(0, 0, 0, 0.6)",
  184. shadowColor: "rgba(0, 0, 0, 0.3)",
  185. bgColor: "#111827",
  186. bgWhite: "#000000",
  187. bgGrayLight: "#1a1a1a",
  188. bgGrayDark: "#f5f7fa",
  189. bgBlack: "#ffffff"
  190. };
  191. const lightCss = {
  192. "--u-background": "#ffffff",
  193. "--u-surface": "#f7f8fa",
  194. "--u-text": "#303133"
  195. };
  196. const darkCss = {
  197. "--u-background": "#0f1115",
  198. "--u-surface": "#1c2233",
  199. "--u-text": "#f5f6f7"
  200. };
  201. const defaultThemes = [
  202. {
  203. name: config.defaultTheme,
  204. label: "默认蓝",
  205. description: "uView Pro 默认主题,支持亮色与暗黑模式",
  206. color: lightPalette,
  207. darkColor: darkPalette,
  208. css: lightCss,
  209. darkCss
  210. }
  211. ];
  212. const color = reactive({ ...lightPalette });
  213. function getSystemDarkMode() {
  214. try {
  215. if (typeof uni !== "undefined" && typeof uni.getSystemInfoSync === "function") {
  216. const systemInfo = uni.getSystemInfoSync();
  217. const theme = systemInfo.osTheme || systemInfo.theme || "light";
  218. if (theme === "dark") {
  219. return "dark";
  220. }
  221. if (theme === "light") {
  222. return "light";
  223. }
  224. }
  225. } catch (e) {
  226. console.warn("[system-theme] getSystemDarkMode failed", e);
  227. }
  228. return "light";
  229. }
  230. const zhCN = {
  231. name: "zh-CN",
  232. label: "简体中文",
  233. locale: "zh-Hans",
  234. uActionSheet: {
  235. cancelText: "取消"
  236. },
  237. uUpload: {
  238. uploadText: "选择图片",
  239. retry: "点击重试",
  240. overSize: "超出允许的文件大小",
  241. overMaxCount: "超出最大允许的文件个数",
  242. reUpload: "重新上传",
  243. uploadFailed: "上传失败,请重试",
  244. modalTitle: "提示",
  245. deleteConfirm: "您确定要删除此项吗?",
  246. terminatedRemove: "已终止移除",
  247. removeSuccess: "移除成功",
  248. previewFailed: "预览图片失败",
  249. notAllowedExt: "不允许选择{ext}格式的文件",
  250. noAction: "请配置上传地址"
  251. },
  252. uVerificationCode: {
  253. startText: "获取验证码",
  254. changeText: "X秒重新获取",
  255. endText: "重新获取"
  256. },
  257. uSection: {
  258. subTitle: "更多"
  259. },
  260. uSelect: {
  261. cancelText: "取消",
  262. confirmText: "确认"
  263. },
  264. uSearch: {
  265. placeholder: "请输入关键字",
  266. actionText: "搜索"
  267. },
  268. uNoNetwork: {
  269. tips: "哎呀,网络信号丢失",
  270. checkNetwork: "请检查网络,或前往",
  271. setting: "设置",
  272. retry: "重试",
  273. noConnection: "无网络连接",
  274. connected: "网络已连接"
  275. },
  276. uReadMore: {
  277. closeText: "展开阅读全文",
  278. openText: "收起"
  279. },
  280. uPagination: {
  281. prevText: "上一页",
  282. nextText: "下一页"
  283. },
  284. uPicker: {
  285. cancelText: "取消",
  286. confirmText: "确认"
  287. },
  288. uModal: {
  289. title: "提示",
  290. content: "内容",
  291. confirmText: "确认",
  292. cancelText: "取消"
  293. },
  294. uLoadmore: {
  295. loadmore: "加载更多",
  296. loading: "正在加载...",
  297. nomore: "没有更多了"
  298. },
  299. uLink: {
  300. mpTips: "链接已复制,请在浏览器打开"
  301. },
  302. uKeyboard: {
  303. cancelText: "取消",
  304. confirmText: "确认",
  305. number: "数字键盘",
  306. idCard: "身份证键盘",
  307. plate: "车牌号键盘"
  308. },
  309. uInput: {
  310. placeholder: "请输入内容"
  311. },
  312. uCalendar: {
  313. startText: "开始",
  314. endText: "结束",
  315. toolTip: "选择日期",
  316. outOfRange: "日期超出范围啦~",
  317. year: "年",
  318. month: "月",
  319. sun: "日",
  320. mon: "一",
  321. tue: "二",
  322. wed: "三",
  323. thu: "四",
  324. fri: "五",
  325. sat: "六",
  326. confirmText: "确定",
  327. to: "至"
  328. },
  329. uEmpty: {
  330. car: "购物车为空",
  331. page: "页面不存在",
  332. search: "没有搜索结果",
  333. address: "没有收货地址",
  334. wifi: "没有WiFi",
  335. order: "订单为空",
  336. coupon: "没有优惠券",
  337. favor: "暂无收藏",
  338. permission: "无权限",
  339. history: "无历史记录",
  340. news: "无新闻列表",
  341. message: "消息列表为空",
  342. list: "列表为空",
  343. data: "数据为空"
  344. },
  345. uCountDown: {
  346. day: "天",
  347. hour: "时",
  348. minute: "分",
  349. second: "秒"
  350. },
  351. uFullScreen: {
  352. title: "发现新版本",
  353. upgrade: "升级"
  354. }
  355. };
  356. const enUS = {
  357. name: "en-US",
  358. label: "English",
  359. locale: "en",
  360. uActionSheet: {
  361. cancelText: "Cancel"
  362. },
  363. uUpload: {
  364. uploadText: "Select Image",
  365. retry: "Retry",
  366. overSize: "File size exceeds allowed limit",
  367. overMaxCount: "Exceeds maximum allowed number of files",
  368. reUpload: "Re-upload",
  369. uploadFailed: "Upload failed, please try again",
  370. modalTitle: "Notice",
  371. deleteConfirm: "Are you sure you want to delete this item?",
  372. terminatedRemove: "Removal cancelled",
  373. removeSuccess: "Removed successfully",
  374. previewFailed: "Failed to preview image",
  375. notAllowedExt: "Files with {ext} format are not allowed",
  376. noAction: "Please configure upload address"
  377. },
  378. uVerificationCode: {
  379. startText: "Get Code",
  380. changeText: "Retry in Xs",
  381. endText: "Retry"
  382. },
  383. uSection: {
  384. subTitle: "More"
  385. },
  386. uSelect: {
  387. cancelText: "Cancel",
  388. confirmText: "Confirm"
  389. },
  390. uSearch: {
  391. placeholder: "Please enter keywords",
  392. actionText: "Search"
  393. },
  394. uNoNetwork: {
  395. tips: "Ooops, network disconnected",
  396. checkNetwork: "Please check network or go to",
  397. setting: "Settings",
  398. retry: "Retry",
  399. noConnection: "No network connection",
  400. connected: "Network connected"
  401. },
  402. uReadMore: {
  403. closeText: "Read More",
  404. openText: "Collapse"
  405. },
  406. uPagination: {
  407. prevText: "Prev",
  408. nextText: "Next"
  409. },
  410. uPicker: {
  411. cancelText: "Cancel",
  412. confirmText: "Confirm"
  413. },
  414. uModal: {
  415. title: "Notice",
  416. content: "Content",
  417. confirmText: "Confirm",
  418. cancelText: "Cancel"
  419. },
  420. uLoadmore: {
  421. loadmore: "Load more",
  422. loading: "Loading...",
  423. nomore: "No more"
  424. },
  425. uLink: {
  426. mpTips: "Link copied, please open it in browser"
  427. },
  428. uKeyboard: {
  429. cancelText: "Cancel",
  430. confirmText: "Confirm",
  431. number: "Number Keyboard",
  432. idCard: "ID Card Keyboard",
  433. plate: "Plate Keyboard"
  434. },
  435. uInput: {
  436. placeholder: "Please enter"
  437. },
  438. uCalendar: {
  439. startText: "Start",
  440. endText: "End",
  441. toolTip: "Select date",
  442. outOfRange: "Date out of range",
  443. year: "",
  444. month: "",
  445. sun: "Sun",
  446. mon: "Mon",
  447. tue: "Tue",
  448. wed: "Wed",
  449. thu: "Thu",
  450. fri: "Fri",
  451. sat: "Sat",
  452. confirmText: "Confirm",
  453. to: " to "
  454. },
  455. uEmpty: {
  456. car: "Shopping cart is empty",
  457. page: "Page not found",
  458. search: "No search results",
  459. address: "No shipping address",
  460. wifi: "No WiFi",
  461. order: "No orders",
  462. coupon: "No coupons",
  463. favor: "No favorites",
  464. permission: "No permission",
  465. history: "No history",
  466. news: "No news",
  467. message: "No messages",
  468. list: "No list",
  469. data: "No data"
  470. },
  471. uCountDown: {
  472. day: "days",
  473. hour: "hours",
  474. minute: "minutes",
  475. second: "Second"
  476. },
  477. uFullScreen: {
  478. title: "New Version Available",
  479. upgrade: "Upgrade"
  480. }
  481. };
  482. const localePack = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
  483. __proto__: null,
  484. enUS,
  485. zhCN
  486. }, Symbol.toStringTag, { value: "Module" }));
  487. const THEME_STORAGE_KEY = "uview-pro-theme";
  488. const DARK_MODE_STORAGE_KEY = "uview-pro-dark-mode";
  489. const LOCALE_STORAGE_KEY = "uview-pro-locale";
  490. const DEFAULT_LIGHT_TOKENS = ((_a = defaultThemes[0]) == null ? void 0 : _a.color) || {};
  491. const DEFAULT_DARK_TOKENS = ((_b = defaultThemes[0]) == null ? void 0 : _b.darkColor) || {};
  492. const STRUCTURAL_TOKENS = /* @__PURE__ */ new Set([
  493. "bgColor",
  494. "bgWhite",
  495. "bgGrayLight",
  496. "bgGrayDark",
  497. "bgBlack",
  498. "borderColor",
  499. "lightColor",
  500. "mainColor",
  501. "contentColor",
  502. "tipsColor",
  503. "whiteColor",
  504. "blackColor",
  505. "dividerColor",
  506. "maskColor",
  507. "shadowColor"
  508. ]);
  509. class ConfigProvider {
  510. constructor() {
  511. this.themesRef = ref([]);
  512. this.currentThemeRef = ref(null);
  513. this.darkModeRef = ref(config.defaultDarkMode);
  514. this.cssVarsRef = ref({});
  515. this.localesRef = ref([]);
  516. this.currentLocaleRef = ref(null);
  517. this.baseColorTokens = DEFAULT_LIGHT_TOKENS;
  518. this.baseDarkColorTokens = DEFAULT_DARK_TOKENS;
  519. this.debug = false;
  520. this.systemDarkModeMediaQuery = null;
  521. this.lastAppliedCssKeys = [];
  522. this.interval = 0;
  523. this.initSystemDarkModeListener();
  524. }
  525. /**
  526. * 初始化系统暗黑模式监听器
  527. * 支持 H5、App、小程序等平台
  528. */
  529. initSystemDarkModeListener() {
  530. try {
  531. if (typeof window !== "undefined" && window.matchMedia) {
  532. this.systemDarkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
  533. const listener = () => {
  534. if (this.darkModeRef.value === "auto") {
  535. this.applyTheme(this.currentThemeRef.value);
  536. }
  537. };
  538. if (this.systemDarkModeMediaQuery.addEventListener) {
  539. this.systemDarkModeMediaQuery.addEventListener("change", listener);
  540. } else if (this.systemDarkModeMediaQuery.addListener) {
  541. this.systemDarkModeMediaQuery.addListener(listener);
  542. }
  543. }
  544. } catch (e) {
  545. if (this.debug)
  546. console.warn("[ConfigProvider] H5 system dark mode listener failed", e);
  547. }
  548. try {
  549. if (typeof uni !== "undefined" && typeof uni.onThemeChange === "function") {
  550. uni.onThemeChange((res) => {
  551. console.log("[ConfigProvider] system theme changed", res);
  552. if (this.darkModeRef.value === "auto") {
  553. this.applyTheme(this.currentThemeRef.value);
  554. }
  555. });
  556. }
  557. } catch (e) {
  558. if (this.debug)
  559. console.warn("[ConfigProvider] uni-app system dark mode listener failed", e);
  560. }
  561. this.initAppEvent();
  562. }
  563. /**
  564. * App 平台事件监听
  565. * 经测试 uni.onThemeChange 在 App 平台目前没生效,暂时只能通过定时检查
  566. */
  567. initAppEvent() {
  568. try {
  569. if (this.interval)
  570. clearInterval(this.interval);
  571. this.interval = setInterval(() => {
  572. if (this.darkModeRef.value === "auto") {
  573. this.applyTheme(this.currentThemeRef.value);
  574. }
  575. }, 5e3);
  576. } catch (e) {
  577. if (this.debug)
  578. console.warn("[ConfigProvider] setInterval failed", e);
  579. }
  580. }
  581. /**
  582. * 检测当前是否应该使用暗黑模式
  583. */
  584. isSystemDarkMode() {
  585. try {
  586. if (this.systemDarkModeMediaQuery) {
  587. return this.systemDarkModeMediaQuery.matches;
  588. }
  589. } catch (e) {
  590. if (this.debug)
  591. console.warn("[ConfigProvider] matchMedia check failed", e);
  592. }
  593. try {
  594. return getSystemDarkMode() === "dark";
  595. } catch (e) {
  596. if (this.debug)
  597. console.warn("[ConfigProvider] native system theme check failed", e);
  598. return false;
  599. }
  600. }
  601. /**
  602. * 初始化主题系统
  603. * @param themes 可用主题数组
  604. * @param defaultTheme 可选默认主题名
  605. */
  606. initTheme(themes, defaultConfig, isForce) {
  607. const normalizedThemes = this.normalizeThemes(themes);
  608. if (!normalizedThemes.length) {
  609. console.warn("[ConfigProvider] init called with empty themes");
  610. return;
  611. }
  612. if (defaultConfig) {
  613. if (typeof defaultConfig === "string") {
  614. config.defaultTheme = defaultConfig || config.defaultTheme;
  615. } else if (typeof defaultConfig === "object") {
  616. const { defaultTheme, defaultDarkMode } = defaultConfig;
  617. config.defaultTheme = defaultTheme || config.defaultTheme;
  618. config.defaultDarkMode = defaultDarkMode || config.defaultDarkMode;
  619. }
  620. }
  621. this.themesRef.value = normalizedThemes.slice();
  622. const saved = this.readStorage(THEME_STORAGE_KEY);
  623. let initialName = saved || config.defaultTheme || this.themesRef.value[0].name;
  624. if (isForce && config.defaultTheme)
  625. initialName = config.defaultTheme;
  626. let found = this.themesRef.value.find((t) => t.name === initialName);
  627. if (!found)
  628. found = this.themesRef.value.find((t) => t.name === config.defaultTheme);
  629. if (!found)
  630. found = this.themesRef.value[0];
  631. this.currentThemeRef.value = found;
  632. this.initDarkMode(config.defaultDarkMode, isForce);
  633. this.applyTheme(found);
  634. if (this.debug)
  635. console.log("[ConfigProvider] initialized, theme=", found.name, "darkMode=", this.darkModeRef.value);
  636. return this;
  637. }
  638. /**
  639. * 初始化暗黑模式设置
  640. * @param darkMode
  641. */
  642. initDarkMode(darkMode, isForce) {
  643. const savedDarkMode = this.readStorage(DARK_MODE_STORAGE_KEY);
  644. let darkModeValue = savedDarkMode || darkMode || config.defaultDarkMode;
  645. if (isForce && darkMode)
  646. darkModeValue = darkMode;
  647. this.darkModeRef.value = darkModeValue;
  648. }
  649. /**
  650. * 初始化国际化数据
  651. * @param locales 可选的 locale 列表(对象数组,包含 name 字段)
  652. * @param defaultLocaleName 可选默认 locale 名称
  653. */
  654. initLocales(locales, defaultLocaleName, isForce) {
  655. const normalized = this.normalizeLocales(locales);
  656. if (!normalized.length) {
  657. if (this.debug)
  658. console.warn("[ConfigProvider] initLocales called with empty locales");
  659. return;
  660. }
  661. this.localesRef.value = normalized.slice();
  662. const saved = this.readStorage(LOCALE_STORAGE_KEY);
  663. let initialName = saved || defaultLocaleName || config.defaultLocale;
  664. if (isForce && defaultLocaleName)
  665. initialName = defaultLocaleName;
  666. let found = this.localesRef.value.find((l) => l.name === initialName);
  667. if (!found)
  668. found = this.localesRef.value.find((l) => l.name === config.defaultLocale);
  669. if (!found)
  670. found = this.localesRef.value[0];
  671. this.currentLocaleRef.value = found;
  672. if (this.debug)
  673. console.log("[ConfigProvider] locales initialized, locale=", found == null ? void 0 : found.name);
  674. return this;
  675. }
  676. /**
  677. * 归一化 locale 配置,保证始终至少有一个默认 locale
  678. */
  679. normalizeLocales(locales) {
  680. let builtinList = [];
  681. try {
  682. builtinList = Object.values(localePack || {}).filter((v) => v && typeof v === "object");
  683. } catch (e) {
  684. if (this.debug)
  685. console.warn("[ConfigProvider] normalizeLocales read builtin failed", e);
  686. }
  687. if (!Array.isArray(locales) || !locales.length) {
  688. return builtinList.slice();
  689. }
  690. const map = /* @__PURE__ */ new Map();
  691. builtinList.forEach((item) => {
  692. if (item && item.name) {
  693. map.set(item.name, { ...item || {} });
  694. }
  695. });
  696. locales.forEach((loc) => {
  697. if (!loc || !loc.name)
  698. return;
  699. const existing = map.get(loc.name);
  700. if (!existing) {
  701. map.set(loc.name, { ...loc || {} });
  702. return;
  703. }
  704. const merged = { ...existing };
  705. Object.keys(loc).forEach((k) => {
  706. const v = loc[k];
  707. if (v != null && typeof v === "object" && !Array.isArray(v) && typeof merged[k] === "object") {
  708. merged[k] = { ...merged[k] || {}, ...v || {} };
  709. } else {
  710. merged[k] = v;
  711. }
  712. });
  713. map.set(loc.name, merged);
  714. });
  715. return Array.from(map.values());
  716. }
  717. /**
  718. * 获取所有可用 locale
  719. */
  720. getLocales() {
  721. return this.localesRef.value.slice();
  722. }
  723. /**
  724. * 获取当前 locale 对象
  725. */
  726. getCurrentLocale() {
  727. return this.currentLocaleRef.value;
  728. }
  729. /**
  730. * 切换 locale 并持久化
  731. */
  732. setLocale(localeName) {
  733. if (!this.localesRef.value || this.localesRef.value.length === 0) {
  734. console.warn("[ConfigProvider] setLocale called but locales list empty");
  735. return;
  736. }
  737. const locale = this.localesRef.value.find((l) => l.name === localeName);
  738. if (!locale) {
  739. console.warn("[ConfigProvider] locale not found:", localeName);
  740. return;
  741. }
  742. this.currentLocaleRef.value = locale;
  743. this.writeStorage(LOCALE_STORAGE_KEY, localeName);
  744. if (this.debug)
  745. console.log("[ConfigProvider] setLocale ->", localeName);
  746. }
  747. /**
  748. * 翻译函数
  749. * 支持 key 路径,例如 'calendar.placeholder'
  750. * replacements 支持数组或对象替换占位符 {0} 或 {name}
  751. */
  752. t(key, replacements, localeName) {
  753. try {
  754. if (!Array.isArray(this.localesRef.value) || this.localesRef.value.length === 0) {
  755. this.initLocales();
  756. }
  757. } catch (e) {
  758. if (this.debug)
  759. console.warn("[ConfigProvider] lazy initLocales failed", e);
  760. }
  761. const localeObj = localeName && this.localesRef.value.find((l) => l.name === localeName) || this.currentLocaleRef.value;
  762. if (!localeObj)
  763. return key;
  764. const parts = key.split(".");
  765. let cur = localeObj;
  766. for (let i = 0; i < parts.length; i++) {
  767. if (cur == null)
  768. break;
  769. cur = cur[parts[i]];
  770. }
  771. let text = typeof cur === "string" ? cur : key;
  772. if (replacements != null) {
  773. if (Array.isArray(replacements)) {
  774. replacements.forEach((val, idx) => {
  775. text = text.split(`{${idx}}`).join(String(val));
  776. });
  777. } else if (typeof replacements === "object") {
  778. Object.keys(replacements).forEach((k) => {
  779. text = text.split(`{${k}}`).join(String(replacements[k]));
  780. });
  781. }
  782. }
  783. return text;
  784. }
  785. /**
  786. * 获取所有可用主题
  787. */
  788. getThemes() {
  789. return this.themesRef.value.slice();
  790. }
  791. /**
  792. * 获取当前主题
  793. */
  794. getCurrentTheme() {
  795. return this.currentThemeRef.value;
  796. }
  797. /**
  798. * 切换主题并持久化
  799. */
  800. setTheme(themeName) {
  801. if (!this.themesRef.value || this.themesRef.value.length === 0) {
  802. console.warn("[ConfigProvider] setTheme called but themes list empty");
  803. return;
  804. }
  805. const theme = this.themesRef.value.find((t) => t.name === themeName);
  806. if (!theme) {
  807. console.warn("[ConfigProvider] theme not found:", themeName);
  808. return;
  809. }
  810. this.currentThemeRef.value = theme;
  811. this.applyTheme(theme);
  812. this.writeStorage(THEME_STORAGE_KEY, themeName);
  813. if (this.debug)
  814. console.log("[ConfigProvider] setTheme ->", themeName);
  815. }
  816. /**
  817. * 运行时更新当前主题颜色并应用(不持久化)
  818. * @param colors 主题颜色键值,支持部分更新
  819. */
  820. setThemeColor(colors) {
  821. if (!colors || Object.keys(colors).length === 0)
  822. return;
  823. if (!this.currentThemeRef.value) {
  824. console.warn("[ConfigProvider] setThemeColor called but no current theme");
  825. return;
  826. }
  827. const mode = this.getActiveMode();
  828. if (mode === "dark") {
  829. const existing = this.currentThemeRef.value.darkColor || {};
  830. this.currentThemeRef.value.darkColor = {
  831. ...existing,
  832. ...colors
  833. };
  834. } else {
  835. const existing = this.currentThemeRef.value.color || {};
  836. this.currentThemeRef.value.color = {
  837. ...existing,
  838. ...colors
  839. };
  840. }
  841. this.applyTheme(this.currentThemeRef.value);
  842. if (this.debug)
  843. console.log("[ConfigProvider] setThemeColor ->", colors);
  844. }
  845. /**
  846. * 获取当前暗黑模式设置
  847. */
  848. getDarkMode() {
  849. return this.darkModeRef.value;
  850. }
  851. /**
  852. * 设置暗黑模式
  853. * @param mode 'auto' (跟随系统) | 'light' (强制亮色) | 'dark' (强制暗黑)
  854. */
  855. setDarkMode(mode) {
  856. this.darkModeRef.value = mode;
  857. this.writeStorage(DARK_MODE_STORAGE_KEY, mode);
  858. this.applyTheme(this.currentThemeRef.value);
  859. if (this.debug)
  860. console.log("[ConfigProvider] setDarkMode ->", mode);
  861. }
  862. /**
  863. * 检查当前是否处于暗黑模式
  864. */
  865. isInDarkMode() {
  866. const mode = this.darkModeRef.value;
  867. if (mode === "dark")
  868. return true;
  869. if (mode === "light")
  870. return false;
  871. return this.isSystemDarkMode();
  872. }
  873. /**
  874. * 归一化主题配置,保证始终至少有一个默认主题
  875. */
  876. normalizeThemes(themes) {
  877. if (Array.isArray(themes) && themes.length) {
  878. return this.mergeThemes(defaultThemes, themes);
  879. }
  880. return defaultThemes.slice();
  881. }
  882. mergeThemes(...lists) {
  883. const map = /* @__PURE__ */ new Map();
  884. lists.filter((list) => Array.isArray(list) && list.length > 0).forEach((list) => {
  885. list.forEach((theme) => {
  886. const normalized = this.ensureDarkVariant({
  887. ...theme,
  888. color: this.applyColorFallbacks(theme.color),
  889. darkColor: theme.darkColor ? { ...theme.darkColor } : void 0,
  890. css: theme.css ? { ...theme.css } : void 0,
  891. darkCss: theme.darkCss ? { ...theme.darkCss } : void 0
  892. });
  893. map.set(normalized.name, normalized);
  894. });
  895. });
  896. return Array.from(map.values());
  897. }
  898. ensureDarkVariant(theme) {
  899. const finalDark = this.buildDarkPalette(theme);
  900. return {
  901. ...theme,
  902. darkColor: this.applyDarkFallbacks(finalDark)
  903. };
  904. }
  905. buildDarkPalette(theme) {
  906. const provided = theme.darkColor || {};
  907. const generated = this.generateDarkFromLight(theme.color || {}, provided);
  908. return {
  909. ...generated,
  910. ...provided
  911. };
  912. }
  913. /**
  914. * 应用亮色主题
  915. */
  916. applyColorFallbacks(color2) {
  917. return {
  918. ...this.baseColorTokens || {},
  919. ...color2 || {}
  920. };
  921. }
  922. /**
  923. * 应用暗黑主题
  924. */
  925. applyDarkFallbacks(color2) {
  926. return {
  927. ...this.baseDarkColorTokens || {},
  928. ...color2 || {}
  929. };
  930. }
  931. filterNonStructuralTokens(palette) {
  932. const result = {};
  933. Object.entries(palette || {}).forEach(([key, value]) => {
  934. if (!this.isStructuralToken(key)) {
  935. result[key] = value;
  936. }
  937. });
  938. return result;
  939. }
  940. generateDarkFromLight(palette, provided) {
  941. const result = {};
  942. const nonStructural = this.filterNonStructuralTokens(palette);
  943. Object.entries(nonStructural).forEach(([key, value]) => {
  944. var _a2;
  945. if (typeof value !== "string")
  946. return;
  947. if (provided && Object.prototype.hasOwnProperty.call(provided, key)) {
  948. return;
  949. }
  950. const fallback = (_a2 = this.baseDarkColorTokens) == null ? void 0 : _a2[key];
  951. result[key] = this.createDarkVariantFromLight(value, fallback);
  952. });
  953. return result;
  954. }
  955. createDarkVariantFromLight(color2, fallback) {
  956. const normalized = this.normalizeHex(color2);
  957. const fallbackHex = fallback ? this.normalizeHex(fallback) : null;
  958. if (normalized && fallbackHex) {
  959. return this.mixHex(normalized, fallbackHex, 0.6);
  960. }
  961. if (fallbackHex)
  962. return fallbackHex;
  963. return normalized || color2;
  964. }
  965. normalizeHex(color2) {
  966. if (!color2)
  967. return null;
  968. const hex = color2.trim();
  969. if (/^#([0-9a-fA-F]{6})$/.test(hex))
  970. return hex.toLowerCase();
  971. return null;
  972. }
  973. mixHex(fromHex, toHex, ratio) {
  974. const from = this.hexToRgb(fromHex);
  975. const to = this.hexToRgb(toHex);
  976. if (!from || !to)
  977. return toHex;
  978. const clamp = (val) => Math.min(255, Math.max(0, Math.round(val)));
  979. const r = clamp(from.r * (1 - ratio) + to.r * ratio);
  980. const g = clamp(from.g * (1 - ratio) + to.g * ratio);
  981. const b = clamp(from.b * (1 - ratio) + to.b * ratio);
  982. return this.rgbToHex(r, g, b);
  983. }
  984. hexToRgb(hex) {
  985. const match = /^#([0-9a-fA-F]{6})$/.exec(hex);
  986. if (!match)
  987. return null;
  988. return {
  989. r: parseInt(match[1].slice(0, 2), 16),
  990. g: parseInt(match[1].slice(2, 4), 16),
  991. b: parseInt(match[1].slice(4, 6), 16)
  992. };
  993. }
  994. rgbToHex(r, g, b) {
  995. const toHex = (val) => val.toString(16).padStart(2, "0");
  996. return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
  997. }
  998. isStructuralToken(token) {
  999. return STRUCTURAL_TOKENS.has(token);
  1000. }
  1001. /**
  1002. * 运行时同步主题颜色($u.color)
  1003. * 更新响应式 color 对象,确保所有使用 $u.color 的地方都能响应式更新
  1004. */
  1005. syncRuntimeTheme(palette) {
  1006. var _a2;
  1007. try {
  1008. const defaultPalette = this.getActiveMode() === "dark" ? this.baseDarkColorTokens : this.baseColorTokens;
  1009. const mergedPalette = {
  1010. ...defaultPalette,
  1011. ...palette
  1012. };
  1013. Object.keys(mergedPalette).forEach((key) => {
  1014. const value = mergedPalette[key];
  1015. if (value != null) {
  1016. color[key] = value;
  1017. }
  1018. });
  1019. if (typeof uni !== "undefined" && ((_a2 = uni == null ? void 0 : uni.$u) == null ? void 0 : _a2.color)) {
  1020. uni.$u.color = color;
  1021. }
  1022. } catch (e) {
  1023. if (this.debug)
  1024. console.warn("[ConfigProvider] sync runtime theme failed", e);
  1025. }
  1026. }
  1027. /**
  1028. * 获取当前激活的模式
  1029. */
  1030. getActiveMode() {
  1031. return this.isInDarkMode() ? "dark" : "light";
  1032. }
  1033. /**
  1034. * 获取当前主题的配色方案
  1035. */
  1036. getPaletteForMode(theme, mode) {
  1037. if (mode === "dark") {
  1038. return theme.darkColor && Object.keys(theme.darkColor).length ? theme.darkColor : theme.color || {};
  1039. }
  1040. return theme.color || {};
  1041. }
  1042. /**
  1043. * 获取当前主题的CSS变量覆盖
  1044. */
  1045. getCssOverrides(theme, mode) {
  1046. if (mode === "dark") {
  1047. return (theme.darkCss && Object.keys(theme.darkCss).length ? theme.darkCss : theme.css) || {};
  1048. }
  1049. return theme.css || {};
  1050. }
  1051. /**
  1052. * 读取Storage key
  1053. */
  1054. readStorage(key) {
  1055. try {
  1056. if (typeof uni === "undefined" || typeof uni.getStorageSync !== "function")
  1057. return null;
  1058. const value = uni.getStorageSync(key);
  1059. return value ?? null;
  1060. } catch (e) {
  1061. if (this.debug)
  1062. console.warn("[ConfigProvider] failed to read storage", e);
  1063. return null;
  1064. }
  1065. }
  1066. /**
  1067. * 写入Storage key value
  1068. */
  1069. writeStorage(key, value) {
  1070. try {
  1071. if (typeof uni === "undefined" || typeof uni.setStorageSync !== "function")
  1072. return;
  1073. uni.setStorageSync(key, value);
  1074. } catch (e) {
  1075. if (this.debug)
  1076. console.warn("[ConfigProvider] failed to write storage", e);
  1077. }
  1078. }
  1079. /**
  1080. * 更新文档主题模式 H5
  1081. */
  1082. updateDocumentMode(mode) {
  1083. if (typeof document === "undefined" || !document.documentElement)
  1084. return;
  1085. const root = document.documentElement;
  1086. root.dataset.uThemeMode = mode;
  1087. root.classList.remove("u-theme-light", "u-theme-dark");
  1088. root.classList.add(`u-theme-${mode}`);
  1089. }
  1090. /**
  1091. * 转换为 CSS 变量名称
  1092. */
  1093. toCssVarName(key, prefix = "--u") {
  1094. const types = config.type;
  1095. if (types.some((type) => key.startsWith(type))) {
  1096. prefix += "-type";
  1097. }
  1098. const kebab = key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
  1099. return `${prefix}-${kebab}`;
  1100. }
  1101. /**
  1102. * 添加 RGB 值
  1103. */
  1104. attachRgbVar(target, varName, value) {
  1105. if (typeof value !== "string")
  1106. return;
  1107. const hex = value.startsWith("#") ? value.slice(1) : "";
  1108. if (!/^[0-9a-fA-F]{6}$/.test(hex))
  1109. return;
  1110. const r = parseInt(hex.slice(0, 2), 16);
  1111. const g = parseInt(hex.slice(2, 4), 16);
  1112. const b = parseInt(hex.slice(4, 6), 16);
  1113. target[`${varName}-rgb`] = `${r}, ${g}, ${b}`;
  1114. }
  1115. /**
  1116. * 构建 CSS 变量映射表
  1117. * 生成格式:
  1118. */
  1119. buildCssVarMap(theme, mode) {
  1120. const map = {
  1121. "--u-theme-mode": mode
  1122. };
  1123. const palette = this.getPaletteForMode(theme, mode);
  1124. const cssOverrides = this.getCssOverrides(theme, mode);
  1125. const applyEntry = (key, value) => {
  1126. if (value == null)
  1127. return;
  1128. const strValue = String(value);
  1129. if (key.startsWith("--")) {
  1130. map[key] = strValue;
  1131. this.attachRgbVar(map, key, strValue);
  1132. return;
  1133. }
  1134. const cssVarName = this.toCssVarName(key);
  1135. map[cssVarName] = strValue;
  1136. this.attachRgbVar(map, cssVarName, strValue);
  1137. };
  1138. Object.entries(palette || {}).forEach(([key, value]) => applyEntry(key, value));
  1139. Object.entries(cssOverrides || {}).forEach(([key, value]) => applyEntry(key, value));
  1140. return map;
  1141. }
  1142. /**
  1143. * 刷新 CSS 变量 H5
  1144. */
  1145. flushCssVars(vars) {
  1146. if (typeof document === "undefined" || !document.documentElement)
  1147. return;
  1148. const root = document.documentElement;
  1149. this.lastAppliedCssKeys.forEach((key) => {
  1150. root.style.removeProperty(key);
  1151. });
  1152. Object.entries(vars).forEach(([key, value]) => {
  1153. root.style.setProperty(key, value);
  1154. });
  1155. this.lastAppliedCssKeys = Object.keys(vars);
  1156. }
  1157. /**
  1158. * 将主题应用到运行时:
  1159. * - 1) 调用 uni.$u.setColor(theme.color) 如果存在
  1160. * - 2) 在 H5 环境中,将 css map 注入到 document.documentElement 的 CSS 变量中
  1161. * - 3) 支持暗黑模式:根据 darkColor 或 color 应用相应的颜色
  1162. */
  1163. applyTheme(theme) {
  1164. if (!theme)
  1165. return;
  1166. const mode = this.getActiveMode();
  1167. const palette = this.getPaletteForMode(theme, mode);
  1168. this.syncRuntimeTheme(palette);
  1169. const cssVarMap = this.buildCssVarMap(theme, mode);
  1170. this.cssVarsRef.value = cssVarMap;
  1171. this.flushCssVars(cssVarMap);
  1172. this.updateDocumentMode(mode);
  1173. }
  1174. }
  1175. const configProvider = new ConfigProvider();
  1176. const zIndex = {
  1177. tabbar: 998
  1178. };
  1179. const originalConsole = {
  1180. log: console.log,
  1181. info: console.info,
  1182. warn: console.warn,
  1183. error: console.error,
  1184. debug: console.debug,
  1185. trace: console.trace,
  1186. table: console.table,
  1187. time: console.time,
  1188. timeEnd: console.timeEnd,
  1189. group: console.group,
  1190. groupEnd: console.groupEnd,
  1191. groupCollapsed: console.groupCollapsed,
  1192. assert: console.assert,
  1193. clear: console.clear,
  1194. count: console.count,
  1195. countReset: console.countReset
  1196. };
  1197. Object.keys(originalConsole).forEach((key) => {
  1198. const methodKey = key;
  1199. if (!originalConsole[methodKey]) {
  1200. originalConsole[methodKey] = () => {
  1201. };
  1202. }
  1203. });
  1204. configProvider.themesRef;
  1205. configProvider.currentThemeRef;
  1206. configProvider.darkModeRef;
  1207. ({
  1208. /** z-index层级 */
  1209. zIndex: { default: zIndex.tabbar }
  1210. });
  1211. Promise.resolve("./app.css.js").then(() => {
  1212. });