ts-glitter 16.2.4 → 16.2.8
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/lowcode/Entry.js +1 -1
- package/lowcode/Entry.ts +1 -1
- package/lowcode/glitter-base/global/currency.d.ts +19 -0
- package/lowcode/glitter-base/global/currency.js.map +1 -0
- package/lowcode/glitter-base/global/global-user.d.ts +5 -0
- package/lowcode/glitter-base/global/global-user.js.map +1 -1
- package/lowcode/glitter-base/global/language.d.ts +22 -0
- package/lowcode/glitter-base/global/language.js.map +1 -0
- package/lowcode/glitter-base/route/shopping.d.ts +28 -2
- package/lowcode/glitter-base/route/shopping.js.map +1 -1
- package/lowcode/glitterBundle/api/base.js.map +1 -1
- package/package.json +5 -2
- package/src/api-public/services/shopping.d.ts +1 -1
- package/src/api-public/services/shopping.js +1 -0
- package/src/api-public/services/shopping.js.map +1 -1
- package/src/api-public/services/shopping.ts +1 -0
- package/src/helper/glitter-util.d.ts +4 -2
- package/src/helper/glitter-util.js +20 -11
- package/src/helper/glitter-util.js.map +1 -1
- package/src/helper/glitter-util.ts +22 -12
- package/src/index.js +255 -433
- package/src/index.js.map +1 -1
- package/src/index.ts +246 -441
- package/src/seo-config.d.ts +40 -0
- package/src/seo-config.js +303 -0
- package/src/seo-config.js.map +1 -0
- package/src/seo-config.ts +357 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import {Manager} from "./api-public/services/manager.js";
|
|
2
|
+
import db from "./modules/database.js";
|
|
3
|
+
import {UtDatabase} from "./api-public/utils/ut-database.js";
|
|
4
|
+
import {Seo} from "./services/seo.js";
|
|
5
|
+
import {Shopping} from "./api-public/services/shopping.js";
|
|
6
|
+
|
|
7
|
+
const html = String.raw
|
|
8
|
+
|
|
9
|
+
export class SeoConfig {
|
|
10
|
+
//編輯器的SEO
|
|
11
|
+
public static editorSeo = html`<title>SHOPNEX後台系統</title>
|
|
12
|
+
<link rel="canonical" href="/index"/>
|
|
13
|
+
<meta name="keywords" content="SHOPNEX,電商平台"/>
|
|
14
|
+
<link
|
|
15
|
+
id="appImage"
|
|
16
|
+
rel="shortcut icon"
|
|
17
|
+
href="https://d3jnmi1tfjgtti.cloudfront.net/file/234285319/size1440_s*px$_sas0s9s0s1sesas0_1697354801736-Glitterlogo.png"
|
|
18
|
+
type="image/x-icon"
|
|
19
|
+
/>
|
|
20
|
+
<link
|
|
21
|
+
rel="icon"
|
|
22
|
+
href="https://d3jnmi1tfjgtti.cloudfront.net/file/234285319/size1440_s*px$_sas0s9s0s1sesas0_1697354801736-Glitterlogo.png"
|
|
23
|
+
type="image/png"
|
|
24
|
+
sizes="128x128"
|
|
25
|
+
/>
|
|
26
|
+
<meta property="og:image"
|
|
27
|
+
content="https://d3jnmi1tfjgtti.cloudfront.net/file/252530754/1718778766524-shopnex_banner.jpg"/>
|
|
28
|
+
<meta property="og:title" content="SHOPNEX後台系統"/>
|
|
29
|
+
<meta
|
|
30
|
+
name="description"
|
|
31
|
+
content="SHOPNEX電商開店平台,零抽成、免手續費。提供精美模板和豐富插件,操作簡單,3分鐘內快速打造專屬商店。購物車、金物流、SEO行銷、資料分析一站搞定。支援APP上架,並提供100%客製化設計,立即免費體驗30天。"
|
|
32
|
+
/>
|
|
33
|
+
<meta
|
|
34
|
+
name="og:description"
|
|
35
|
+
content="SHOPNEX電商開店平台,零抽成、免手續費。提供精美模板和豐富插件,操作簡單,3分鐘內快速打造專屬商店。購物車、金物流、SEO行銷、資料分析一站搞定。支援APP上架,並提供100%客製化設計,立即免費體驗30天。"
|
|
36
|
+
/>`
|
|
37
|
+
|
|
38
|
+
//分類頁的SEO
|
|
39
|
+
public static async collectionSeo(cf: {
|
|
40
|
+
appName: string,
|
|
41
|
+
language: string,
|
|
42
|
+
data: any,
|
|
43
|
+
page: string
|
|
44
|
+
}) {
|
|
45
|
+
const cols =
|
|
46
|
+
(
|
|
47
|
+
await Manager.getConfig({
|
|
48
|
+
appName: cf.appName,
|
|
49
|
+
key: 'collection',
|
|
50
|
+
language: cf.language as any,
|
|
51
|
+
})
|
|
52
|
+
)[0] ?? {};
|
|
53
|
+
const colJson = extractCols(cols);
|
|
54
|
+
const urlCode = decodeURI((cf.page as string).split('/')[1]);
|
|
55
|
+
const colData = colJson.find((item: any) => {
|
|
56
|
+
if (item.language_data && item.language_data[cf.language]) {
|
|
57
|
+
return item.language_data[cf.language].seo.domain === urlCode || item.title === urlCode
|
|
58
|
+
} else {
|
|
59
|
+
return (item.code === urlCode) || item.title === urlCode;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (colData) {
|
|
63
|
+
if (colData.language_data && colData.language_data[cf.language]) {
|
|
64
|
+
cf.data.page_config.seo.title = colData.language_data[cf.language].seo.title || urlCode;
|
|
65
|
+
cf.data.page_config.seo.content = colData.language_data[cf.language].seo.content;
|
|
66
|
+
cf.data.tag = cf.page
|
|
67
|
+
} else {
|
|
68
|
+
cf.data.page_config.seo.title = colData.seo_title || urlCode;
|
|
69
|
+
cf.data.page_config.seo.content = colData.seo_content;
|
|
70
|
+
cf.data.page_config.seo.keywords = colData.seo_keywords;
|
|
71
|
+
cf.data.tag = cf.page
|
|
72
|
+
}
|
|
73
|
+
cf.data.page_config.seo.image = colData.seo_image;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//分銷連結的SEO
|
|
78
|
+
public static async distributionSEO(cf: {
|
|
79
|
+
appName: string,
|
|
80
|
+
page: string,
|
|
81
|
+
url: string,
|
|
82
|
+
link_prefix: string,
|
|
83
|
+
data: any,
|
|
84
|
+
language: string
|
|
85
|
+
}) {
|
|
86
|
+
const redURL = new URL(`https://127.0.0.1${cf.url}`);
|
|
87
|
+
const rec = await db.query(
|
|
88
|
+
`SELECT *
|
|
89
|
+
FROM \`${cf.appName}\`.t_recommend_links
|
|
90
|
+
WHERE content ->>'$.link' = ?;
|
|
91
|
+
`,
|
|
92
|
+
[(cf.page as string).split('/')[1]]
|
|
93
|
+
);
|
|
94
|
+
const page = rec[0] && rec[0].content ? rec[0].content : {status: false};
|
|
95
|
+
if (page.status && isCurrentTimeWithinRange(page)) {
|
|
96
|
+
let query = [`(content->>'$.type'='article')`, `(content->>'$.tag'='${page.redirect.split('/')[2]}')`];
|
|
97
|
+
const article: any = await new UtDatabase(cf.appName, `t_manager_post`).querySql(query, {
|
|
98
|
+
page: 0,
|
|
99
|
+
limit: 1,
|
|
100
|
+
});
|
|
101
|
+
cf.data.page_config = cf.data.page_config ?? {};
|
|
102
|
+
cf.data.page_config.seo = cf.data.page_config.seo ?? {};
|
|
103
|
+
if (article.data[0]) {
|
|
104
|
+
if (article.data[0].content.language_data[cf.language]) {
|
|
105
|
+
cf.data.page_config.seo.title = article.data[0].content.language_data[cf.language].seo.title;
|
|
106
|
+
cf.data.page_config.seo.content = article.data[0].content.language_data[cf.language].seo.content;
|
|
107
|
+
cf.data.page_config.seo.keywords = article.data[0].content.language_data[cf.language].seo.keywords;
|
|
108
|
+
} else {
|
|
109
|
+
cf.data.page_config.seo.title = article.data[0].content.seo.title;
|
|
110
|
+
cf.data.page_config.seo.content = article.data[0].content.seo.content;
|
|
111
|
+
cf.data.page_config.seo.keywords = article.data[0].content.seo.keywords;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
return html`localStorage.setItem('distributionCode','${page.code}');
|
|
116
|
+
location.href = '${cf.link_prefix ? `/` : ``}${cf.link_prefix}${page.redirect}${redURL.search}';
|
|
117
|
+
`;
|
|
118
|
+
} else {
|
|
119
|
+
return html`location.href = '/';`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//商品頁面SEO
|
|
124
|
+
public static async productSEO(cf: {
|
|
125
|
+
data: any,
|
|
126
|
+
language: any,
|
|
127
|
+
appName: string,
|
|
128
|
+
product_id: string,
|
|
129
|
+
page: string
|
|
130
|
+
}) {
|
|
131
|
+
const product_domain = (cf.page as string).split('/')[1];
|
|
132
|
+
const pd = await new Shopping(cf.appName, undefined).getProduct(
|
|
133
|
+
product_domain
|
|
134
|
+
? {
|
|
135
|
+
page: 0,
|
|
136
|
+
limit: 1,
|
|
137
|
+
domain: decodeURIComponent(product_domain),
|
|
138
|
+
language: cf.language,
|
|
139
|
+
}
|
|
140
|
+
: {
|
|
141
|
+
page: 0,
|
|
142
|
+
limit: 1,
|
|
143
|
+
id: cf.product_id as string,
|
|
144
|
+
language: cf.language,
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
if (pd.data.content) {
|
|
148
|
+
pd.data.content.language_data = pd.data.content.language_data ?? {};
|
|
149
|
+
const productSeo = (pd.data.content.language_data[cf.language] && pd.data.content.language_data[cf.language].seo) || (pd.data.content.seo ?? {});
|
|
150
|
+
const language_data = pd.data.content.language_data
|
|
151
|
+
cf.data.page_config = cf.data.page_config ?? {};
|
|
152
|
+
cf.data.page_config.seo = cf.data.page_config.seo ?? {};
|
|
153
|
+
cf.data.page_config.seo.title = productSeo.title;
|
|
154
|
+
cf.data.page_config.seo.image = (language_data && language_data[cf.language] && language_data.preview_image && language_data.preview_image[0]) || pd.data.content.preview_image[0];
|
|
155
|
+
cf.data.page_config.seo.content = productSeo.content;
|
|
156
|
+
cf.data.tag = cf.page;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//網誌頁面SEO
|
|
161
|
+
public static async articleSeo(cf: {
|
|
162
|
+
article: any,
|
|
163
|
+
page: string,
|
|
164
|
+
appName: string,
|
|
165
|
+
data: any,
|
|
166
|
+
language: any
|
|
167
|
+
}) {
|
|
168
|
+
cf.article = cf.article || (cf.page as any).split('/')[1];
|
|
169
|
+
let query = [`(content->>'$.type'='article')`, `(content->>'$.tag'='${cf.article}')`];
|
|
170
|
+
const article: any = await new UtDatabase(cf.appName, `t_manager_post`).querySql(query, {
|
|
171
|
+
page: 0,
|
|
172
|
+
limit: 1,
|
|
173
|
+
});
|
|
174
|
+
cf.data.page_config = cf.data.page_config ?? {};
|
|
175
|
+
cf.data.page_config.seo = cf.data.page_config.seo ?? {};
|
|
176
|
+
if (article.data[0]) {
|
|
177
|
+
cf.data.tag = cf.page;
|
|
178
|
+
if (article.data[0].content.language_data && article.data[0].content.language_data[cf.language]) {
|
|
179
|
+
cf.data.page_config.seo.title = article.data[0].content.language_data[cf.language].seo.title;
|
|
180
|
+
cf.data.page_config.seo.content = article.data[0].content.language_data[cf.language].seo.content;
|
|
181
|
+
cf.data.page_config.seo.keywords = article.data[0].content.language_data[cf.language].seo.keywords;
|
|
182
|
+
} else {
|
|
183
|
+
cf.data.page_config.seo.title = article.data[0].content.seo.title;
|
|
184
|
+
cf.data.page_config.seo.content = article.data[0].content.seo.content;
|
|
185
|
+
cf.data.page_config.seo.keywords = article.data[0].content.seo.keywords;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//取得多國語言
|
|
191
|
+
public static async language(store_info: any, req: any) {
|
|
192
|
+
function checkIncludes(lan: string) {
|
|
193
|
+
return store_info.language_setting.support.includes(lan);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function checkEqual(lan: string) {
|
|
197
|
+
return `${req.query.page}`.startsWith(`${lan}/`) || req.query.page === lan;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function replace(lan: string) {
|
|
201
|
+
if (req.query.page === lan) {
|
|
202
|
+
req.query.page = '';
|
|
203
|
+
} else {
|
|
204
|
+
req.query.page = `${req.query.page}`.replace(lan + '/', '');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (checkEqual('en') && checkIncludes('en-US')) {
|
|
209
|
+
replace('en');
|
|
210
|
+
return `en-US`;
|
|
211
|
+
} else if (checkEqual('cn') && checkIncludes('zh-CN')) {
|
|
212
|
+
replace('cn');
|
|
213
|
+
return `zh-CN`;
|
|
214
|
+
} else if (checkEqual('tw') && checkIncludes('zh-TW')) {
|
|
215
|
+
replace('tw');
|
|
216
|
+
return `zh-TW`;
|
|
217
|
+
} else {
|
|
218
|
+
return store_info.language_setting.def;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//FB像素
|
|
223
|
+
public static fbCode(FBCode: any) {
|
|
224
|
+
return FBCode && FBCode.pixel
|
|
225
|
+
? html`<!-- Meta Pixel Code -->
|
|
226
|
+
<script>
|
|
227
|
+
!(function (f, b, e, v, n, t, s) {
|
|
228
|
+
if (f.fbq) return;
|
|
229
|
+
n = f.fbq = function () {
|
|
230
|
+
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
|
|
231
|
+
};
|
|
232
|
+
if (!f._fbq) f._fbq = n;
|
|
233
|
+
n.push = n;
|
|
234
|
+
n.loaded = !0;
|
|
235
|
+
n.version = '2.0';
|
|
236
|
+
n.queue = [];
|
|
237
|
+
t = b.createElement(e);
|
|
238
|
+
t.async = !0;
|
|
239
|
+
t.src = v;
|
|
240
|
+
s = b.getElementsByTagName(e)[0];
|
|
241
|
+
s.parentNode.insertBefore(t, s);
|
|
242
|
+
})(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
|
|
243
|
+
fbq('init', '${FBCode.pixel}');
|
|
244
|
+
fbq('track', 'PageView');
|
|
245
|
+
</script>
|
|
246
|
+
<noscript><img height="1" width="1" style="display:none"
|
|
247
|
+
src="https://www.facebook.com/tr?id=617830100580621&ev=PageView&noscript=1"/>
|
|
248
|
+
</noscript>
|
|
249
|
+
<!-- End Meta Pixel Code -->`
|
|
250
|
+
: ''
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//GA標籤
|
|
254
|
+
public static gTag(g_tag: any[]) {
|
|
255
|
+
return (g_tag || [])
|
|
256
|
+
.map((dd: any) => {
|
|
257
|
+
return html`<!-- Google tag (gtag.js) -->
|
|
258
|
+
<!-- Google Tag Manager -->
|
|
259
|
+
<script>
|
|
260
|
+
(function (w, d, s, l, i) {
|
|
261
|
+
w[l] = w[l] || [];
|
|
262
|
+
w[l].push({
|
|
263
|
+
'gtm.start': new Date().getTime(),
|
|
264
|
+
event: 'gtm.js',
|
|
265
|
+
});
|
|
266
|
+
var f = d.getElementsByTagName(s)[0],
|
|
267
|
+
j = d.createElement(s),
|
|
268
|
+
dl = l != 'dataLayer' ? '&l=' + l : '';
|
|
269
|
+
j.async = true;
|
|
270
|
+
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
|
|
271
|
+
f.parentNode.insertBefore(j, f);
|
|
272
|
+
})(window, document, 'script', 'dataLayer', '${dd.code}');
|
|
273
|
+
</script>
|
|
274
|
+
<!-- End Google Tag Manager -->`;
|
|
275
|
+
})
|
|
276
|
+
.join('')
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
//GA追蹤
|
|
280
|
+
public static gA4(ga4: any[]) {
|
|
281
|
+
return (ga4 || [])
|
|
282
|
+
.map((dd: any) => {
|
|
283
|
+
return html`<!-- Google tag (gtag.js) -->
|
|
284
|
+
<script async
|
|
285
|
+
src="https://www.googletagmanager.com/gtag/js?id=${dd.code}"></script>
|
|
286
|
+
<script>
|
|
287
|
+
window.dataLayer = window.dataLayer || [];
|
|
288
|
+
|
|
289
|
+
function gtag() {
|
|
290
|
+
dataLayer.push(arguments);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
gtag('js', new Date());
|
|
294
|
+
|
|
295
|
+
gtag('config', '${dd.code}');
|
|
296
|
+
</script>`;
|
|
297
|
+
})
|
|
298
|
+
.join('')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
export function extractCols(data: {
|
|
306
|
+
value: any[];
|
|
307
|
+
updated_at: Date;
|
|
308
|
+
}) {
|
|
309
|
+
let items: any = [];
|
|
310
|
+
const updated_at = new Date(data.updated_at).toISOString().replace(/\.\d{3}Z$/, '+00:00');
|
|
311
|
+
data.value.map((item: any) => {
|
|
312
|
+
items.push({
|
|
313
|
+
...item,
|
|
314
|
+
updated_at
|
|
315
|
+
});
|
|
316
|
+
if (item.array && item.array.length > 0) {
|
|
317
|
+
items = items.concat(extractCols({
|
|
318
|
+
value: item.array,
|
|
319
|
+
updated_at: data.updated_at
|
|
320
|
+
}))
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
return items;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function extractProds(data: any) {
|
|
327
|
+
const items: any = [];
|
|
328
|
+
data.map((item: any) => {
|
|
329
|
+
const updated_at = new Date(item.updated_time).toISOString().replace(/\.\d{3}Z$/, '+00:00');
|
|
330
|
+
items.push({items});
|
|
331
|
+
});
|
|
332
|
+
return items;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 判斷現在時間是否在 start 和 end 之間的函數
|
|
336
|
+
function isCurrentTimeWithinRange(data: {
|
|
337
|
+
startDate: string;
|
|
338
|
+
startTime: string;
|
|
339
|
+
endDate?: string;
|
|
340
|
+
endTime?: string
|
|
341
|
+
}): boolean {
|
|
342
|
+
const now = new Date();
|
|
343
|
+
now.setTime(now.getTime() + 8 * 3600 * 1000);
|
|
344
|
+
// 組合 start 的完整日期時間
|
|
345
|
+
const startDateTime = new Date(`${data.startDate}T${data.startTime}`);
|
|
346
|
+
|
|
347
|
+
// 若 endDate 或 endTime 為 undefined,視為無期限
|
|
348
|
+
const hasEnd = data.endDate && data.endTime;
|
|
349
|
+
const endDateTime = hasEnd ? new Date(`${data.endDate}T${data.endTime}`) : null;
|
|
350
|
+
|
|
351
|
+
// 判斷現在時間是否在範圍內
|
|
352
|
+
if (hasEnd) {
|
|
353
|
+
return now >= startDateTime && now <= endDateTime!;
|
|
354
|
+
} else {
|
|
355
|
+
return now >= startDateTime;
|
|
356
|
+
}
|
|
357
|
+
}
|