finstep-template-cli 1.0.1

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 (51) hide show
  1. package/README.md +41 -0
  2. package/cli.js +117 -0
  3. package/package.json +39 -0
  4. package/template/.env.development +3 -0
  5. package/template/.env.production +3 -0
  6. package/template/.env.staging +3 -0
  7. package/template/.env.test +3 -0
  8. package/template/.eslintrc.cjs +21 -0
  9. package/template/README.md +69 -0
  10. package/template/auto-imports.d.ts +47 -0
  11. package/template/eslint.config.js +26 -0
  12. package/template/index.html +16 -0
  13. package/template/package.json +46 -0
  14. package/template/postcss.config.js +5 -0
  15. package/template/public/logo.svg +85 -0
  16. package/template/public/vite.svg +1 -0
  17. package/template/src/App.css +42 -0
  18. package/template/src/App.tsx +25 -0
  19. package/template/src/api/home/index.ts +56 -0
  20. package/template/src/api/home/typings.d.ts +8 -0
  21. package/template/src/assets/logo.svg +85 -0
  22. package/template/src/assets/react.svg +1 -0
  23. package/template/src/components/admin-layout.tsx +211 -0
  24. package/template/src/components/f-b-footer.tsx +46 -0
  25. package/template/src/components/f-b-header.tsx +894 -0
  26. package/template/src/components/global-loading.tsx +143 -0
  27. package/template/src/components/hero-section.tsx +371 -0
  28. package/template/src/components/products-preview.tsx +175 -0
  29. package/template/src/components/stats-section.tsx +85 -0
  30. package/template/src/components/trusted-by.tsx +53 -0
  31. package/template/src/hooks/useGlobalLoading.ts +57 -0
  32. package/template/src/index.css +341 -0
  33. package/template/src/main.tsx +30 -0
  34. package/template/src/pages/admin/index.tsx +361 -0
  35. package/template/src/pages/admin/pages/applications/index.tsx +558 -0
  36. package/template/src/pages/home/index.tsx +129 -0
  37. package/template/src/router/index.tsx +9 -0
  38. package/template/src/router/routes.tsx +30 -0
  39. package/template/src/stores/loading.store.ts +46 -0
  40. package/template/src/stores/root.store.ts +22 -0
  41. package/template/src/stores/store-context.tsx +43 -0
  42. package/template/src/stores/user.store.ts +46 -0
  43. package/template/src/utils/index.ts +14 -0
  44. package/template/src/utils/request.ts +116 -0
  45. package/template/src/utils/tokenManager.ts +168 -0
  46. package/template/src/vite-env.d.ts +1 -0
  47. package/template/tailwind.config.js +19 -0
  48. package/template/tsconfig.app.json +29 -0
  49. package/template/tsconfig.json +7 -0
  50. package/template/tsconfig.node.json +26 -0
  51. package/template/vite.config.ts +36 -0
@@ -0,0 +1,9 @@
1
+ import { useRoutes } from "react-router-dom";
2
+ import routes from "./routes";
3
+
4
+ const Router = () => {
5
+ const element = useRoutes(routes);
6
+ return element;
7
+ };
8
+
9
+ export default Router;
@@ -0,0 +1,30 @@
1
+ import { lazy } from "react";
2
+ import type { RouteObject } from "react-router-dom";
3
+ const Home = lazy(() => import("@/pages/home/index"));
4
+ const Admin = lazy(() => import("@/pages/admin/index"));
5
+ const Applications = lazy(() => import("@/pages/admin/pages/applications"));
6
+ const routes: RouteObject[] = [
7
+ {
8
+ path: "/",
9
+ children: [
10
+ {
11
+ index: true,
12
+ element: <Home />,
13
+ },
14
+ {
15
+ path: "/home",
16
+ element: <Home />,
17
+ },
18
+ {
19
+ path: "/admin",
20
+ element: <Admin />,
21
+ },
22
+ {
23
+ path: "/admin/applications",
24
+ element: <Applications />,
25
+ },
26
+ ],
27
+ },
28
+ ];
29
+
30
+ export default routes;
@@ -0,0 +1,46 @@
1
+ // AI生成的代码 - 全局Loading状态管理
2
+ import { makeAutoObservable } from "mobx";
3
+ import type { RootStore } from "./root.store";
4
+
5
+ export default class LoadingStore {
6
+ private rootStore: RootStore;
7
+ // AI生成的代码 - loading状态管理
8
+ isLoading = false;
9
+ loadingText = "加载中...";
10
+ loadingCount = 0; // 用于管理多个并发loading请求
11
+
12
+ constructor(rootStore: RootStore) {
13
+ makeAutoObservable(this);
14
+ this.rootStore = rootStore;
15
+ }
16
+
17
+ // AI生成的代码 - 显示loading
18
+ showLoading = (text?: string) => {
19
+ this.loadingCount++;
20
+ this.isLoading = true;
21
+ if (text) {
22
+ this.loadingText = text;
23
+ }
24
+ };
25
+
26
+ // AI生成的代码 - 隐藏loading
27
+ hideLoading = () => {
28
+ this.loadingCount = Math.max(0, this.loadingCount - 1);
29
+ if (this.loadingCount === 0) {
30
+ this.isLoading = false;
31
+ this.loadingText = "加载中...";
32
+ }
33
+ };
34
+
35
+ // AI生成的代码 - 强制隐藏loading(清除所有loading状态)
36
+ forceHideLoading = () => {
37
+ this.loadingCount = 0;
38
+ this.isLoading = false;
39
+ this.loadingText = "加载中...";
40
+ };
41
+
42
+ // AI生成的代码 - 设置loading文本
43
+ setLoadingText = (text: string) => {
44
+ this.loadingText = text;
45
+ };
46
+ }
@@ -0,0 +1,22 @@
1
+ import { makeAutoObservable } from "mobx";
2
+ import { makePersistable } from "mobx-persist-store";
3
+ import UserStore from "./user.store";
4
+ import LoadingStore from "./loading.store";
5
+ export class RootStore {
6
+ userStore: UserStore;
7
+ loadingStore: LoadingStore;
8
+ constructor() {
9
+ makeAutoObservable(this);
10
+ this.userStore = new UserStore(this);
11
+ this.loadingStore = new LoadingStore(this);
12
+ makePersistable(this.userStore, {
13
+ name: "UserStore",
14
+ properties: ["userInfo"],
15
+ storage: window.localStorage,
16
+ });
17
+ }
18
+ }
19
+
20
+ // 创建RootStore实例
21
+ export const rootStore = new RootStore();
22
+ export const getRootStore = () => rootStore;
@@ -0,0 +1,43 @@
1
+ import React, { createContext, useContext, ReactNode } from "react";
2
+ import type { RootStore } from "./root.store";
3
+ import { rootStore } from "./root.store";
4
+
5
+ const StoreContext = createContext<RootStore | null>(null);
6
+
7
+ interface StoreProviderProps {
8
+ children: ReactNode;
9
+ }
10
+
11
+ export const StoreProvider: React.FC<StoreProviderProps> = ({
12
+ children,
13
+ }: {
14
+ children: ReactNode;
15
+ }) => {
16
+ return (
17
+ <StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider>
18
+ );
19
+ };
20
+
21
+ let globalStore: RootStore | null = null;
22
+
23
+ export const setGlobalStore = (store: RootStore) => {
24
+ globalStore = store;
25
+ };
26
+
27
+ export const getStore = () => {
28
+ if (!globalStore) {
29
+ throw new Error("Store not initialized");
30
+ }
31
+ return globalStore;
32
+ };
33
+
34
+ export const useStore = () => {
35
+ const store = useContext(StoreContext);
36
+ if (!store) {
37
+ throw new Error("useStore must be used within a StoreProvider");
38
+ }
39
+ if (!globalStore) {
40
+ setGlobalStore(store);
41
+ }
42
+ return store;
43
+ };
@@ -0,0 +1,46 @@
1
+ import { makeAutoObservable, runInAction } from "mobx";
2
+ import type { RootStore } from "./root.store";
3
+ interface userInfoType {
4
+ userId: number | string;
5
+ login: boolean;
6
+ phone?: string;
7
+ avatar?: string; // AI生成的代码 - 用户头像
8
+ username?: string; // AI生成的代码 - 用户名
9
+ accessToken?: string;
10
+ refreshToken?: string;
11
+ }
12
+ export default class User {
13
+ userInfo: userInfoType;
14
+ // @ts-expect-error RootStore circular dependency with User store
15
+ constructor(private rootStore: RootStore) {
16
+ this.rootStore = rootStore;
17
+ this.userInfo = {
18
+ userId: "",
19
+ login: false,
20
+ phone: "",
21
+ avatar: "", // AI生成的代码 - 默认头像
22
+ username: "", // AI生成的代码 - 默认用户名
23
+ accessToken: "",
24
+ refreshToken: "",
25
+ };
26
+ makeAutoObservable(this, {}, { autoBind: true });
27
+ }
28
+ setUserInfo(userInfo: userInfoType) {
29
+ runInAction(() => {
30
+ this.userInfo = userInfo;
31
+ });
32
+ }
33
+
34
+ // AI生成:退出登录方法
35
+ logout() {
36
+ runInAction(() => {
37
+ this.userInfo = {
38
+ userId: "",
39
+ login: false,
40
+ phone: "",
41
+ avatar: "",
42
+ username: "",
43
+ };
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,14 @@
1
+ export const getQueryParams = () => {
2
+ const url = window.location.href;
3
+ let queryString = "";
4
+
5
+ if (url.includes("#")) {
6
+ // Hash 路由
7
+ queryString = url.split("#")[1].split("?")[1];
8
+ } else {
9
+ // History 路由
10
+ queryString = window.location.search.substring(1);
11
+ }
12
+ const urlParams = new URLSearchParams(queryString);
13
+ return Object.fromEntries(urlParams.entries());
14
+ };
@@ -0,0 +1,116 @@
1
+ import axios from "axios";
2
+ import { showError } from "finstep-b-components";
3
+ import type {
4
+ AxiosResponse,
5
+ AxiosError,
6
+ InternalAxiosRequestConfig,
7
+ } from "axios";
8
+ import { tokenManager } from "./tokenManager";
9
+ // AI生成的代码 - 自定义请求配置接口,支持登录接口标识
10
+ export interface CustomAxiosRequestConfig {
11
+ url?: string;
12
+ method?: string;
13
+ data?: unknown;
14
+ params?: unknown;
15
+ headers?: Record<string, string>;
16
+ timeout?: number;
17
+ isLoginApi?: boolean; // AI生成的代码 - 标识是否为登录接口
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ export const baseURL = import.meta.env.VITE_API_URL;
22
+ export const baseHeader = {
23
+ "Content-Type": "application/json",
24
+ };
25
+ const request = axios.create({
26
+ baseURL,
27
+ timeout: 10000,
28
+ headers: {
29
+ ...baseHeader,
30
+ },
31
+ });
32
+
33
+ // AI生成的代码 - 为 request 函数添加类型重载,支持自定义配置
34
+ interface RequestFunction {
35
+ <T = unknown>(config: CustomAxiosRequestConfig): Promise<T>;
36
+ <T = unknown>(url: string, config?: CustomAxiosRequestConfig): Promise<T>;
37
+ }
38
+
39
+ // AI生成的代码 - 创建类型安全的 request 函数
40
+ const typedRequest = request as RequestFunction;
41
+
42
+ // AI生成的代码 - 请求拦截器,处理登录接口的特殊认证
43
+ request.interceptors.request.use(
44
+ (config: InternalAxiosRequestConfig & { isLoginApi?: boolean }) => {
45
+ if (config.isLoginApi) {
46
+ if (!config.headers) {
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ config.headers = {} as any;
49
+ }
50
+ config.headers["authorization"] = "Basic Y2FpeXVlOmNhaXl1ZQ==";
51
+ }
52
+ return config;
53
+ },
54
+ (error: AxiosError) => {
55
+ return Promise.reject(error);
56
+ }
57
+ );
58
+
59
+ // AI生成的代码 - 响应拦截器,处理 401 错误和 token 刷新
60
+ request.interceptors.response.use(
61
+ (response: AxiosResponse) => {
62
+ if (response.data.code !== 0) {
63
+ if (response.data.msg) {
64
+ showError(response.data.msg);
65
+ return Promise.reject(response.data);
66
+ }
67
+ }
68
+ return response.data.data;
69
+ },
70
+ async (error: AxiosError) => {
71
+ if (error.response) {
72
+ switch (error.response.status) {
73
+ case 400:
74
+ console.error(error.response?.data);
75
+ break;
76
+ case 401: {
77
+ console.error("未授权,请重新登录");
78
+ const { config: axiosConfig } = error;
79
+ if (!axiosConfig) return Promise.reject(error);
80
+ try {
81
+ // 使用 TokenManager 处理 401 错误
82
+ await tokenManager.handle401Error(axiosConfig);
83
+ return await request(axiosConfig);
84
+ } catch (refreshError) {
85
+ // 刷新失败,跳转到登录页
86
+ window.location.href = "/login";
87
+ return Promise.reject(
88
+ refreshError instanceof Error
89
+ ? refreshError
90
+ : new Error(String(refreshError))
91
+ );
92
+ }
93
+ }
94
+ case 403:
95
+ console.error("拒绝访问");
96
+ break;
97
+ case 404:
98
+ console.error("请求的资源不存在");
99
+ break;
100
+ case 500:
101
+ showError("服务异常,请稍后再试");
102
+ break;
103
+ default:
104
+ console.error("发生错误:", error.message);
105
+ }
106
+ } else if (error.request) {
107
+ console.error("网络错误,请检查您的网络连接");
108
+ } else {
109
+ console.error("请求配置错误:", (error as Error).message);
110
+ }
111
+ return Promise.reject(error);
112
+ }
113
+ );
114
+
115
+ // AI生成的代码 - 导出类型安全的 request 函数
116
+ export default typedRequest;
@@ -0,0 +1,168 @@
1
+ // AI生成的代码 - Token 管理器,处理 token 刷新和请求队列
2
+ import { refreshToken } from "@/api/home";
3
+ import type { AxiosRequestConfig } from "axios";
4
+ import { getRootStore } from "@/stores/root.store";
5
+ // AI生成的代码 - 请求队列项接口
6
+ interface QueueItem {
7
+ config: AxiosRequestConfig;
8
+ resolve: (value: AxiosRequestConfig) => void;
9
+ reject: (reason?: Error) => void;
10
+ }
11
+
12
+ // AI生成的代码 - Token 管理器类
13
+ class TokenManager {
14
+ private isRefreshing = false; // 是否正在刷新 token
15
+ private requestQueue: QueueItem[] = []; // 请求队列
16
+ private refreshDebounceTimer: number | null = null; // 防抖定时器
17
+ private readonly DEBOUNCE_DELAY = 100; // 防抖延迟时间(毫秒)
18
+
19
+ /**
20
+ * AI生成的代码 - 判断是否为登录相关接口
21
+ * @param config 请求配置
22
+ * @returns 是否为登录接口
23
+ */
24
+ private isLoginApi(
25
+ config: AxiosRequestConfig & { isLoginApi?: boolean }
26
+ ): boolean {
27
+ // 检查自定义标识
28
+ if (config.isLoginApi) {
29
+ return true;
30
+ }
31
+
32
+ // 检查 URL 路径
33
+ const loginPaths = ["/auth/oauth2/token"];
34
+
35
+ const url = config.url || "";
36
+ return loginPaths.some((path) => url.includes(path));
37
+ }
38
+
39
+ /**
40
+ * AI生成的代码 - 获取存储的 refresh token
41
+ * @returns refresh token 或 null
42
+ */
43
+ private getRefreshToken(): string | null {
44
+ return getRootStore().userStore.userInfo.refreshToken || "";
45
+ }
46
+
47
+ /**
48
+ * AI生成的代码 - 保存新的 token 信息
49
+ * @param tokenData token 数据
50
+ */
51
+ private saveTokens(tokenData: {
52
+ access_token?: string;
53
+ refresh_token?: string;
54
+ }): void {
55
+ getRootStore().userStore.setUserInfo({
56
+ ...getRootStore().userStore.userInfo,
57
+ accessToken: tokenData.access_token || "",
58
+ refreshToken: tokenData.refresh_token || "",
59
+ });
60
+ }
61
+
62
+ /**
63
+ * AI生成的代码 - 清除所有 token
64
+ */
65
+ private clearTokens(): void {
66
+ getRootStore().userStore.setUserInfo({
67
+ userId: "",
68
+ login: false,
69
+ phone: "",
70
+ avatar: "", // AI生成的代码 - 默认头像
71
+ username: "", // AI生成的代码 - 默认用户名
72
+ accessToken: "",
73
+ refreshToken: "",
74
+ });
75
+ }
76
+
77
+ /**
78
+ * AI生成的代码 - 处理队列中的请求
79
+ * @param error 错误信息(如果刷新失败)
80
+ */
81
+ private processQueue(error?: Error): void {
82
+ this.requestQueue.forEach(({ resolve, reject, config }) => {
83
+ if (error) {
84
+ reject(error);
85
+ } else {
86
+ resolve(config);
87
+ }
88
+ });
89
+ this.requestQueue = [];
90
+ }
91
+
92
+ /**
93
+ * AI生成的代码 - 刷新 token
94
+ * @returns Promise<void>
95
+ */
96
+ async refreshToken(): Promise<void> {
97
+ const refreshTokenValue = this.getRefreshToken();
98
+
99
+ if (!refreshTokenValue) {
100
+ this.clearTokens();
101
+ throw new Error("No refresh token available");
102
+ }
103
+
104
+ try {
105
+ const response = await refreshToken({
106
+ refresh_token: refreshTokenValue,
107
+ });
108
+
109
+ this.saveTokens(response);
110
+ this.processQueue(); // 处理队列中的请求
111
+ } catch (error) {
112
+ this.clearTokens();
113
+ this.processQueue(
114
+ error instanceof Error ? error : new Error(String(error))
115
+ ); // 确保传递Error类型的错误给队列中的请求
116
+ throw error;
117
+ } finally {
118
+ this.isRefreshing = false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * AI生成的代码 - 处理 401 错误,支持请求队列和防抖
124
+ * @param config 原始请求配置
125
+ * @returns Promise<any>
126
+ */
127
+ async handle401Error(
128
+ config: AxiosRequestConfig & { isLoginApi?: boolean }
129
+ ): Promise<AxiosRequestConfig> {
130
+ // 如果是登录接口,直接拒绝
131
+ if (this.isLoginApi(config)) {
132
+ return Promise.reject(new Error("Login API returned 401"));
133
+ }
134
+
135
+ return new Promise((resolve, reject) => {
136
+ // 将请求添加到队列
137
+ this.requestQueue.push({ config, resolve, reject });
138
+
139
+ // 如果已经在刷新中,直接返回
140
+ if (this.isRefreshing) {
141
+ return;
142
+ }
143
+
144
+ // 清除之前的防抖定时器
145
+ if (this.refreshDebounceTimer) {
146
+ clearTimeout(this.refreshDebounceTimer);
147
+ }
148
+
149
+ // 设置防抖定时器
150
+ this.refreshDebounceTimer = setTimeout(async () => {
151
+ this.isRefreshing = true;
152
+
153
+ try {
154
+ await this.refreshToken();
155
+ } catch (error) {
156
+ // 刷新失败,清除队列
157
+ this.processQueue(
158
+ error instanceof Error ? error : new Error(String(error))
159
+ );
160
+ }
161
+ }, this.DEBOUNCE_DELAY);
162
+ });
163
+ }
164
+ }
165
+
166
+ // AI生成的代码 - 导出单例实例
167
+ export const tokenManager = new TokenManager();
168
+ export default TokenManager;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,19 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ // AI生成的代码 - Tailwind CSS配置文件
3
+ export default {
4
+ content: [
5
+ "./index.html",
6
+ "./src/**/*.{js,ts,jsx,tsx}",
7
+ ],
8
+ theme: {
9
+ extend: {
10
+ fontFamily: {
11
+ 'pacifico': ['Pacifico', 'cursive'],
12
+ 'inter': ['Inter', 'sans-serif'],
13
+ 'jetbrains': ['JetBrains Mono', 'monospace'],
14
+ },
15
+ },
16
+ },
17
+ plugins: [],
18
+ }
19
+
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "@/*": ["src/*"]
20
+ },
21
+ /* Linting */
22
+ "strict": true,
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "noFallthroughCasesInSwitch": true,
26
+ "noUncheckedSideEffectImports": true
27
+ },
28
+ "include": ["src", "./auto-imports.d.ts"]
29
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "types": ["node"],
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
@@ -0,0 +1,36 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import AutoImport from "unplugin-auto-import/vite";
4
+ import { resolve } from "path";
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ AutoImport({
10
+ // 这里可以配置自动导入的库,例如 vue、react 等
11
+ imports: ["react", "react-router-dom", "react-router"], // 根据需要选择要自动导入的库
12
+ // 其他配置项
13
+ dts: "auto-imports.d.ts", // 可以生成 TypeScript 声明文件
14
+ }),
15
+ ],
16
+ resolve: {
17
+ alias: {
18
+ "@": resolve(__dirname, "./src"),
19
+ },
20
+ },
21
+ server: {
22
+ port: 80,
23
+ host: "0.0.0.0",
24
+ // AI生成的代码 - 添加允许的主机列表以解决被阻止的请求问题
25
+ // allowedHosts: ["sso-login.finstep.cn"],
26
+ proxy: {
27
+ "/api": {
28
+ // 免费的在线REST API
29
+ // target: "http://10.159.0.130:9527",
30
+ target: "http://product-test.finstep.cn",
31
+ changeOrigin: true,
32
+ rewrite: (path) => path.replace(/^\/api/, ""),
33
+ },
34
+ },
35
+ },
36
+ });