@ybais/create-ybais-app 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +21 -0
  2. package/dist/index.js +371 -0
  3. package/package.json +34 -0
  4. package/template/.cursor/rules/ybais-app.mdc +21 -0
  5. package/template/.cursor/skills/ybais-app/SKILL.md +140 -0
  6. package/template/.cursor/skills/ybais-app/reference.md +180 -0
  7. package/template/.env +2 -0
  8. package/template/.env.prod +8 -0
  9. package/template/.env.test +8 -0
  10. package/template/.mise/tasks/build +107 -0
  11. package/template/.oxlintrc.json +22 -0
  12. package/template/AGENTS.md +25 -0
  13. package/template/README.md +251 -0
  14. package/template/dist/assets/css/index-bQrzgOlr.css +1 -0
  15. package/template/dist/assets/js/dist-CX33qWwE.js +2 -0
  16. package/template/dist/assets/js/dist-legacy-BnxRxjXx.js +1 -0
  17. package/template/dist/assets/js/index-BNqkt-js.js +2 -0
  18. package/template/dist/assets/js/index-legacy-DPO2FCfH.js +1 -0
  19. package/template/dist/assets/js/polyfills-Dsaw-v00.js +1 -0
  20. package/template/dist/assets/js/polyfills-legacy-DyZJWH2U.js +1 -0
  21. package/template/dist/assets/js/rolldown-runtime-LhxUO5vB.js +1 -0
  22. package/template/dist/assets/js/rolldown-runtime-legacy-BptCTa2h.js +1 -0
  23. package/template/dist/assets/js/routes-D2ibRZk6.js +1 -0
  24. package/template/dist/assets/js/routes-legacy-DGEWjKnS.js +1 -0
  25. package/template/dist/assets/js/vconsole.min-BcOaunN2.js +1 -0
  26. package/template/dist/assets/js/vconsole.min-legacy-Bub-moxG.js +1 -0
  27. package/template/dist/favicon.svg +1 -0
  28. package/template/dist/index.html +29 -0
  29. package/template/dist/version.json +4 -0
  30. package/template/index.html +19 -0
  31. package/template/mise.toml +11 -0
  32. package/template/package.json +60 -0
  33. package/template/plugins/app-version.ts +88 -0
  34. package/template/pnpm-lock.yaml +3359 -0
  35. package/template/pnpm-workspace.yaml +7 -0
  36. package/template/postcss.config.js +24 -0
  37. package/template/public/favicon.svg +1 -0
  38. package/template/scripts/run-build.mjs +5 -0
  39. package/template/scripts/setup-mise.mjs +47 -0
  40. package/template/scripts/with-mise.mjs +107 -0
  41. package/template/src/api/alova.d.ts +7 -0
  42. package/template/src/api/alova.ts +50 -0
  43. package/template/src/api/index.ts +8 -0
  44. package/template/src/api/qiniu.ts +8 -0
  45. package/template/src/api/uploader.ts +25 -0
  46. package/template/src/components/NotFoundComponent/index.tsx +48 -0
  47. package/template/src/components/RouterErrorComponent/index.tsx +27 -0
  48. package/template/src/components/index.ts +2 -0
  49. package/template/src/lib/README.md +17 -0
  50. package/template/src/main.tsx +64 -0
  51. package/template/src/routeTree.gen.ts +59 -0
  52. package/template/src/router.ts +10 -0
  53. package/template/src/routes/__root.tsx +10 -0
  54. package/template/src/routes/index.tsx +39 -0
  55. package/template/src/styles/index.css +42 -0
  56. package/template/src/styles/variant.css +18 -0
  57. package/template/src/styles/webview-compat.css +49 -0
  58. package/template/src/utils/cn.ts +63 -0
  59. package/template/src/vite-env.d.ts +41 -0
  60. package/template/tailwind.config.js +14 -0
  61. package/template/tsconfig.app.json +37 -0
  62. package/template/tsconfig.json +7 -0
  63. package/template/tsconfig.node.json +28 -0
  64. package/template/vite.config.ts +131 -0
@@ -0,0 +1,7 @@
1
+ # 单包应用也可使用此文件声明 pnpm 策略(非 monorepo packages 列表)
2
+ allowBuilds:
3
+ core-js: true
4
+
5
+ minimumReleaseAgeExclude:
6
+ - "@vitejs/plugin-legacy@8.2.2"
7
+ - sonner@2.0.8
@@ -0,0 +1,24 @@
1
+ export default {
2
+ plugins: {
3
+ // 0. Tailwind CSS 必须置于顶部优先编译
4
+ tailwindcss: {},
5
+ // 1. 自动添加浏览器前缀(iOS 12 强烈依赖 -webkit- 前缀)
6
+ autoprefixer: {
7
+ overrideBrowserslist: [
8
+ "iOS >= 12",
9
+ "Android >= 7",
10
+ "Chrome >= 55",
11
+ "Safari >= 12",
12
+ ],
13
+ },
14
+ // 2. 将 px 自动按比例转换为 vw 实现移动端自适应
15
+ "postcss-px-to-viewport-8-plugin": {
16
+ viewportWidth: 375, // 设计稿宽度(以 375px 移动端逻辑像素为基准)
17
+ unitPrecision: 5, // 保留 5 位小数
18
+ viewportUnit: "vw", // 转换目标单位
19
+ selectorBlackList: [".ignore", ".hairlines"], // 忽略转换的 CSS 类名
20
+ minPixelValue: 1, // 小于等于 1px 不做转换
21
+ mediaQuery: false, // 是否在 @media 查询中转换
22
+ },
23
+ },
24
+ };
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { runMise } from "./with-mise.mjs";
3
+
4
+ // 自动 trust 本项目配置后执行 mise 构建任务(用户无需改 mise.toml / 手动 trust)
5
+ runMise(["run", "build"]);
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pnpm install 后轻量准备:若已安装 mise,则自动 trust + 安装工具链。
4
+ * 未安装 mise 时静默跳过(开发可用系统 Node;构建时会给出友好提示)。
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
+
13
+ function findMise() {
14
+ const fromPath = spawnSync("mise", ["--version"], {
15
+ encoding: "utf8",
16
+ shell: process.platform === "win32",
17
+ });
18
+ if (fromPath.status === 0) return "mise";
19
+
20
+ for (const candidate of [
21
+ "/opt/homebrew/bin/mise",
22
+ "/usr/local/bin/mise",
23
+ path.join(process.env.HOME ?? "", ".local/bin/mise"),
24
+ ]) {
25
+ if (candidate && fs.existsSync(candidate)) return candidate;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ const mise = findMise();
31
+ if (!mise) {
32
+ process.exit(0);
33
+ }
34
+
35
+ spawnSync(mise, ["trust", root], {
36
+ cwd: root,
37
+ stdio: "ignore",
38
+ shell: process.platform === "win32",
39
+ });
40
+
41
+ spawnSync(mise, ["install"], {
42
+ cwd: root,
43
+ stdio: "inherit",
44
+ shell: process.platform === "win32",
45
+ });
46
+
47
+ process.exit(0);
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 无感调用 mise:自动信任本项目 mise.toml,再执行给定子命令。
4
+ * 用户日常只需跑 pnpm scripts,无需手动 mise trust / 改配置。
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const root = path.resolve(path.dirname(__filename), "..");
13
+
14
+ function findMise() {
15
+ const fromPath = spawnSync("mise", ["--version"], {
16
+ encoding: "utf8",
17
+ shell: process.platform === "win32",
18
+ });
19
+ if (fromPath.status === 0) {
20
+ return "mise";
21
+ }
22
+
23
+ const candidates = [
24
+ "/opt/homebrew/bin/mise",
25
+ "/usr/local/bin/mise",
26
+ path.join(process.env.HOME ?? "", ".local/bin/mise"),
27
+ ];
28
+ for (const candidate of candidates) {
29
+ if (candidate && fs.existsSync(candidate)) {
30
+ return candidate;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function printMiseMissing() {
37
+ console.error(`
38
+ [ybais] 未检测到 mise,但本项目的 \`pnpm build\` 依赖它(按 Git 分支选择 test/prod)。
39
+
40
+ 请安装后重试(安装后无需改任何 mise 配置,直接 pnpm build 即可):
41
+
42
+ curl https://mise.run | sh
43
+
44
+ 或 macOS:
45
+
46
+ brew install mise
47
+
48
+ 安装后重新打开终端,再执行:
49
+
50
+ pnpm build
51
+ `);
52
+ }
53
+
54
+ /**
55
+ * @param {string[]} miseArgs mise 子命令参数,如 ["run", "build"]
56
+ * @param {{ installTools?: boolean }} [opts]
57
+ */
58
+ export function runMise(miseArgs, opts = {}) {
59
+ const mise = findMise();
60
+ if (!mise) {
61
+ printMiseMissing();
62
+ process.exit(1);
63
+ }
64
+
65
+ spawnSync(mise, ["trust", root], {
66
+ cwd: root,
67
+ stdio: "ignore",
68
+ shell: process.platform === "win32",
69
+ });
70
+
71
+ if (opts.installTools) {
72
+ const install = spawnSync(mise, ["install"], {
73
+ cwd: root,
74
+ stdio: "inherit",
75
+ shell: process.platform === "win32",
76
+ });
77
+ if (install.status !== 0) {
78
+ process.exit(install.status ?? 1);
79
+ }
80
+ }
81
+
82
+ const result = spawnSync(mise, miseArgs, {
83
+ cwd: root,
84
+ stdio: "inherit",
85
+ shell: process.platform === "win32",
86
+ });
87
+ process.exit(result.status ?? 1);
88
+ }
89
+
90
+ const isMain =
91
+ process.argv[1] !== undefined &&
92
+ path.resolve(process.argv[1]) === __filename;
93
+
94
+ if (isMain) {
95
+ const args = process.argv.slice(2);
96
+ if (args.length === 0) {
97
+ console.error(
98
+ "Usage: node scripts/with-mise.mjs [--install-tools] <mise-args...>",
99
+ );
100
+ process.exit(1);
101
+ }
102
+ if (args[0] === "--install-tools") {
103
+ runMise(args.slice(1), { installTools: true });
104
+ } else {
105
+ runMise(args, { installTools: false });
106
+ }
107
+ }
@@ -0,0 +1,7 @@
1
+ import type { MethodMeta } from "@ybais/alova";
2
+
3
+ declare module "alova" {
4
+ interface AlovaCustomTypes {
5
+ meta: MethodMeta;
6
+ }
7
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 应用侧 Alova 装配:读取 Vite env,注入 bridge / error-guard。
3
+ */
4
+ import { createYbaisAlova } from "@ybais/alova";
5
+ import { goLogin } from "@ybais/bridge";
6
+ import { reportError } from "@ybais/error-guard";
7
+
8
+ export const API_BASE_URL = import.meta.env.DEV
9
+ ? "/proxyApi"
10
+ : import.meta.env.VITE_API_BASE_URL;
11
+
12
+ export const CRYPTO_APP_KEY = import.meta.env.VITE_CRYPTO_APP_KEY;
13
+ export const CRYPTO_APP_SECRET = import.meta.env.VITE_CRYPTO_APP_SECRET;
14
+
15
+ export const alovaInstance = createYbaisAlova({
16
+ baseURL: API_BASE_URL,
17
+ cryptoAppKey: CRYPTO_APP_KEY,
18
+ cryptoAppSecret: CRYPTO_APP_SECRET,
19
+ reportError,
20
+ onAuthFail: () => {
21
+ void goLogin().catch(() => {
22
+ // 浏览器无 Bridge:忽略,由业务页自行引导登录
23
+ });
24
+ },
25
+ });
26
+
27
+ export {
28
+ ApiError,
29
+ TokenBootstrap,
30
+ applyBusinessCodeHandlers,
31
+ businessCodeHandlers,
32
+ BUSINESS_CODE_HANDLER_COOLDOWN_MS,
33
+ getToken,
34
+ isApiSuccess,
35
+ removeToken,
36
+ resetBusinessCodeHandlerCooldown,
37
+ setToken,
38
+ showApiError,
39
+ SUCCESS_CODE,
40
+ syncTokenFromUrl,
41
+ TOKEN_STORAGE_KEY,
42
+ unwrapApiResponse,
43
+ } from "@ybais/alova";
44
+
45
+ export type {
46
+ ApiResponse,
47
+ BusinessCodeHandler,
48
+ MethodMeta,
49
+ UnwrapApiResponseOptions,
50
+ } from "@ybais/alova";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 按业务模块拆分 method 函数,每个函数返回 method 实例。
3
+ * 示例:
4
+ * export const getUserInfo = (id: number) =>
5
+ * alovaInstance.Get<UserInfo>(`/user/${id}`);
6
+ */
7
+
8
+ export * from "./qiniu";
@@ -0,0 +1,8 @@
1
+ import { alovaInstance } from "@/api/alova";
2
+
3
+ export const fetchQiniuToken = () => {
4
+ return alovaInstance.Post<{
5
+ token: string;
6
+ domain: string;
7
+ }>("/api/upload/getToken");
8
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 七牛上传单例:token API + AES 来自本应用 env。
3
+ */
4
+ import { createUploader } from "@ybais/qiniu-upload";
5
+ import { fetchQiniuToken } from "@/api/qiniu";
6
+
7
+ export const uploader = createUploader({
8
+ fetchToken: () => fetchQiniuToken(),
9
+ aesKey: import.meta.env.VITE_QINIU_AES_KEY ?? "",
10
+ aesIv: import.meta.env.VITE_QINIU_AES_IV ?? "",
11
+ });
12
+
13
+ export {
14
+ compressQiniuImageUrl,
15
+ dataURLtoFile,
16
+ createUploader,
17
+ Uploader,
18
+ } from "@ybais/qiniu-upload";
19
+
20
+ export type {
21
+ CreateUploaderOptions,
22
+ FetchQiniuTokenResult,
23
+ UploadOptions,
24
+ UploadResult,
25
+ } from "@ybais/qiniu-upload";
@@ -0,0 +1,48 @@
1
+ import { goBack } from "@ybais/bridge";
2
+ import { router } from "@/router";
3
+ import { Link } from "@tanstack/react-router";
4
+
5
+ /**
6
+ * 路由未匹配时的 404 页(挂到 root `notFoundComponent`)
7
+ */
8
+ export function NotFoundComponent() {
9
+ const handleGoBack = () => {
10
+ // App 内走原生 goBack;浏览器无 Bridge 时 reject,回退到路由 history
11
+ void goBack().catch(() => {
12
+ router.history.back();
13
+ });
14
+ };
15
+
16
+ return (
17
+ <div className="min-h-screen flex flex-col items-center justify-center bg-slate-50 px-6 py-10 text-center">
18
+ <div className="w-full max-w-md rounded-2xl bg-white px-5 py-6 shadow-sm ring-1 ring-slate-200">
19
+ <p className="text-sm font-medium text-slate-400">404</p>
20
+ <h1 className="mt-2 text-lg font-semibold text-slate-900">
21
+ 页面不存在
22
+ </h1>
23
+ <p className="mt-3 text-sm leading-relaxed text-slate-600">
24
+ 你访问的页面无效或已被移除,请返回首页继续使用。
25
+ </p>
26
+
27
+ <div className="mt-6 flex flex-col sm:flex-row sm:justify-center">
28
+ <Link
29
+ to="/"
30
+ replace
31
+ className="rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-medium text-white active:opacity-80"
32
+ >
33
+ 返回首页
34
+ </Link>
35
+ <button
36
+ type="button"
37
+ className="mt-2 rounded-xl bg-slate-100 px-4 py-2.5 text-sm font-medium text-slate-800 active:opacity-80 sm:mt-0 sm:ml-2"
38
+ onClick={handleGoBack}
39
+ >
40
+ 返回上一页
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ export default NotFoundComponent;
@@ -0,0 +1,27 @@
1
+ import { type ErrorReport, FallbackPage } from "@ybais/error-guard";
2
+
3
+ export function RouterErrorComponent({
4
+ error,
5
+ reset,
6
+ }: {
7
+ error: unknown;
8
+ reset: () => void;
9
+ }) {
10
+ const err = error instanceof Error ? error : new Error(String(error));
11
+ const report: ErrorReport = {
12
+ kind: "page",
13
+ message: err.message || "路由渲染失败",
14
+ error,
15
+ stack: err.stack,
16
+ fatal: true,
17
+ timestamp: Date.now(),
18
+ };
19
+
20
+ return (
21
+ <FallbackPage
22
+ report={report}
23
+ onRetry={reset}
24
+ onReload={() => window.location.reload()}
25
+ />
26
+ );
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./RouterErrorComponent";
2
+ export * from "./NotFoundComponent";
@@ -0,0 +1,17 @@
1
+ # lib 装配说明
2
+
3
+ 可复用基础设施已拆为 `@ybais/*` 包,本目录**不再**放置实现代码。
4
+
5
+ | 能力 | 包 / 装配入口 |
6
+ |------|----------------|
7
+ | Bridge | `@ybais/bridge` / `@ybais/bridge/react` |
8
+ | Clipboard | `@ybais/clipboard` / `@ybais/clipboard/react` |
9
+ | Center toast | `@ybais/center-toast`(样式:`@ybais/center-toast/style.css`) |
10
+ | Crypto | `@ybais/crypto` |
11
+ | Error guard | `@ybais/error-guard` |
12
+ | Alova | `@ybais/alova` + 本应用 [`src/api/alova.ts`](../api/alova.ts) |
13
+ | Qiniu | `@ybais/qiniu-upload` + [`src/api/uploader.ts`](../api/uploader.ts) |
14
+ | Updater | `@ybais/updater` |
15
+ | VConsole | `@ybais/vconsole` |
16
+
17
+ 业务工具(如 `cn`)放在 `@/utils`,不要写回本目录。
@@ -0,0 +1,64 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { RouterProvider } from "@tanstack/react-router";
4
+ import { configureBridge } from "@ybais/bridge";
5
+ import {
6
+ ErrorGuardProvider,
7
+ reportDebug,
8
+ setupErrorGuard,
9
+ } from "@ybais/error-guard";
10
+ import { startUpdater } from "@ybais/updater";
11
+ import { TokenBootstrap } from "@/api/alova";
12
+ // @@vconsole
13
+ // 仅 test 静态可达,便于 prod 构建剔除整个模块
14
+ if (import.meta.env.VITE_APP_ENV === "test") {
15
+ void import("@ybais/vconsole").then((m) =>
16
+ m.setupVConsole({
17
+ appEnv: import.meta.env.VITE_APP_ENV,
18
+ gitBranch: import.meta.env.VITE_GIT_BRANCH,
19
+ isDev: import.meta.env.DEV,
20
+ }),
21
+ );
22
+ }
23
+ // @@/vconsole
24
+
25
+ import { router } from "./router";
26
+
27
+ import "./styles/index.css";
28
+ import "@ybais/center-toast/style.css";
29
+
30
+ configureBridge({
31
+ onUnavailable: (error, context) => {
32
+ reportDebug(`原生能力不可用:${context.method}`, {
33
+ source: "bridge",
34
+ error,
35
+ detail: {
36
+ method: context.method,
37
+ reason: context.reason,
38
+ params: context.params,
39
+ },
40
+ description: "浏览器预览属预期,真机 WebView 可用",
41
+ });
42
+ },
43
+ });
44
+
45
+ setupErrorGuard({
46
+ isDev: import.meta.env.DEV,
47
+ });
48
+ startUpdater({
49
+ isProd: import.meta.env.PROD,
50
+ });
51
+
52
+ const rootElement = document.getElementById("root")!;
53
+ if (!rootElement.innerHTML) {
54
+ const root = createRoot(rootElement);
55
+ root.render(
56
+ <StrictMode>
57
+ <ErrorGuardProvider>
58
+ <TokenBootstrap>
59
+ <RouterProvider router={router} />
60
+ </TokenBootstrap>
61
+ </ErrorGuardProvider>
62
+ </StrictMode>,
63
+ );
64
+ }
@@ -0,0 +1,59 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from './routes/__root'
12
+ import { Route as IndexRouteImport } from './routes/index'
13
+
14
+ const IndexRoute = IndexRouteImport.update({
15
+ id: '/',
16
+ path: '/',
17
+ getParentRoute: () => rootRouteImport,
18
+ } as any)
19
+
20
+ export interface FileRoutesByFullPath {
21
+ '/': typeof IndexRoute
22
+ }
23
+ export interface FileRoutesByTo {
24
+ '/': typeof IndexRoute
25
+ }
26
+ export interface FileRoutesById {
27
+ __root__: typeof rootRouteImport
28
+ '/': typeof IndexRoute
29
+ }
30
+ export interface FileRouteTypes {
31
+ fileRoutesByFullPath: FileRoutesByFullPath
32
+ fullPaths: '/'
33
+ fileRoutesByTo: FileRoutesByTo
34
+ to: '/'
35
+ id: '__root__' | '/'
36
+ fileRoutesById: FileRoutesById
37
+ }
38
+ export interface RootRouteChildren {
39
+ IndexRoute: typeof IndexRoute
40
+ }
41
+
42
+ declare module '@tanstack/react-router' {
43
+ interface FileRoutesByPath {
44
+ '/': {
45
+ id: '/'
46
+ path: '/'
47
+ fullPath: '/'
48
+ preLoaderRoute: typeof IndexRouteImport
49
+ parentRoute: typeof rootRouteImport
50
+ }
51
+ }
52
+ }
53
+
54
+ const rootRouteChildren: RootRouteChildren = {
55
+ IndexRoute: IndexRoute,
56
+ }
57
+ export const routeTree = rootRouteImport
58
+ ._addFileChildren(rootRouteChildren)
59
+ ._addFileTypes<FileRouteTypes>()
@@ -0,0 +1,10 @@
1
+ import { createRouter } from "@tanstack/react-router";
2
+ import { routeTree } from "./routeTree.gen";
3
+
4
+ declare module "@tanstack/react-router" {
5
+ interface Register {
6
+ router: typeof router;
7
+ }
8
+ }
9
+
10
+ export const router = createRouter({ routeTree });
@@ -0,0 +1,10 @@
1
+ import { NotFoundComponent, RouterErrorComponent } from "@/components";
2
+ import { createRootRoute, Outlet } from "@tanstack/react-router";
3
+
4
+ const RootLayout = () => <Outlet />;
5
+
6
+ export const Route = createRootRoute({
7
+ component: RootLayout,
8
+ errorComponent: RouterErrorComponent,
9
+ notFoundComponent: NotFoundComponent,
10
+ });
@@ -0,0 +1,39 @@
1
+ import { startRpCheck } from "@ybais/bridge";
2
+ import { useCopyText } from "@ybais/clipboard/react";
3
+ import { cn } from "@/utils/cn";
4
+ import { createFileRoute } from "@tanstack/react-router";
5
+
6
+ export const Route = createFileRoute("/")({
7
+ component: Index,
8
+ });
9
+
10
+ function Index() {
11
+ const { copy, copied, } = useCopyText();
12
+
13
+ return (
14
+ <div className="p-2">
15
+ <h3 className="mb-2">Welcome Home!</h3>
16
+
17
+ <div className="space-x-3">
18
+ <button
19
+ className="bg-blue-500 text-white p-2 rounded-md text-sm"
20
+ type="button"
21
+ onClick={startRpCheck}
22
+ >
23
+ Click me
24
+ </button>
25
+
26
+ <button
27
+ className={cn(
28
+ "text-white p-2 rounded-md text-sm",
29
+ copied ? "bg-emerald-600" : "bg-green-500",
30
+ )}
31
+ type="button"
32
+ onClick={() => void copy("啊就打算建档立卡随机掉落空间")}
33
+ >
34
+ {copied ? "🎉复制成功🎉" : "点击复制"}
35
+ </button>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,42 @@
1
+ @import "./variant.css";
2
+ @import "./webview-compat.css";
3
+
4
+ @tailwind base;
5
+ @tailwind components;
6
+ @tailwind utilities;
7
+
8
+ /* 移动端全局常规行为控制 */
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ /* 禁用全局点击灰色高亮框 */
16
+ -webkit-tap-highlight-color: transparent;
17
+ /* 禁用全局文本选中(如果部分内容需复制,可在对应组件上单独覆盖为 text) */
18
+ -webkit-user-select: none;
19
+ user-select: none;
20
+ /* 开启平滑弹性滚动 */
21
+ -webkit-overflow-scrolling: touch;
22
+ }
23
+
24
+ /* 全局样式中禁用文本意外选中与默认 touch 反馈 */
25
+ body {
26
+ -webkit-touch-callout: none;
27
+ -webkit-user-select: none;
28
+ user-select: none;
29
+ -webkit-tap-highlight-color: transparent;
30
+ }
31
+
32
+ /* 允许输入框等交互元素可选中 */
33
+ input,
34
+ textarea {
35
+ -webkit-user-select: auto;
36
+ user-select: auto;
37
+ }
38
+
39
+ #root {
40
+ width: 100%;
41
+ min-height: 100%;
42
+ }