create-fesd-app 1.0.14 → 1.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fesd-app",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@tailwindcss/postcss": "^4.1.10",
26
26
  "@tailwindcss/vite": "^4.1.10",
27
- "@xwadex/fesd": "0.0.37",
27
+ "@xwadex/fesd": "0.0.38",
28
28
  "ansi-colors": "^4.1.3",
29
29
  "chalk": "^5.3.0",
30
30
  "clsx": "^2.1.1",
@@ -100,6 +100,7 @@
100
100
  cursor: pointer
101
101
  +rwdmax(767)
102
102
  .cookie-main
103
+ padding: 50px 25px
103
104
  margin: auto auto 0 auto
104
105
  .cookie-container
105
106
  flex-direction: column
@@ -114,6 +115,9 @@
114
115
  .cookie-container
115
116
  .button-group
116
117
  flex-direction: column
118
+ .cookie-settingBtn
119
+ margin-top: 30px
120
+ margin-right: auto
117
121
 
118
122
  [data-step]
119
123
  opacity: 0
@@ -193,9 +197,19 @@
193
197
  align-items: center
194
198
  flex-shrink: 0
195
199
  .icon
200
+ position: relative
196
201
  cursor: pointer
197
202
  opacity: .2
198
203
  transition: opacity .5s
204
+ &::after
205
+ position: absolute
206
+ left: 50%
207
+ top: 50%
208
+ transform: translate3d(-50%,-50%,0)
209
+ width: 30px
210
+ height: 50px
211
+ z-index: 1
212
+ content: ""
199
213
  .icon-arrow
200
214
  font-size: px(10)
201
215
  +hover
@@ -283,7 +297,7 @@
283
297
  border-radius: 500px
284
298
  background: #fff
285
299
  border: 1px solid rgba(0, 0, 0, .1)
286
- transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1)
300
+ transition: all 0.28s cubic-bezier(0.4, 0, 0.2, 1)
287
301
  vertical-align: middle
288
302
  cursor: pointer
289
303
  &::after
@@ -305,13 +319,16 @@
305
319
  height: 15px
306
320
  background: #808080
307
321
  border-radius: 50%
308
- transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1)
322
+ transform: translateX(0)
323
+ will-change: transform
324
+ transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1)
309
325
  &:active::before
310
326
  box-shadow: 0 2px 8px rgba(0,0,0,0.28), 0 0 0 20px rgba(128,128,128,0.1)
311
327
  input:checked + .switch
312
328
  background: #006FFF
313
329
  input:checked + .switch::before
314
- left: 23px
330
+ // left: 23px
331
+ transform: translateX(18px)
315
332
  background: #fff
316
333
  input:checked + .switch::after
317
334
  content: "啟用"
@@ -77,10 +77,6 @@ $typeFull-padding_rwd: 30px 20px
77
77
  $typeFull-content-padding: 85px 75px 100px
78
78
  $typeFull-content-padding_rwd: 20px
79
79
 
80
- // swiper pagination
81
- $pagination-TB-gap: 20px
82
- $pagination-TB-gap_rwd: 10px
83
-
84
80
  // 表格與其他物件上下間的距離
85
81
  $table-TB-gap: 20px
86
82
  $table-TB-gap_rwd: 20px
@@ -341,28 +337,31 @@ $quote-TB-gap: 20px
341
337
  // @extend %table_border_rows
342
338
  // @extend %table_border_full
343
339
  @extend %table_solidSingle_borderColumn
340
+
341
+ &[data-table-markdown="on"]
342
+ table p
343
+ white-space: pre-wrap
344
+
344
345
  table
345
346
  th, td
346
347
  text-align: left
347
348
  background: #fff
349
+ span
350
+ display: block
351
+ ol,ul
352
+ &:not(:first-child)
353
+ margin-top: 10px
348
354
  thead
349
355
  th
350
356
  // 以下可修改
351
357
  font-size: 16px
352
358
  line-height: 1.2
353
359
  font-weight: 600
354
- span
355
- display: block
356
360
  tbody
357
361
  td
358
362
  // 以下可修改
359
363
  font-size: 16px
360
364
  line-height: 1.2
361
- span
362
- display: block
363
- ol,ul
364
- &:not(:first-child)
365
- margin-top: 10px
366
365
 
367
366
  %table_tipText
368
367
  // 以下可修改
@@ -581,8 +580,6 @@ $quote-TB-gap: 20px
581
580
  line-height: 1.2
582
581
  &:not(:first-child)
583
582
  margin-top: 10px
584
- p
585
- white-space: pre-wrap
586
583
  em
587
584
  font-style: italic
588
585
  a
@@ -1,13 +1,19 @@
1
+ // 此處為 demo 資料, 後端會用相同資料結構洗資料在 #_wcookie 上
2
+ // 給後端 :
3
+ // 基本上進階版細項開關不開, 有需求才開; 若開啟進階版細項, 大分類的 close 開關預設會無效, 會以判斷細項是否有開啟 or 全關為主
4
+ // close: 0 -> 開啟; close: 1 -> 關閉
1
5
  export const cookieData = [
2
6
  {
7
+ multiType: true, // 是否開啟個別選項設定
3
8
  title: "您的 Cookie 偏好設定",
4
9
  text: "我們使用不同類型的 Cookies 來優化您在我們的網站上的體驗,點擊下面類別以了解其目的與更多訊息。<br>您可以選擇允許的 Cookies 類型,也可以隨時更改您的偏好設定。<br>提醒您,停用 Cookies 可能會影響您在網站上的體驗,您可以瀏覽我們的隱私權政策,進一步了解我們如何使用 Cookie。",
5
10
  options: [
6
11
  {
7
12
  key: "required",
13
+ close: 0,
14
+ disabled: true,
8
15
  title: "必要",
9
16
  text: "這些 Cookies 對於支持核心網站功能(例如提供安全登錄)至關重要。",
10
- disabled: true,
11
17
  data: {
12
18
  items: [
13
19
  {
@@ -15,18 +21,26 @@ export const cookieData = [
15
21
  text: "第一方 cookies 由用戶訪問的網站(即主機域名)設置。網站通常使用第一方 cookies 用於跟踪訪客在網站上的行為並個性化他們在網站上的瀏覽體驗以及個性化他們的瀏覽體驗。",
16
22
  list: [
17
23
  {
24
+ key: "PHPSESSID",
25
+ close: 0,
18
26
  title: "PHPSESSID",
19
27
  text: "php用來辨識用戶的session id",
20
28
  },
21
29
  {
30
+ key: "XSRF",
31
+ close: 0,
22
32
  title: "XSRF-TOKEN",
23
33
  text: "網站防止 CSRF(Cross-Site Request Forgery,跨站請求偽造)攻擊 的 token",
24
34
  },
25
35
  {
36
+ key: "web_session",
37
+ close: 0,
26
38
  title: "web_session",
27
39
  text: "網站用來辨識用戶的session id",
28
40
  },
29
41
  {
42
+ key: "hubspot",
43
+ close: 0,
30
44
  title: "hubspot",
31
45
  text: "hubspot 使用了Cloudflare服務,區分真人訪客與惡意機器人",
32
46
  },
@@ -37,12 +51,12 @@ export const cookieData = [
37
51
  },
38
52
  {
39
53
  key: "analytics",
54
+ close: 1,
55
+ disabled: false,
40
56
  title: "分析與自訂",
41
57
  text: "這些 Cookies 分析網站優化的使用情況。",
42
58
  notice: "我們將無法分析您的網站使用情況,以提供自定義內容",
43
- disabled: false,
44
59
  data: {
45
- // notice: "",
46
60
  items: [
47
61
  {
48
62
  main: "第一方 Cookie 提供商",
@@ -50,9 +64,13 @@ export const cookieData = [
50
64
  list: [
51
65
  {
52
66
  title: "gtm",
67
+ key: "gtm",
68
+ close: 1,
53
69
  },
54
70
  {
55
71
  title: "ga",
72
+ key: "ga",
73
+ close: 1,
56
74
  detail: [
57
75
  {
58
76
  title: "_ga",
@@ -76,10 +94,14 @@ export const cookieData = [
76
94
  list: [
77
95
  {
78
96
  title: "googlefont",
97
+ key: "googlefont",
98
+ close: 1,
79
99
  },
80
100
  {
81
101
  title: "Youtube",
82
102
  text: "https://www.youtube.com/",
103
+ key: "Youtube",
104
+ close: 1,
83
105
  detail: [
84
106
  {
85
107
  title: "YSC",
@@ -121,6 +143,8 @@ export const cookieData = [
121
143
  {
122
144
  title: "Vimeo",
123
145
  text: "https://vimeo.com/",
146
+ key: "Vimeo",
147
+ close: 1,
124
148
  detail: [
125
149
  {
126
150
  title: "_cf_bm",
@@ -139,6 +163,8 @@ export const cookieData = [
139
163
  {
140
164
  title: "Tiktok",
141
165
  text: "https://www.tiktok.com/zh-Hant-TW/",
166
+ key: "Tiktok",
167
+ close: 1,
142
168
  detail: [
143
169
  {
144
170
  title: "_ttp",
@@ -169,6 +195,8 @@ export const cookieData = [
169
195
  {
170
196
  title: "instagram",
171
197
  text: "https://www.instagram.com/",
198
+ key: "instagram",
199
+ close: 1,
172
200
  detail: [
173
201
  {
174
202
  title: "mid",
@@ -181,6 +209,8 @@ export const cookieData = [
181
209
  {
182
210
  title: "meta",
183
211
  text: "https://www.facebook.com/privacy/policy",
212
+ key: "meta",
213
+ close: 1,
184
214
  detail: [
185
215
  {
186
216
  title: "_fbp",
@@ -197,15 +227,17 @@ export const cookieData = [
197
227
  },
198
228
  {
199
229
  key: "functionality",
230
+ close: 1,
231
+ disabled: false,
200
232
  title: "性能與功能",
201
233
  text: "這些 Cookies 有助於測量和分析,以改善瀏覽體驗",
202
- disabled: false,
203
234
  },
204
235
  {
205
236
  key: "advertising",
237
+ close: 1,
238
+ disabled: false,
206
239
  title: "廣告",
207
240
  text: "這些 Cookies 有助於提供與您相關的廣告內容",
208
- disabled: false,
209
241
  },
210
242
  ],
211
243
  },
@@ -0,0 +1,157 @@
1
+ // js 產 cookie 結構方法
2
+ /**
3
+ * @param {Array} target
4
+ * @param {(opt: cookieData[0], index: number) => string} fn
5
+ */
6
+ const generateHtmlString = (target, fn) => {
7
+ return target.map((data, index) => fn(data, index)).join("")
8
+ }
9
+
10
+ export const cookieStep1El = (opt, index, isDisabled, isChecked) => {
11
+ return `
12
+ <div class="item" data-index="${index}">
13
+ <div class="left">
14
+ <div class="checkbox ${isDisabled}">
15
+ <div class="input-wrap">
16
+ <input type="checkbox"
17
+ id="s1-check-${index}"
18
+ ${isChecked()}
19
+ data-name="${opt.key}"
20
+ >
21
+ <div class="fake-checkbox">
22
+ <i class="icon-check"></i>
23
+ </div>
24
+ </div>
25
+ <label for="s1-check-${index}">
26
+ <span>${opt.title}</span>
27
+ </label>
28
+ </div>
29
+ <div class="desc">
30
+ <div class="text">${opt.text}</div>
31
+ ${opt.notice ? `
32
+ <div class="notice">
33
+ <div class="icon">
34
+ <i class="icon-notice"></i>
35
+ </div>
36
+ <span>${opt.notice}</span>
37
+ </div>` : ""}
38
+ </div>
39
+ </div>
40
+ <div class="right">
41
+ <div class="switch-box ${isDisabled}">
42
+ <input type="checkbox" hidden
43
+ id="s1-switch-${index}"
44
+ ${isChecked()}
45
+ data-name="${opt.key}"
46
+ >
47
+ <label class="switch" for="s1-switch-${index}"></label>
48
+ </div>
49
+ <div class="icon" data-open>
50
+ <i class="icon-arrow"></i>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ `
55
+ }
56
+
57
+ export const cookieStep2TittleEl = (opt, index, isDisabled, isChecked) => {
58
+ return `
59
+ <div class="box">
60
+ <div class="left">
61
+ <div class="checkbox ${isDisabled}">
62
+ <div class="input-wrap">
63
+ <input type="checkbox"
64
+ id="s2-check-${index}"
65
+ ${isChecked()}
66
+ data-name="${opt.key}"
67
+ >
68
+ <div class="fake-checkbox">
69
+ <i class="icon-check"></i>
70
+ </div>
71
+ </div>
72
+ <label for="s2-check-${index}">
73
+ <span>${opt.title}</span>
74
+ </label>
75
+ </div>
76
+ </div>
77
+ <div class="right">
78
+ <div class="switch-box ${isDisabled}">
79
+ <input type="checkbox" hidden
80
+ id="s2-switch-${index}"
81
+ ${isChecked()}
82
+ data-name="${opt.key}"
83
+ >
84
+ <label class="switch" for="s2-switch-${index}"></label>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <div class="desc">
89
+ <div class="text">${opt.text || ""}</div>
90
+ ${opt.notice ?
91
+ `<div class="notice">
92
+ <div class="icon">
93
+ <i class="icon-notice"></i>
94
+ </div>
95
+ <span>${opt.notice}</span>
96
+ </div> ` : ""}
97
+ </div>
98
+ `
99
+ }
100
+
101
+ export const cookieStep2DetailEl = (data, li, index, parentKey, num, isDisabled, isListChecked) => {
102
+ return `
103
+ <div class="list collapseItem" data-collapse>
104
+ <div class="title collapseTitle" data-collapse-click>
105
+ <div class="icon">
106
+ <i class="icon-arrow"></i>
107
+ </div>
108
+ <span>${li.title || ""}</span>
109
+ ${data.multiType ?
110
+ `<div class="switch-box ${isDisabled}">
111
+ <input
112
+ type="checkbox"
113
+ hidden
114
+ id="s2-content-${num}-${index}"
115
+ ${isListChecked}
116
+ data-name="${li.key}"
117
+ data-parent="${parentKey || ""}"
118
+ >
119
+ <label
120
+ class="switch"
121
+ for="s2-content-${num}-${index}">
122
+ </label>
123
+ </div>`
124
+ : ""
125
+ }
126
+ </div>
127
+ ${li.text ?
128
+ `<div class="text">${li.text}</div>`
129
+ : ""
130
+ }
131
+ <div class="list-detail collapseBox" data-collapse-content>
132
+ <div class="innerBox">
133
+ <div class="content">
134
+ ${li.detail?.length > 0 ?
135
+ generateHtmlString(li.detail, (i) => {
136
+ return `
137
+ <div class="li">
138
+ <div class="title">${i.title || ""}</div>
139
+ ${i.text ?
140
+ `<div class="text">${i.text}</div>`
141
+ : ""
142
+ }
143
+ <div class="flex">
144
+ <div class="text bold">${i.typeTitle || ""}</div>
145
+ <div class="text">${i.typeDesc || ""}</div>
146
+ </div>
147
+ </div>
148
+ `
149
+ })
150
+ : ""
151
+ }
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ `
157
+ }
@@ -5,15 +5,24 @@ import {
5
5
  scrollLock,
6
6
  scrollUnlock
7
7
  } from '@xwadex/fesd/tools';
8
- import Cookies from "js-cookie"
9
- import { OverlayScrollbars } from 'overlayscrollbars'
10
- import { cookieData } from "./cookieData"
11
-
8
+ import Cookies from "js-cookie";
9
+ import { OverlayScrollbars } from 'overlayscrollbars';
10
+ import { cookieData } from "./cookieData";
11
+ import { cookieStep1El, cookieStep2TittleEl, cookieStep2DetailEl } from "./cookieElement"
12
+
13
+ // 基礎版 cookie
14
+ const cookieBasic = {}
15
+ // 進階版 cookie
16
+ const cookieAdvanced = {}
17
+ // 存方法工具
12
18
  const cookieSetting = {}
19
+ // 存結構
13
20
  const cookieElements = {}
21
+ // 存一個空物件
22
+ let cookieNewObj = {};
14
23
 
15
24
  // 簡易版 cookie 設定
16
- cookieSetting.basicCookiePolicy = () => {
25
+ cookieBasic.basicCookiePolicy = () => {
17
26
  const $cookiePolicy = $(".cookie-check");
18
27
  if (!$cookiePolicy.length) return;
19
28
 
@@ -38,12 +47,30 @@ cookieSetting.basicCookiePolicy = () => {
38
47
  $agreeBtn.on("click", () => {
39
48
  $cookiePolicy.addClass("agree");
40
49
  localStorage.setItem("frameworkCookie-agree", "true");
50
+ // 執行後端方法
51
+ if (typeof window._wdSetting === 'function') {
52
+ window._wdSetting()
53
+ }
41
54
  });
42
55
 
43
56
  $closeBtn.on("click", () => {
44
57
  $cookiePolicy.addClass("hidden");
45
58
  sessionStorage.setItem("frameworkCookie-agree", "false");
59
+ if (typeof window._wdSetting === 'function') {
60
+ window._wdSetting()
61
+ }
46
62
  });
63
+
64
+ // ⚠️ 以下可斟酌移除
65
+ // 開啟 cookie 視窗按鈕
66
+ $("[data-cookie-open]").on("click", function() {
67
+ $cookiePolicy.addClass("first-enter");
68
+ $cookiePolicy.removeClass("agree");
69
+ $cookiePolicy.removeClass("hidden");
70
+ setTimeout(() => {
71
+ $cookiePolicy.addClass("show");
72
+ }, 800);
73
+ })
47
74
  };
48
75
 
49
76
  // 執行事件
@@ -51,33 +78,90 @@ cookieSetting.basicCookiePolicy = () => {
51
78
  clickEvent();
52
79
  };
53
80
 
54
- // cookie 開啟方法
55
- cookieSetting.openEvent = (el) => {
56
- $(el).addClass("active")
57
- setTimeout(() => {
58
- $(el).addClass("show")
59
- scrollLock();
60
- const scrollers = $(".cookie-settingBlock").find('.scroller')[0]
61
- OverlayScrollbars(scrollers, {})
62
- }, 500);
81
+ // ⚠️ ** --- 結構這邊修改 --- ** ⚠️
82
+ // step 1 結構
83
+ cookieElements.getStep1Element = (opt, index) => {
84
+ const isDisabled = opt.disabled ? "disabled" : ""
85
+ const isChecked = () => {
86
+ if (opt.key == "required") {
87
+ return "checked"
88
+ } else {
89
+ if (cookieSetting.hasAnyListItemsOpen(opt.key)) {
90
+ return "checked"
91
+ } else {
92
+ return ""
93
+ }
94
+ }
95
+ }
96
+ return cookieStep1El(opt, index, isDisabled, isChecked)
63
97
  }
64
98
 
65
- // cookie 關閉方法
66
- cookieSetting.closeEvent = (el) => {
67
- $(el).removeClass("show")
68
- setTimeout(() => {
69
- $(el).removeClass("active")
70
- scrollUnlock();
71
- }, 500);
99
+ // step 2 上面結構
100
+ cookieElements.getStep2TopElement = (opt, index) => {
101
+ const isDisabled = opt.disabled ? "disabled" : ""
102
+ const isChecked = () => {
103
+ if (opt.key == "required") {
104
+ return "checked"
105
+ } else {
106
+ if (cookieSetting.hasAnyListItemsOpen(opt.key)) {
107
+ return "checked"
108
+ } else {
109
+ return ""
110
+ }
111
+ }
112
+ }
113
+ return cookieStep2TittleEl(opt, index, isDisabled, isChecked)
72
114
  }
73
115
 
74
- // 打開進階版 cookie 介面
75
- cookieSetting.clickEvent = () => {
76
- const $settingBtn = $("[data-cookie-open]")
77
- const $settingBlock = $(".cookie-settingBlock")
78
- $settingBtn.on("click", () => {
79
- cookieSetting.openEvent($settingBlock)
80
- });
116
+ // step 2 下面細項結構
117
+ cookieElements.getStep2BottomElement = (data, li, index, parentKey, num) => {
118
+ const isDisabled = li.disabled ? " disabled" : ""
119
+ const isListChecked = cookieNewObj[parentKey].list[li.key].close == 0 ? "checked" : ""
120
+
121
+ return cookieStep2DetailEl(data, li, index, parentKey, num, isDisabled, isListChecked)
122
+ }
123
+
124
+ // 產生新物件
125
+ cookieAdvanced.newObj = () => {
126
+ if (Cookies.get('_wd') !== undefined) {
127
+ const cloudDdata = JSON.parse(Cookies.get('_wd'))
128
+ cookieNewObj = { ...cloudDdata }
129
+ } else {
130
+ const data = $("#_wcookie").val() ? JSON.parse($("#_wcookie").val())[0] : cookieData[0]
131
+ const options = data?.options;
132
+ if (options) {
133
+ // 判斷是否全開 or 全關 (排除 required)
134
+ const nonRequiredOptions = options.filter(opt => opt.key !== 'required');
135
+ const allNonRequiredAreFalse = nonRequiredOptions.every(opt => opt.close === 1);
136
+ const globalCloseValue = allNonRequiredAreFalse == 1 ? 1 : 0;
137
+
138
+ cookieNewObj.close = globalCloseValue;
139
+
140
+ const categoriesObject = options.reduce((acc, option) => {
141
+ const topLevelKey = option.key;
142
+ const dataItems = option.data?.items;
143
+
144
+ acc[topLevelKey] = { close: option.close };
145
+
146
+ if (data.multiType && dataItems !== undefined && dataItems.length > 0) {
147
+ acc[topLevelKey].list = {};
148
+
149
+ dataItems.forEach(item => {
150
+ item.list?.forEach(listItem => {
151
+ if (listItem.key) {
152
+ acc[topLevelKey].list[listItem.key] = {
153
+ close: listItem.close
154
+ };
155
+ }
156
+ });
157
+ });
158
+ }
159
+ return acc;
160
+ }, {});
161
+ // 若無 cookie 紀錄可抓, 直接新建一個新的物件預存
162
+ Object.assign(cookieNewObj, categoriesObject);
163
+ }
164
+ }
81
165
  }
82
166
 
83
167
  // js 產 cookie 結構方法
@@ -90,42 +174,53 @@ cookieElements.generateHtmlString = (target, fn) => {
90
174
  }
91
175
 
92
176
  // 產資料結構
93
- cookieSetting.dataAppend = () => {
177
+ cookieAdvanced.dataAppend = () => {
94
178
  const $block = $(".cookie-settingBlock")
95
179
  if (!$block.length) return
96
180
 
97
- const data = cookieData[0]
181
+ const data = $("#_wcookie").val() ? JSON.parse($("#_wcookie").val())[0] : cookieData[0]
98
182
  const $step1 = $(".setting-container[data-step='1']")
99
183
  const $step2 = $(".setting-container[data-step='2']")
100
184
 
101
- // step1
185
+ // step1 結構
102
186
  $step1.find(".content-block").html(cookieElements.generateHtmlString(data.options, cookieElements.getStep1Element))
103
187
 
104
- // step2:點擊打開
188
+ // step2 點擊打開
105
189
  $("[data-open]").on("click", function () {
106
190
  const index = $(this).closest(".item").data("index")
107
191
  const opt = data.options[index]
108
192
  const items = opt.data?.items || []
109
- const currentVal = $(`input[type="checkbox"][data-name="${opt.key}"]`).first().prop('checked') ?? false
110
193
 
111
- // Step2 上方
112
- $step2.find(".top-block").html(cookieElements.getStep2TopElement(opt, index))
113
-
114
- // Step2 下方
194
+ // ⚠️ ** --- 結構這邊修改 --- ** ⚠️
195
+ // 產 step2 下方結構
115
196
  const htmlStep2bottom = items.length > 0
116
- ? items.map(i => `
197
+ ? items.map((item,num) => `
117
198
  <div class="item">
118
199
  <div class="list">
119
- <div class="main">${i.main}</div>
120
- <div class="text">${i.text}</div>
200
+ <div class="main">${item.main}</div>
201
+ <div class="text">${item.text}</div>
121
202
  </div>
122
203
  ${
123
- (i.list || []).map(li =>
124
- (li.detail && li.detail.length > 0)
125
- ? cookieElements.getStep2BottomElement(li)
204
+ (item.list || []).map((li, index) =>
205
+ (li.detail && li.detail.length > 0) ?
206
+ cookieElements.getStep2BottomElement(data, li, index, opt.key, num)
126
207
  : `
127
208
  <div class="list">
128
- <div class="title">${li.title || ""}</div>
209
+ <div class="title">
210
+ <span>${li.title || ""}</span>
211
+ ${data.multiType && li.key.length > 0 ?
212
+ `<div class="switch-box ${opt.disabled ? "disabled" : ""}">
213
+ <input type="checkbox" hidden
214
+ id="s2-content-${num}-${index}"
215
+ ${cookieNewObj[opt.key].list[li.key].close == 0 ? "checked" : ""}
216
+ data-name="${li.key}"
217
+ data-parent="${opt.key || ""}"
218
+ >
219
+ <label class="switch" for="s2-content-${num}-${index}"></label>
220
+ </div>`
221
+ : ""
222
+ }
223
+ </div>
129
224
  <div class="text">${li.text || ""}</div>
130
225
  </div>
131
226
  `
@@ -135,192 +230,240 @@ cookieSetting.dataAppend = () => {
135
230
  `).join("")
136
231
  : `<div class="item"><div class="text">此分類目前沒有詳細資料。</div></div>`
137
232
 
233
+ // 產 step2 上方結構
234
+ $step2.find(".top-block").html(cookieElements.getStep2TopElement(opt, index))
235
+ // 產 step2 下方結構
138
236
  $step2.find(".content-block").html(htmlStep2bottom)
139
- $step2.find(`input[type="checkbox"][data-name="${opt.key}"]`).prop('checked', currentVal)
140
237
 
238
+ // 換頁
141
239
  methods.pageChange(true)
142
- methods.collapseEvent('[data-collapse-click]', '[data-collapse]', false, true)
240
+ // 重綁收合
241
+ methods.collapseEvent('[data-collapse-click]', '[data-collapse]', '[data-collapse-content]', false, true)
143
242
  })
243
+ }
244
+
245
+ // switch 的一些狀態
246
+ cookieAdvanced.switchState = () => {
247
+ const $step2contentSwitch = `[data-step="2"] .content-block input[type="checkbox"][data-parent][data-name]:not(.disabled)`
248
+ const $step2topSwitch = `[data-step="2"] .top-block input[type="checkbox"][data-name]:not(.disabled)`
249
+
250
+ let isSyncing = false
251
+
252
+ // 開關同步連動控制
253
+ // ⚠️ 若只有一顆控制按鈕, 可關閉
254
+ $(document).on('change', 'input[type="checkbox"][data-name]:not(:disabled)', function () {
255
+ const key = $(this).data('name')
256
+ const val = $(this).prop('checked')
257
+ if (isSyncing) return
258
+ isSyncing = true
144
259
 
145
- // 開關同步
146
- $(document).on('change', 'input[type="checkbox"][data-name]:not(:disabled)', function () {
147
- const key = $(this).data('name')
148
- const val = $(this).prop('checked')
260
+ $(`input[type="checkbox"][data-name="${key}"]`).not(this).prop('checked', val)
149
261
 
150
- $(`input[type="checkbox"][data-name="${key}"]`).prop('checked', val)
262
+ isSyncing = false
151
263
  })
152
- }
153
264
 
154
- // cookie
155
- cookieSetting.cookieSaveFn = (el) => {
156
- const $container = $(".cookie-settingBlock")
157
- const mainCookies = ['required', 'analytics', 'functionality', 'advertising']
265
+ // step1 變動 → 更新 cookieNewObj
266
+ $(document)
267
+ .off('change.cookieStep1')
268
+ .on('change.cookieStep1', `[data-step="1"] input[type="checkbox"][data-name]`, function () {
269
+ if (isSyncing) return
270
+ isSyncing = true
271
+
272
+ const key = $(this).data("name")
273
+ const val = $(this).prop('checked') ? 0 : 1
274
+ if (cookieNewObj[key]) {
275
+ cookieNewObj[key].close = val
276
+ const targetList = cookieNewObj[key].list;
277
+ if (targetList && typeof targetList === 'object') {
278
+ Object.keys(targetList).forEach(childKey => {
279
+ if (targetList[childKey].close !== undefined) {
280
+ targetList[childKey].close = val;
281
+ }
282
+ });
283
+ }
284
+ }
158
285
 
159
- cookieSetting.save = () => {
160
- let allOpen = true;
161
- const mainCookies = ['required', 'analytics', 'functionality', 'advertising'];
162
- const obj = {};
286
+ isSyncing = false
287
+ })
163
288
 
164
- mainCookies.forEach((key) => {
165
- const isOpen = $(`input[type="checkbox"][data-name="${key}"]`)
166
- .first()
167
- .prop('checked');
289
+ $(document)
290
+ .off("change.step2content")
291
+ .on("change.step2content", $step2contentSwitch, function () {
168
292
 
169
- obj[key] = { close: isOpen ? 0 : 1 };
170
- });
293
+ if (isSyncing) return
294
+ isSyncing = true
171
295
 
172
- for (let i = 0; i < mainCookies.length; i++) {
173
- const key = mainCookies[i];
296
+ const key = $(this).data("name")
297
+ const parent = $(this).data("parent")
298
+ const val = $(this).prop('checked') ? 0 : 1
299
+ const $step1Switch = $(`[data-step="1"] input[type="checkbox"][data-name='${parent}']:not(.disabled)`)
300
+ const total = $($step2contentSwitch).length
301
+ const checked = $($step2contentSwitch).filter(':checked').length
174
302
 
175
- if (key === 'required') continue;
303
+ // 改變 cookieNewObj 的值
304
+ cookieNewObj[parent].list[key].close = val
176
305
 
177
- if (obj[key].close !== 1) {
178
- allOpen = false;
179
- break;
306
+ // Step2 子項全部打開
307
+ if (checked === total || checked > 0) {
308
+ cookieNewObj[parent].close = 0
309
+ $($step2topSwitch).prop("checked", true)
310
+ $step1Switch.prop("checked", true)
311
+ }
312
+
313
+ // Step2 子項全部關閉
314
+ else if (checked === 0) {
315
+ cookieNewObj[parent].close = 1
316
+ $($step2topSwitch).prop("checked", false)
317
+ $step1Switch.prop("checked", false)
180
318
  }
181
- }
182
-
183
- obj.close = allOpen ? 1 : 0;
184
319
 
185
- Cookies.set('_wd', JSON.stringify(obj), {
186
- expires: 180, // 180 天
187
- path: '/',
188
- sameSite: true,
189
- });
190
- }
320
+ isSyncing = false
321
+ })
191
322
 
192
- // 重整後重抓 cookie 設定
193
- cookieSetting.restore = () => {
194
- const raw = Cookies.get('_wd')
195
- if (!raw) return
196
- let mystore
197
- try {
198
- mystore = JSON.parse(raw)
199
- } catch {
200
- return
201
- }
202
- mainCookies.forEach((key) => {
203
- if (!mystore[key]) return
204
- const checked = mystore[key].close === 0
205
- $(`input[type="checkbox"][data-name="${key}"]`).prop('checked', checked).trigger('change')
323
+ $(document)
324
+ .off("change.step2top")
325
+ .on("change.step2top", $step2topSwitch, function () {
326
+
327
+ if (isSyncing) return
328
+ isSyncing = true
329
+
330
+ const key = $(this).data("name")
331
+ const isChecked = $(this).prop("checked")
332
+ const val = isChecked ? 0 : 1
333
+
334
+ // 改所有子項
335
+ $($step2contentSwitch)
336
+ .prop('checked', isChecked)
337
+
338
+ // 改 cookie main
339
+ if (cookieNewObj[key]) {
340
+ const list = cookieNewObj[key].list
341
+ cookieNewObj[key].close = val
342
+ if (list) {
343
+ Object.keys(list).forEach(childKey => {
344
+ list[childKey].close = val
345
+ })
346
+ }
347
+ }
348
+ isSyncing = false
206
349
  })
207
- }
350
+ }
351
+
352
+ // 存 cookie
353
+ cookieAdvanced.cookieSaveFn = () => {
354
+ const nonRequiredValues = Object.entries(cookieNewObj)
355
+ .filter(([key]) => key !== 'required' && key !== 'close')
356
+ .map(([, value]) => value);
357
+
358
+ const allNonRequiredAreClosed = nonRequiredValues.every(opt => opt.close === 1);
359
+ const globalCloseValue = allNonRequiredAreClosed ? 1 : 0;
360
+
361
+ // 判斷設定第一層的 close 值
362
+ cookieNewObj.close = globalCloseValue;
363
+
364
+ // 存入 cookie
365
+ Cookies.set('_wd', JSON.stringify(cookieNewObj), {
366
+ expires: 180,
367
+ path: '/',
368
+ sameSite: true,
369
+ });
370
+ }
371
+
372
+ // 點擊方法
373
+ cookieAdvanced.clickEvent = () => {
374
+ const $settingBtn = $(".cookie-settingBtn")
375
+ const $settingBlock = $(".cookie-settingBlock")
376
+
377
+ // 打開進階版 cookie 介面
378
+ $settingBtn.on("click", () => {
379
+ cookieSetting.openEvent($settingBlock)
380
+ });
208
381
 
209
382
  // 一鍵全開
210
383
  cookieSetting.enableAll = () => {
211
- $(`input[type="checkbox"][data-name]:not(:disabled)`).prop('checked', true).trigger('change')
384
+ $(`input[type="checkbox"][data-name]:not(.disabled)`).prop('checked', true).trigger('change')
385
+ cookieNewObj.close = 0
386
+ Object.keys(cookieNewObj).forEach(data => {
387
+ cookieNewObj[data].close == 0
388
+ })
389
+ }
390
+ // 一鍵全關
391
+ cookieSetting.rejectAll = () => {
392
+ $(`input[type="checkbox"][data-name]:not([data-name="required"])`).prop('checked', false).trigger('change')
393
+ cookieNewObj.close = 1
394
+ Object.keys(cookieNewObj).forEach(data => {
395
+ cookieNewObj[data].close == 1
396
+ })
212
397
  }
213
-
214
398
  // 綁定按鈕
215
- $("[data-cookie-all]").on('click', cookieSetting.enableAll)
399
+ $("[data-cookie-all]").on('click', function(){
400
+ cookieSetting.enableAll()
401
+ })
402
+ // 儲存
216
403
  $("[data-cookie-save]").on('click', function(){
217
- cookieSetting.save()
218
- cookieSetting.closeEvent($container)
404
+ cookieAdvanced.cookieSaveFn()
405
+ })
406
+ // 關閉視窗
407
+ $("[data-cookie-close]").on('click', function(){
408
+ cookieSetting.closeEvent($settingBlock)
409
+ })
410
+ $("[data-cookie-reject]").on('click', function() {
411
+ cookieSetting.rejectAll()
412
+ cookieAdvanced.cookieSaveFn()
219
413
  })
220
-
221
- // 頁面載入時先還原一次
222
- $(cookieSetting.restore)
223
414
  }
224
415
 
225
- // step 1 結構
226
- cookieElements.getStep1Element = (opt, index) => {
227
- return `
228
- <div class="item" data-index="${index}">
229
- <div class="left">
230
- <div class="checkbox ${opt.disabled ? " disabled" : ""}">
231
- <div class="input-wrap">
232
- <input type="checkbox" id="s1-check-${index}" ${opt.disabled ? "checked" : ""} data-name="${opt.key}">
233
- <div class="fake-checkbox"><i class="icon-check"></i></div>
234
- </div>
235
- <label for="s1-check-${index}"><span>${opt.title}</span></label>
236
- </div>
237
- <div class="desc">
238
- <div class="text">${opt.text}</div>
239
- ${opt.notice ? `
240
- <div class="notice">
241
- <div class="icon"><i class="icon-notice"></i></div>
242
- <span>${opt.notice}</span>
243
- </div>` : ""}
244
- </div>
245
- </div>
246
- <div class="right">
247
- <div class="switch-box ${opt.disabled ? " disabled" : ""}">
248
- <input type="checkbox" hidden id="s1-switch-${index}" ${opt.disabled ? "checked" : ""} data-name="${opt.key}">
249
- <label class="switch" for="s1-switch-${index}"></label>
250
- </div>
251
- <div class="icon" data-open><i class="icon-arrow"></i></div>
252
- </div>
253
- </div>
254
- `
416
+ // ** --- 工具們 --- **
417
+ // cookie 開啟方法
418
+ cookieSetting.openEvent = (el) => {
419
+ $(el).addClass("active")
420
+ setTimeout(() => {
421
+ $(el).addClass("show")
422
+ scrollLock();
423
+ const scrollers = $(".cookie-settingBlock").find('.scroller')[0]
424
+ OverlayScrollbars(scrollers, {})
425
+ }, 500);
255
426
  }
256
-
257
- // step 2 上面結構
258
- cookieElements.getStep2TopElement = (opt, index) => {
259
- return `
260
- <div class="box">
261
- <div class="left">
262
- <div class="checkbox ${opt.disabled ? " disabled" : ""}">
263
- <div class="input-wrap">
264
- <input type="checkbox" id="s2-check-${index}" ${opt.disabled ? "checked" : ""} data-name="${opt.key}">
265
- <div class="fake-checkbox"><i class="icon-check"></i></div>
266
- </div>
267
- <label for="s2-check-${index}"><span>${opt.title}</span></label>
268
- </div>
269
- </div>
270
- <div class="right">
271
- <div class="switch-box ${opt.disabled ? " disabled" : ""}">
272
- <input type="checkbox" hidden id="s2-switch-${index}" ${opt.disabled ? "checked" : ""} data-name="${opt.key}">
273
- <label class="switch" for="s2-switch-${index}"></label>
274
- </div>
275
- </div>
276
- </div>
277
- <div class="desc">
278
- <div class="text">${opt.text || ""}</div>
279
- ${opt.notice ? `<div class="notice"><div class="icon"><i class="icon-notice"></i></div><span>${opt.notice}</span></div> ` : ""}
280
- </div>
281
- `
427
+ // cookie 關閉方法
428
+ cookieSetting.closeEvent = (el) => {
429
+ $(el).removeClass("show")
430
+ setTimeout(() => {
431
+ $(el).removeClass("active")
432
+ scrollUnlock();
433
+ }, 500);
282
434
  }
435
+ // 判斷該分類底下細項按鈕狀態
436
+ cookieSetting.hasAnyListItemsOpen = (key) => {
437
+ const targetList = cookieNewObj[key]?.list;
438
+ if (!targetList) {
439
+ if (cookieNewObj[key].close == 0) {
440
+ return true;
441
+ } else {
442
+ return false;
443
+ }
444
+ }
445
+ const childKeys = Object.keys(targetList);
446
+ const isOpen = childKeys.some(childKey => {
447
+ return targetList[childKey]?.close === 0;
448
+ });
449
+ return isOpen;
450
+ };
283
451
 
284
- // step 2 底下結構
285
- cookieElements.getStep2BottomElement = (opt) => {
286
- return `
287
- <div class="list collapseItem" data-collapse>
288
- <div class="title collapseTitle" data-collapse-click>
289
- <div class="icon"><i class="icon-arrow"></i></div>
290
- <span>${opt.title || ""}</span>
291
- </div>
292
- ${opt.text ? `<div class="text">${opt.text}</div>` : ""}
293
- <div class="list-detail collapseBox" data-collapse-content>
294
- <div class="innerBox">
295
- <div class="content">
296
- ${
297
- opt.detail?.length > 0 ? cookieElements.generateHtmlString(opt.detail, (i) => {
298
- return `
299
- <div class="li">
300
- <div class="title">${i.title || ""}</div>
301
- ${i.text ? `<div class="text">${i.text}</div>` : ""}
302
- <div class="flex">
303
- <div class="text bold">${i.typeTitle || ""}</div>
304
- <div class="text">${i.typeDesc || ""}</div>
305
- </div>
306
- </div>
307
- `
308
- }) : ""
309
- }
310
- </div>
311
- </div>
312
- </div>
313
- </div>
314
- `
452
+ cookieAdvanced.init = () => {
453
+ cookieAdvanced.newObj();
454
+ cookieAdvanced.dataAppend();
455
+ // cookieAdvanced.initialState();
456
+ cookieAdvanced.clickEvent();
457
+ cookieAdvanced.cookieSaveFn();
458
+ cookieAdvanced.switchState();
315
459
  }
316
460
 
317
461
  methods.toBackend({ getCookieValue: (el) => Cookies.get(el) });
318
462
 
319
- export const init = () => {
463
+ export const init = (isOpenAdvanced = false) => {
320
464
  // cookie 基本版
321
- cookieSetting.basicCookiePolicy()
465
+ cookieBasic.basicCookiePolicy();
466
+ if (!isOpenAdvanced) return
322
467
  // cookie 進階版
323
- cookieSetting.clickEvent();
324
- cookieSetting.cookieSaveFn();
325
- cookieSetting.dataAppend();
326
- }
468
+ cookieAdvanced.init();
469
+ }
@@ -52,7 +52,7 @@ export const inits = () => {
52
52
  new ImageValidate();
53
53
  // uiInit
54
54
  navbar.init();
55
- cookie.init();
55
+ cookie.init(true);
56
56
  // coding...
57
57
  firstEntryHandler.init();
58
58
  }
@@ -1,7 +1,5 @@
1
1
  // plugins
2
- import {
3
- Anchor4,
4
- } from '@xwadex/fesd';
2
+ import { Anchor4 } from '@xwadex/fesd';
5
3
 
6
4
  export const toBackend = (contents) => {
7
5
  if (!contents) return
@@ -16,7 +14,7 @@ export const toBackend = (contents) => {
16
14
  }
17
15
 
18
16
  // collapse
19
- export function collapseEvent(clickTarget, parentBox, anchor = false, single = false) {
17
+ export function collapseEvent(clickTarget, parentBox, contentBox, anchor = false, single = false) {
20
18
  if (!clickTarget || !parentBox) return;
21
19
 
22
20
  const $clickTarget = $(clickTarget);
@@ -25,14 +23,16 @@ export function collapseEvent(clickTarget, parentBox, anchor = false, single = f
25
23
 
26
24
  $clickTarget.on('click', function () {
27
25
  const $parent = $(this).closest(parentBox);
28
- if (!$parent.length) return;
26
+ const $content = $parent.find(contentBox);
27
+
28
+ if (!$parent.length || !$content.length) return;
29
29
  if ($parent.hasClass('open')) return $parent.removeClass('open');
30
30
 
31
31
  $parent.addClass('open');
32
32
  if (single) $parentBoxes.not($parent).removeClass('open');
33
33
  if (anchor) setTimeout(() => Anchor4.run({ target: $parent[0] }), 500);
34
34
  });
35
- }
35
+ };
36
36
 
37
37
  export function noContentCheck() {
38
38
  const params = new URLSearchParams(document.location.search);
@@ -61,7 +61,7 @@ export const pageChange = (type = true) => {
61
61
  // 移除 show class
62
62
  $('[data-step]').removeClass('show');
63
63
  // data-step 監聽 transitionend
64
- $('[data-step]').off('transitionend webkitTransitionEnd oTransitionEnd').on('transitionend webkitTransitionEnd oTransitionEnd', function() {
64
+ $('[data-step]').off('transitionend webkitTransitionEnd oTransitionEnd').on('transitionend webkitTransitionEnd oTransitionEnd', function () {
65
65
  const $this = $(this);
66
66
 
67
67
  if (!$this.hasClass('show')) {
@@ -48,7 +48,7 @@ html(lang="zh-Hant-TW" data-overlayscrollbars-initialize)
48
48
  // cookie
49
49
  block cookie
50
50
  include ./components/_cookiePolicy.pug
51
-
51
+
52
52
  modern-modal(data-modal-id="my-modal" data-modal-animate="clip-right")
53
53
  .close-btn(data-modal-close)
54
- .title Modal4
54
+ .title Modal4
@@ -6,9 +6,9 @@
6
6
  p.title Cookie
7
7
  p.text 關於本網站使用瀏覽器紀錄 Cookie 來提供您最好的使用體驗,我們使用的 Cookie 也包括了第三方 Cookie 。<br>相關資訊請訪問我們的隱私權與 Cookie 政策。如果您選擇繼續瀏覽或關閉這個提示,便表示您已接受我們的網站使用條款。
8
8
  .button-group
9
- .cookie-btn.close Reject 我拒絕
10
- .cookie-btn.agree OK 我接受
11
- .cookie-settingBtn(data-cookie-open)
9
+ .cookie-btn.close(data-cookie-reject) Reject 我拒絕
10
+ .cookie-btn.agree(data-cookie-save) OK 我接受
11
+ .cookie-settingBtn
12
12
  span Cookie 偏好設定
13
13
  .icon
14
14
  i.icon-arrow
@@ -25,7 +25,7 @@
25
25
  // JS append 結構
26
26
  .button-group
27
27
  .cookie-btn.open(data-cookie-all) 啟用所有 Cookie
28
- .cookie-btn.save(data-cookie-save) SAVE 儲存本次變更
28
+ .cookie-btn.save(data-cookie-close) SAVE 儲存本次變更
29
29
  .setting-container(data-step="2")
30
30
  .back(onclick="document.body.fesd.pageChange(false)")
31
31
  .icon
@@ -35,3 +35,6 @@
35
35
  // JS append 結構
36
36
  .content-block
37
37
  // JS append 結構
38
+
39
+ // 後端 cookie 資料放這兒
40
+ input(input type="hidden" id="_wcookie" value='')
@@ -79,7 +79,7 @@ block content
79
79
  h3 Video4 👇
80
80
  .row
81
81
  .grid
82
- .photo-box(video-target video-id="5bMdjkfvONE" video-type="youtube")
82
+ .photo-box(video-target video-id="" video-type="youtube")
83
83
  picture
84
84
  source(srcset="https://cdn.wdd.idv.tw/image/video-cover01.webp" type="image/webp")
85
85
  source(srcset="https://cdn.wdd.idv.tw/image/video-cover01.jpg" type="image/jpeg")