create-wenuts-cli 1.0.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +168 -0
- package/package.json +35 -0
- package/template/default/.env.development +19 -0
- package/template/default/README.md +36 -0
- package/template/default/envConfig/.env.development +26 -0
- package/template/default/envConfig/.env.production +23 -0
- package/template/default/eslint.config.mjs +15 -0
- package/template/default/next.config.ts +10 -0
- package/template/default/package.json +40 -0
- package/template/default/postcss.config.mjs +17 -0
- package/template/default/public/file.svg +1 -0
- package/template/default/public/globe.svg +1 -0
- package/template/default/public/next.svg +1 -0
- package/template/default/public/vercel.svg +1 -0
- package/template/default/public/window.svg +1 -0
- package/template/default/src/api/user.ts +149 -0
- package/template/default/src/app/[local]/(default-layout)/layout.tsx +5 -0
- package/template/default/src/app/[local]/(default-layout)/page.tsx +5 -0
- package/template/default/src/app/[local]/(no-layout)/home/page.tsx +16 -0
- package/template/default/src/app/[local]/(no-layout)/login/components/LoginHandle/index.tsx +55 -0
- package/template/default/src/app/[local]/(no-layout)/login/page.tsx +41 -0
- package/template/default/src/app/[local]/(no-layout)/login/style.module.css +4 -0
- package/template/default/src/app/[local]/layout.tsx +54 -0
- package/template/default/src/app/api/auth/[...nextauth]/route.ts +54 -0
- package/template/default/src/app/favicon.ico +0 -0
- package/template/default/src/app/globals.css +27 -0
- package/template/default/src/app/layout.tsx +26 -0
- package/template/default/src/appConfig.ts +37 -0
- package/template/default/src/components/System/ClientLayout/index.tsx +69 -0
- package/template/default/src/components/System/LoginModal/index.tsx +61 -0
- package/template/default/src/components/System/ThemeProvider/index.tsx +55 -0
- package/template/default/src/config/CookieMap.ts +3 -0
- package/template/default/src/config/LocalStorageMap.ts +11 -0
- package/template/default/src/i18n/request.ts +17 -0
- package/template/default/src/i18n/routing.ts +15 -0
- package/template/default/src/lang/en.json +6 -0
- package/template/default/src/libs/casdoor_provider.ts +39 -0
- package/template/default/src/libs/dataAnalytics.ts +176 -0
- package/template/default/src/libs/fetch.ts +176 -0
- package/template/default/src/libs/fetchCookie/clientCookies.ts +5 -0
- package/template/default/src/libs/fetchCookie/getCookie.ts +15 -0
- package/template/default/src/libs/fetchCookie/serverCookies.ts +10 -0
- package/template/default/src/libs/localCache.ts +28 -0
- package/template/default/src/libs/nextauth.ts +49 -0
- package/template/default/src/libs/seo.ts +89 -0
- package/template/default/src/middleware.ts +11 -0
- package/template/default/src/store/useUserStore.ts +42 -0
- package/template/default/src/types/payment.ts +163 -0
- package/template/default/src/types/user.ts +137 -0
- package/template/default/tsconfig.json +27 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// import { IUserProfile } from '@/types/user';
|
|
2
|
+
// import LocalStorageMap from '@/config/LocalStorageMap';
|
|
3
|
+
// import LocalCache from './localCache';
|
|
4
|
+
|
|
5
|
+
// interface IInitParams {
|
|
6
|
+
// profile?: null | IUserProfile;
|
|
7
|
+
// }
|
|
8
|
+
|
|
9
|
+
// let connectionState = true; // 当前数据上报的连接状态
|
|
10
|
+
|
|
11
|
+
// /**
|
|
12
|
+
// * 关闭数据上报的连接状态,后续所有的数据上报进行缓存,直到重启项目再进行发送
|
|
13
|
+
// */
|
|
14
|
+
// const dataOffline = () => {
|
|
15
|
+
// connectionState = false;
|
|
16
|
+
// };
|
|
17
|
+
|
|
18
|
+
// /**
|
|
19
|
+
// * 上报数据
|
|
20
|
+
// * 如果连接关闭,将要上报的数据存储到 localStorage 中
|
|
21
|
+
// */
|
|
22
|
+
// const dataPush = (key: string, value: any = {}) => {
|
|
23
|
+
// if (connectionState) {
|
|
24
|
+
// let googleValue = value;
|
|
25
|
+
// let shushuValue = value;
|
|
26
|
+
// let bingValue = value;
|
|
27
|
+
// let affiliateValue = value;
|
|
28
|
+
|
|
29
|
+
// switch (key) {
|
|
30
|
+
// case 'begin_checkout': {
|
|
31
|
+
// googleValue = value.google;
|
|
32
|
+
// shushuValue = value.shushu;
|
|
33
|
+
// bingValue = value.bing;
|
|
34
|
+
// break;
|
|
35
|
+
// }
|
|
36
|
+
// case 'purchase': {
|
|
37
|
+
// googleValue = value.google;
|
|
38
|
+
// shushuValue = value.shushu;
|
|
39
|
+
// bingValue = value.bing;
|
|
40
|
+
// affiliateValue = value.affiliate;
|
|
41
|
+
|
|
42
|
+
// break;
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
// window.gtag('event', key, googleValue);
|
|
47
|
+
|
|
48
|
+
// window.ta.track(key, shushuValue);
|
|
49
|
+
|
|
50
|
+
// window.ta.flush();
|
|
51
|
+
|
|
52
|
+
// // bing 上报
|
|
53
|
+
// if (window.uetq) {
|
|
54
|
+
// console.log(
|
|
55
|
+
// {
|
|
56
|
+
// key,
|
|
57
|
+
// bingValue,
|
|
58
|
+
// },
|
|
59
|
+
// '???'
|
|
60
|
+
// );
|
|
61
|
+
|
|
62
|
+
// window.uetq.push('event', key, {
|
|
63
|
+
// r_date: new Date().getTime(),
|
|
64
|
+
// current_location: window.location.href,
|
|
65
|
+
// ...bingValue,
|
|
66
|
+
// });
|
|
67
|
+
// }
|
|
68
|
+
|
|
69
|
+
// if (key === 'purchase' && window.tap && affiliateValue.bizId) {
|
|
70
|
+
// window.tap('conversion', affiliateValue.bizId, affiliateValue.money, {
|
|
71
|
+
// currency: affiliateValue.currency,
|
|
72
|
+
// });
|
|
73
|
+
// }
|
|
74
|
+
// } else {
|
|
75
|
+
// const cacheList: any[] = LocalCache.get(LocalStorageMap.UnreportedList) || [];
|
|
76
|
+
|
|
77
|
+
// cacheList.push({
|
|
78
|
+
// key,
|
|
79
|
+
// value,
|
|
80
|
+
// });
|
|
81
|
+
|
|
82
|
+
// LocalCache.set(LocalStorageMap.UnreportedList, cacheList);
|
|
83
|
+
// }
|
|
84
|
+
// };
|
|
85
|
+
|
|
86
|
+
// /**
|
|
87
|
+
// * 首次事件上报
|
|
88
|
+
// */
|
|
89
|
+
// const dataFirstVisit = () => {
|
|
90
|
+
// window.ta.trackFirst({
|
|
91
|
+
// eventName: 'first_visit',
|
|
92
|
+
// });
|
|
93
|
+
// };
|
|
94
|
+
|
|
95
|
+
// /**
|
|
96
|
+
// * 发送缓存的上报数据
|
|
97
|
+
// */
|
|
98
|
+
// const sendCacheData: (params: IInitParams) => void = ({ profile = null }) => {
|
|
99
|
+
// const cacheList: any[] = LocalCache.get(LocalStorageMap.UnreportedList);
|
|
100
|
+
|
|
101
|
+
// if (cacheList) {
|
|
102
|
+
// for (let i = 0; i < cacheList.length; i++) {
|
|
103
|
+
// const info = cacheList[i];
|
|
104
|
+
|
|
105
|
+
// switch (info.key) {
|
|
106
|
+
// case 'login': {
|
|
107
|
+
// profile && dataPush(info.key, info.value);
|
|
108
|
+
// break;
|
|
109
|
+
// }
|
|
110
|
+
// default: {
|
|
111
|
+
// dataPush(info.key, info.value);
|
|
112
|
+
// }
|
|
113
|
+
// }
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// LocalCache.remove(LocalStorageMap.UnreportedList);
|
|
117
|
+
// }
|
|
118
|
+
// };
|
|
119
|
+
|
|
120
|
+
// /**
|
|
121
|
+
// * 设置公共参数
|
|
122
|
+
// */
|
|
123
|
+
// const dataSetPublicParams: (params: IInitParams) => void = ({ profile = null }) => {
|
|
124
|
+
// // 谷歌上报的公共参数设置
|
|
125
|
+
// const publicParams: any = {
|
|
126
|
+
// page_url: location.href,
|
|
127
|
+
// user_sub_code: 'no_login',
|
|
128
|
+
// };
|
|
129
|
+
|
|
130
|
+
// if (profile) {
|
|
131
|
+
// publicParams.user_id = profile.id;
|
|
132
|
+
// publicParams.user_sub_code = profile.pkgCode || 'free';
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
// window.gtag('set', publicParams);
|
|
136
|
+
|
|
137
|
+
// // 数数上报的公共参数设置
|
|
138
|
+
// const sandbox = process.env.NEXT_PUBLIC_NODE_ENV === 'dev';
|
|
139
|
+
|
|
140
|
+
// window.ta.setSuperProperties({
|
|
141
|
+
// sandbox,
|
|
142
|
+
// });
|
|
143
|
+
|
|
144
|
+
// if (profile) {
|
|
145
|
+
// window.ta.login(profile.id);
|
|
146
|
+
// window.ta.setSuperProperties({
|
|
147
|
+
// uid: profile.id,
|
|
148
|
+
// });
|
|
149
|
+
// window.ta.userSet({
|
|
150
|
+
// page_url: location.href,
|
|
151
|
+
// user_sub_code: profile.pkgCode || 'free',
|
|
152
|
+
// });
|
|
153
|
+
// } else {
|
|
154
|
+
// window.ta.userSet({
|
|
155
|
+
// page_url: location.href,
|
|
156
|
+
// user_sub_code: 'no_login',
|
|
157
|
+
// });
|
|
158
|
+
// }
|
|
159
|
+
// };
|
|
160
|
+
|
|
161
|
+
// /**
|
|
162
|
+
// * 初始化数据上报的公共参数,如果缓存中存在未上报数据,将数据统一进行上报
|
|
163
|
+
// */
|
|
164
|
+
// const dataInit: (params: IInitParams) => void = ({ profile = null }) => {
|
|
165
|
+
// window.gtag('consent', 'update', {
|
|
166
|
+
// analytics_storage: 'granted',
|
|
167
|
+
// });
|
|
168
|
+
|
|
169
|
+
// dataSetPublicParams({ profile });
|
|
170
|
+
|
|
171
|
+
// sendCacheData({ profile });
|
|
172
|
+
|
|
173
|
+
// dataFirstVisit();
|
|
174
|
+
// };
|
|
175
|
+
|
|
176
|
+
// export { dataPush, dataOffline, dataSetPublicParams, dataInit };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import CookieMap from '@/config/CookieMap';
|
|
2
|
+
import qs from 'query-string';
|
|
3
|
+
import { getCookie } from './fetchCookie/getCookie';
|
|
4
|
+
|
|
5
|
+
type TResult<T> = {
|
|
6
|
+
code: number;
|
|
7
|
+
data: T;
|
|
8
|
+
info: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class Fetch {
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
private publicHeaders: any = {
|
|
14
|
+
'X-App-Name': 'aitubo-web',
|
|
15
|
+
'X-App-Version': '2.0.0',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.baseUrl = process.env.NEXT_PUBLIC_BASE_URL!;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async getToken() {
|
|
23
|
+
let token = '';
|
|
24
|
+
|
|
25
|
+
const userState = await getCookie(CookieMap.UserState);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (userState) {
|
|
29
|
+
token = JSON.parse(decodeURIComponent(userState)).token;
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.warn(String(error));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (token) {
|
|
36
|
+
return {
|
|
37
|
+
Authorization: `Bearer ${token}`,
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async get<T>(url: string, params: Record<string, any> = {}): Promise<TResult<T>> {
|
|
45
|
+
const token = await this.getToken();
|
|
46
|
+
|
|
47
|
+
const config: any = {
|
|
48
|
+
method: 'GET',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
...this.publicHeaders,
|
|
52
|
+
...token,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let qsUrl = `?${qs.stringify(params, {
|
|
57
|
+
encode: false,
|
|
58
|
+
})}`;
|
|
59
|
+
|
|
60
|
+
const fetchUrl = `${this.baseUrl}${url}${qsUrl}`;
|
|
61
|
+
|
|
62
|
+
return fetch(fetchUrl, config)
|
|
63
|
+
.then(async response => {
|
|
64
|
+
const result = await response.json();
|
|
65
|
+
|
|
66
|
+
if (result.code !== undefined && typeof result.code === 'number') {
|
|
67
|
+
if (response.status === 401) {
|
|
68
|
+
return Promise.resolve({
|
|
69
|
+
code: 401,
|
|
70
|
+
info: result.info,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Promise.resolve(result);
|
|
75
|
+
} else {
|
|
76
|
+
return Promise.resolve({
|
|
77
|
+
code: response.status,
|
|
78
|
+
info: response.statusText,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.catch(error => {
|
|
83
|
+
return {
|
|
84
|
+
code: -1,
|
|
85
|
+
info: String(error),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async post<T>(url: string, data: Record<string, any> = {}): Promise<TResult<T>> {
|
|
91
|
+
const token = await this.getToken();
|
|
92
|
+
|
|
93
|
+
const config: any = {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
...this.publicHeaders,
|
|
98
|
+
...token,
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify(data),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const fetchUrl = `${this.baseUrl}${url}`;
|
|
104
|
+
|
|
105
|
+
return fetch(fetchUrl, config)
|
|
106
|
+
.then(async response => {
|
|
107
|
+
const result = await response.json();
|
|
108
|
+
|
|
109
|
+
if (result.code !== undefined && typeof result.code === 'number') {
|
|
110
|
+
if (response.status === 401) {
|
|
111
|
+
return Promise.resolve({
|
|
112
|
+
code: 401,
|
|
113
|
+
info: result.info,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Promise.resolve(result);
|
|
118
|
+
} else {
|
|
119
|
+
return Promise.resolve({
|
|
120
|
+
code: response.status,
|
|
121
|
+
info: response.statusText,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
.catch(error => {
|
|
126
|
+
return {
|
|
127
|
+
code: -1,
|
|
128
|
+
info: String(error),
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public async upload<T>(url: string, data: FormData): Promise<TResult<T>> {
|
|
134
|
+
const token = await this.getToken();
|
|
135
|
+
|
|
136
|
+
const config: any = {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: {
|
|
139
|
+
...this.publicHeaders,
|
|
140
|
+
...token,
|
|
141
|
+
},
|
|
142
|
+
body: data,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const fetchUrl = `${this.baseUrl}${url}`;
|
|
146
|
+
|
|
147
|
+
return fetch(fetchUrl, config)
|
|
148
|
+
.then(async response => {
|
|
149
|
+
const result = await response.json();
|
|
150
|
+
|
|
151
|
+
if (result.code !== undefined && typeof result.code === 'number') {
|
|
152
|
+
if (response.status === 401) {
|
|
153
|
+
return Promise.resolve({
|
|
154
|
+
code: 401,
|
|
155
|
+
info: result.info,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return Promise.resolve(result);
|
|
160
|
+
} else {
|
|
161
|
+
return Promise.resolve({
|
|
162
|
+
code: response.status,
|
|
163
|
+
info: response.statusText,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
.catch(error => {
|
|
168
|
+
return {
|
|
169
|
+
code: -1,
|
|
170
|
+
info: String(error),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export default new Fetch();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getServerSideCookie } from './serverCookies';
|
|
2
|
+
import { getClientSideCookie } from './clientCookies';
|
|
3
|
+
|
|
4
|
+
export const getCookie = (key: string): any => {
|
|
5
|
+
return new Promise(async resolve => {
|
|
6
|
+
const isBrowser = typeof window !== 'undefined';
|
|
7
|
+
|
|
8
|
+
if (isBrowser) {
|
|
9
|
+
return resolve(getClientSideCookie(key));
|
|
10
|
+
} else {
|
|
11
|
+
// use server 组件返回的是 Promise
|
|
12
|
+
return resolve(getServerSideCookie(key));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { cookies } from "next/headers";
|
|
3
|
+
|
|
4
|
+
export const getServerSideCookie = async (key: string): Promise<any> => {
|
|
5
|
+
const cookieStore = await cookies();
|
|
6
|
+
|
|
7
|
+
const cookieDetail = cookieStore.get(key);
|
|
8
|
+
|
|
9
|
+
return cookieDetail ? cookieDetail.value : "";
|
|
10
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* window.localStorage 浏览器永久缓存
|
|
3
|
+
* @method set 设置永久缓存
|
|
4
|
+
* @method get 获取永久缓存
|
|
5
|
+
* @method remove 移除永久缓存
|
|
6
|
+
* @method clear 移除全部永久缓存
|
|
7
|
+
*/
|
|
8
|
+
const LocalCache = {
|
|
9
|
+
// 设置永久缓存
|
|
10
|
+
set(key: string, val: any) {
|
|
11
|
+
window.localStorage.setItem(key, JSON.stringify(val));
|
|
12
|
+
},
|
|
13
|
+
// 获取永久缓存
|
|
14
|
+
get(key: string) {
|
|
15
|
+
const json = window.localStorage.getItem(key);
|
|
16
|
+
return JSON.parse(json as string);
|
|
17
|
+
},
|
|
18
|
+
// 移除永久缓存
|
|
19
|
+
remove(key: string) {
|
|
20
|
+
window.localStorage.removeItem(key);
|
|
21
|
+
},
|
|
22
|
+
// 移除全部永久缓存
|
|
23
|
+
clear() {
|
|
24
|
+
window.localStorage.clear();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default LocalCache;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import UserApi from "@/api/user";
|
|
2
|
+
import CookieMap from "@/config/CookieMap";
|
|
3
|
+
import NextAuth, { NextAuthOptions } from "next-auth";
|
|
4
|
+
import GoogleProvider from "next-auth/providers/google";
|
|
5
|
+
import { cookies } from "next/headers";
|
|
6
|
+
import CasdoorProvider from "./casdoor_provider";
|
|
7
|
+
export const authOptions: NextAuthOptions = {
|
|
8
|
+
secret: process.env.NEXTAUTH_SECRET,
|
|
9
|
+
providers: [
|
|
10
|
+
GoogleProvider({
|
|
11
|
+
clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
|
|
12
|
+
clientSecret: process.env.GOOGLE_SECRET!,
|
|
13
|
+
async profile(profile, tokens) {
|
|
14
|
+
// 获取用户信息
|
|
15
|
+
const cookieStore = await cookies();
|
|
16
|
+
const accessToken = tokens.access_token;
|
|
17
|
+
|
|
18
|
+
const userState = await UserApi.userLogin({
|
|
19
|
+
srcType: "google",
|
|
20
|
+
token: accessToken,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
cookieStore.set(
|
|
24
|
+
CookieMap.UserState,
|
|
25
|
+
encodeURIComponent(JSON.stringify(userState.data))
|
|
26
|
+
);
|
|
27
|
+
return {
|
|
28
|
+
id: userState.data.token || "",
|
|
29
|
+
name: userState.data.name || "",
|
|
30
|
+
email: userState.data.email || "",
|
|
31
|
+
image: userState.data.avatar || "",
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
CasdoorProvider({
|
|
36
|
+
clientId: process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID!,
|
|
37
|
+
clientSecret: process.env.CASDOOR_SECRET!,
|
|
38
|
+
issuer: process.env.NEXT_PUBLIC_CASDOOR_ISSUER!,
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
callbacks: {
|
|
43
|
+
session: async ({ session, token, user }) => {
|
|
44
|
+
return session;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default NextAuth(authOptions);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Metadata } from 'next/types';
|
|
2
|
+
import appConfig from '@/appConfig';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 获取语言并拼接路径
|
|
6
|
+
*/
|
|
7
|
+
const getLanguages = (path: string) => {
|
|
8
|
+
const languages: { [key in string]?: string } = {};
|
|
9
|
+
|
|
10
|
+
// 添加 x-default 指向默认语言
|
|
11
|
+
languages['x-default'] = appConfig.baseDomain + path;
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < appConfig.locales.length; i++) {
|
|
14
|
+
const lang = appConfig.locales[i];
|
|
15
|
+
if (lang !== appConfig.defaultLocale) {
|
|
16
|
+
languages[lang] = appConfig.baseDomain + (lang === appConfig.defaultLocale ? '' : `/${lang}`) + path;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return languages;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface ISEOParams {
|
|
24
|
+
title: string;
|
|
25
|
+
description: string;
|
|
26
|
+
keywords?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 获取 SEO 标签
|
|
31
|
+
* @param params.title 标题
|
|
32
|
+
* @param params.description 描述
|
|
33
|
+
* @param params.keywords 关键词
|
|
34
|
+
*/
|
|
35
|
+
const getSEOTags: (params: ISEOParams) => Metadata = ({ title, description, keywords }) => {
|
|
36
|
+
return {
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
keywords: keywords || appConfig.appName,
|
|
40
|
+
openGraph: {
|
|
41
|
+
title,
|
|
42
|
+
description,
|
|
43
|
+
siteName: title,
|
|
44
|
+
type: 'website',
|
|
45
|
+
// images: {
|
|
46
|
+
// url: '',
|
|
47
|
+
// width: 1200,
|
|
48
|
+
// height: 630,
|
|
49
|
+
// },
|
|
50
|
+
},
|
|
51
|
+
twitter: {
|
|
52
|
+
title,
|
|
53
|
+
description,
|
|
54
|
+
card: 'summary_large_image',
|
|
55
|
+
// images: '',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 初始化语言和 url 的 SEO 标签
|
|
62
|
+
*/
|
|
63
|
+
const initLangTags: (path: string, lang: string) => Metadata = (path, lang) => {
|
|
64
|
+
return {
|
|
65
|
+
alternates: {
|
|
66
|
+
canonical: lang + path,
|
|
67
|
+
languages: getLanguages(path),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 初始化 SEO 标签
|
|
74
|
+
*/
|
|
75
|
+
const initSEOTags: () => Metadata = () => {
|
|
76
|
+
const isPro = process.env.NEXT_PUBLIC_NODE_ENV !== 'dev';
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
title: 'Aitubo: AI Image and Video Generator - Aitubo.ai',
|
|
80
|
+
description: `Best free AI art generator: Generate stunning images and videos from text, or create videos from images, all powered by the latest AI technology`,
|
|
81
|
+
keywords: 'AI video generator, text to video, image to video, video generation tool, create videos with AI',
|
|
82
|
+
applicationName: appConfig.appName,
|
|
83
|
+
metadataBase: new URL(appConfig.baseDomain),
|
|
84
|
+
robots: isPro ? { index: true, follow: true, notranslate: true } : { index: false, follow: false, noarchive: true, nosnippet: true, noimageindex: true },
|
|
85
|
+
// robots: { index: false, follow: false, noarchive: true, nosnippet: true, noimageindex: true },
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export { getSEOTags, initSEOTags, initLangTags };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import createMiddleware from "next-intl/middleware";
|
|
2
|
+
import { routing } from "./i18n/routing";
|
|
3
|
+
|
|
4
|
+
export default createMiddleware(routing);
|
|
5
|
+
|
|
6
|
+
export const config = {
|
|
7
|
+
// Match all pathnames except for
|
|
8
|
+
// - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
|
|
9
|
+
// - … the ones containing a dot (e.g. `favicon.ico`)
|
|
10
|
+
matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
|
|
11
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PaymentReason } from "@/types/payment";
|
|
2
|
+
import { IUserProfile } from "@/types/user";
|
|
3
|
+
import { create } from "zustand";
|
|
4
|
+
|
|
5
|
+
interface IUserStore {
|
|
6
|
+
userInfo: null | IUserProfile; // 用户信息,null 为未登录
|
|
7
|
+
paymentReason: PaymentReason; // 购买的原因
|
|
8
|
+
setUserInfo: (info: null | IUserProfile) => void; // 改变用户信息
|
|
9
|
+
setCreditInfo: (args: { credit: number; subCredit: number }) => void; // 更新用户点数
|
|
10
|
+
openLoginModal: () => void; // 打开登录弹框
|
|
11
|
+
setOpenLoginModal: (openLoginModal: () => void) => void;
|
|
12
|
+
paymentModalProxy: () => void; // 打开订阅、加油包弹框事件
|
|
13
|
+
openPaymentModal: (reason: PaymentReason) => void; // 设置订阅原因并执行 paymentModalProxy()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const userStore = create<IUserStore>((set) => ({
|
|
17
|
+
userInfo: null,
|
|
18
|
+
paymentReason: PaymentReason.None,
|
|
19
|
+
setUserInfo: (info) => set({ userInfo: info }),
|
|
20
|
+
setCreditInfo: (args: { credit: number; subCredit: number }) =>
|
|
21
|
+
set((state: IUserStore) => ({
|
|
22
|
+
userInfo: {
|
|
23
|
+
...state.userInfo!,
|
|
24
|
+
...args,
|
|
25
|
+
},
|
|
26
|
+
})),
|
|
27
|
+
openLoginModal: () => {},
|
|
28
|
+
setOpenLoginModal: (openLoginModal) => set({ openLoginModal }),
|
|
29
|
+
paymentModalProxy: () => {},
|
|
30
|
+
openPaymentModal: (reason) =>
|
|
31
|
+
set((state) => {
|
|
32
|
+
if (state.userInfo) {
|
|
33
|
+
state.paymentModalProxy();
|
|
34
|
+
return { paymentReason: reason };
|
|
35
|
+
} else {
|
|
36
|
+
state.openLoginModal();
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
export default userStore;
|