@jx3box/jx3box-ui 2.2.10 → 2.2.12

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": "@jx3box/jx3box-ui",
3
- "version": "2.2.10",
3
+ "version": "2.2.12",
4
4
  "description": "JX3BOX Vue3 UI",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -50,9 +50,10 @@ function later(data) {
50
50
  return $next().post(`/api/next2/userdata/watch-later/item`, data);
51
51
  }
52
52
  // 收藏相关
53
- function setCollect(id, type, post_title) {
53
+ function setCollect(id, type, post_title, author_id) {
54
54
  return $next().post(`/api/article/favorites/add/${id}/${type}`, {
55
55
  post_title: post_title,
56
+ author_id,
56
57
  });
57
58
  }
58
59
  function getCollectList(id, type) {
@@ -33,7 +33,7 @@ import FooterResource from "./footer/resource.vue";
33
33
  import FooterCopyright from "./footer/copyright.vue";
34
34
  import i18nMixin from "../i18n/mixin";
35
35
  export default {
36
- name: "Footer",
36
+ name: "CommonFooter",
37
37
  mixins: [i18nMixin],
38
38
  components: {
39
39
  FooterOfficial,
@@ -53,7 +53,7 @@ body::after {
53
53
  bottom: 0;
54
54
  background: #111827;
55
55
  }
56
- .c-footer--v4{
56
+ .c-footer--v4 {
57
57
  box-sizing: border-box;
58
58
  padding: 3rem 1.5rem;
59
59
  background: #111827;
@@ -113,7 +113,7 @@ body::after {
113
113
  font-size: 1.5rem;
114
114
  line-height: 2rem;
115
115
  font-weight: 700;
116
- letter-spacing: -0.025em;
116
+ letter-spacing: 0;
117
117
  color: #fff;
118
118
  }
119
119
 
@@ -125,8 +125,8 @@ body::after {
125
125
  color: #9ca3af;
126
126
  }
127
127
 
128
- .u-logo{
129
- fill:#fff;
128
+ .u-logo {
129
+ fill: #fff;
130
130
  }
131
131
  }
132
132
  .c-footer--v4__popover {
@@ -466,6 +466,7 @@ export default {
466
466
  avatar: "", //作者头像
467
467
  author_id: "", //作者id
468
468
  },
469
+ author_id: "",
469
470
  subscribeType: "", //订阅类型,用于区分调用百科、文章、作者、帖子等订阅接口,
470
471
  postType: "", //订阅、收藏接口的type,如'face','bps','article'等
471
472
  id: "", //页面数据ID,用于收藏订阅操作
@@ -500,6 +501,15 @@ export default {
500
501
  fixIsActive() {
501
502
  return this.fixList.some((item) => item.url === this.fixPageConfig.url);
502
503
  },
504
+ authorId() {
505
+ return this.drawerConfig.author_id || this.drawerConfig.author?.author_id || "";
506
+ },
507
+ subscribeData() {
508
+ return {
509
+ title: this.drawerConfig.title,
510
+ author_id: this.authorId,
511
+ };
512
+ },
503
513
  },
504
514
  mounted() {
505
515
  this.fixList = JSON.parse(localStorage.getItem(this.fixPageConfig.key)) || [];
@@ -590,7 +600,7 @@ export default {
590
600
  this.collectInfo = {};
591
601
  });
592
602
  } else {
593
- setCollect(conf.id, conf.postType, conf.title).then((res) => {
603
+ setCollect(conf.id, conf.postType, conf.title, this.authorId).then((res) => {
594
604
  this.isCollect = true;
595
605
  this.collectInfo = res.data?.data;
596
606
  });
@@ -629,7 +639,7 @@ export default {
629
639
  this.$emit("subscribe", { isSubscribe: false });
630
640
  });
631
641
  } else {
632
- subscribePost(conf.id, { title: conf.title }).then((res) => {
642
+ subscribePost(conf.id, this.subscribeData).then((res) => {
633
643
  this.isSubscribe = true;
634
644
  this.subscribeInfo = res.data?.data;
635
645
  this.$emit("subscribe", { isSubscribe: true });
@@ -644,7 +654,7 @@ export default {
644
654
  this.$emit("subscribe", { isSubscribe: false });
645
655
  });
646
656
  } else {
647
- subscribeArticle(conf.postType, conf.id, { title: conf.title }).then((res) => {
657
+ subscribeArticle(conf.postType, conf.id, this.subscribeData).then((res) => {
648
658
  this.isSubscribe = true;
649
659
  this.subscribeInfo = res.data?.data;
650
660
  this.$emit("subscribe", { isSubscribe: true });
@@ -659,7 +669,7 @@ export default {
659
669
  this.$emit("subscribe", { isSubscribe: false });
660
670
  });
661
671
  } else {
662
- subscribeWiki(conf.postType, conf.id, { title: conf.title }).then((res) => {
672
+ subscribeWiki(conf.postType, conf.id, this.subscribeData).then((res) => {
663
673
  this.isSubscribe = true;
664
674
  this.subscribeInfo = res.data?.data;
665
675
  this.$emit("subscribe", { isSubscribe: true });
@@ -4,7 +4,7 @@
4
4
  {{ $jx3boxT("jx3boxUi.commonFooter.downloadCenter", "下载中心") }}
5
5
  </h3>
6
6
  <div class="c-footer-resource__grid">
7
- <div v-for="item in downloadLinks" :key="item.name">
7
+ <div v-for="item in downloadLinks" :key="item.key">
8
8
  <el-popover v-if="item.qrcode" trigger="hover" placement="top" popper-class="c-footer--v4__popover">
9
9
  <div class="c-footer-resource__qrcode">
10
10
  <img
@@ -15,58 +15,26 @@
15
15
  <span class="c-footer-resource__qrcode-label">{{ getDownloadLabel(item) }}</span>
16
16
  </div>
17
17
  <template #reference>
18
- <a
19
- class="c-footer-resource__download"
20
- :href="item.href || '#'"
21
- :target="item.href ? '_blank' : null"
22
- :rel="item.href ? 'noopener noreferrer' : null"
23
- @click="handleLinkClick($event, item)"
24
- >
18
+ <button class="c-footer-resource__download" type="button">
25
19
  <span class="c-footer-resource__download-icon">
26
- <img :src="item.icon" :alt="getDownloadName(item)" />
20
+ <img :src="item.icon" alt="" aria-hidden="true" />
27
21
  </span>
28
22
  <span>{{ getDownloadName(item) }}</span>
29
- </a>
23
+ </button>
30
24
  </template>
31
25
  </el-popover>
32
- <el-popover
33
- v-else-if="item.placeholder"
34
- trigger="hover"
35
- placement="top"
36
- :show-after="150"
37
- popper-class="c-footer--v4__popover"
38
- >
39
- <div class="c-footer-resource__placeholder">
40
- {{ item.placeholder }}
41
- </div>
42
- <template #reference>
43
- <a
44
- class="c-footer-resource__download"
45
- :href="item.href || '#'"
46
- :target="item.href ? '_blank' : null"
47
- :rel="item.href ? 'noopener noreferrer' : null"
48
- @click="handleLinkClick($event, item)"
49
- >
50
- <span class="c-footer-resource__download-icon">
51
- <img :src="item.icon" :alt="getDownloadName(item)" />
52
- </span>
53
- <span>{{ getDownloadName(item) }}</span>
54
- </a>
55
- </template>
56
- </el-popover>
57
- <a
26
+ <button
58
27
  v-else
59
28
  class="c-footer-resource__download"
60
- :href="item.href || '#'"
61
- :target="item.href ? '_blank' : null"
62
- :rel="item.href ? 'noopener noreferrer' : null"
63
- @click="handleLinkClick($event, item)"
29
+ :class="{ 'is-active': activePlatform === item.key && downloadDialogVisible }"
30
+ type="button"
31
+ @click="openDownloadDialog(item)"
64
32
  >
65
33
  <span class="c-footer-resource__download-icon">
66
- <img :src="item.icon" :alt="getDownloadName(item)" />
34
+ <img :src="item.icon" alt="" aria-hidden="true" />
67
35
  </span>
68
36
  <span>{{ getDownloadName(item) }}</span>
69
- </a>
37
+ </button>
70
38
  </div>
71
39
  </div>
72
40
 
@@ -98,75 +66,286 @@
98
66
  {{ $jx3boxT("jx3boxUi.commonFooter.add", "添加") }}
99
67
  </a>
100
68
  </div>
69
+
70
+ <el-dialog
71
+ v-model="downloadDialogVisible"
72
+ :title="activeDialogTitle"
73
+ width="960px"
74
+ class="m-footer-app-dialog"
75
+ >
76
+ <div
77
+ v-loading="appConfigLoading"
78
+ class="m-footer-app-download"
79
+ >
80
+ <div class="u-app-header">
81
+ <img class="u-app-icon" :src="appLogo" :alt="appName" @error="handleLogoError" />
82
+ <div class="u-app-meta">
83
+ <div class="u-app-name">{{ appName }}</div>
84
+ <div class="u-app-version">{{ appVersion }}</div>
85
+ <div
86
+ v-if="appDescription"
87
+ class="u-app-desc-box"
88
+ :class="{
89
+ 'is-expanded': appDescExpanded,
90
+ 'is-foldable': hasDescFold(appDescription),
91
+ }"
92
+ @click="toggleAppDesc"
93
+ >
94
+ <el-icon v-if="hasDescFold(appDescription)" class="u-desc-toggle">
95
+ <ArrowUp v-if="appDescExpanded" />
96
+ <ArrowDown v-else />
97
+ </el-icon>
98
+ <div class="u-app-desc">
99
+ {{ getFoldedDesc(appDescription, appDescExpanded) }}
100
+ </div>
101
+ </div>
102
+ </div>
103
+ <div class="u-app-download-action">
104
+ <el-button
105
+ class="u-app-store-button"
106
+ type="primary"
107
+ :disabled="!activeDownloadUrl"
108
+ @click="openActiveDownload"
109
+ >
110
+ <el-icon class="u-button-icon"><Download /></el-icon>
111
+ <span>{{ activeDownloadText }}</span>
112
+ </el-button>
113
+ <div v-if="activePlatform === 'android'" class="u-app-download-tip">
114
+ 仅支持 Android 10 及以上系统
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="u-app-preview">
120
+ <div class="u-app-preview-tabs">
121
+ <button
122
+ v-for="device in previewDevices"
123
+ :key="device.key"
124
+ type="button"
125
+ class="u-preview-tab"
126
+ :class="{ 'is-active': activePreviewDevice === device.key }"
127
+ @click="activePreviewDevice = device.key"
128
+ >
129
+ {{ device.label }}
130
+ </button>
131
+ </div>
132
+
133
+ <div class="u-app-preview-list" :class="`is-${activePreviewDevice}`">
134
+ <div v-for="image in currentPreviewImages" :key="image" class="u-app-preview-card">
135
+ <el-image
136
+ class="u-app-preview-image"
137
+ :src="image"
138
+ :preview-src-list="currentPreviewImages"
139
+ :initial-index="currentPreviewImages.indexOf(image)"
140
+ fit="cover"
141
+ preview-teleported
142
+ />
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </el-dialog>
101
148
  </section>
102
149
  </template>
103
150
 
104
151
  <script>
152
+ import { ArrowDown, ArrowUp, Download } from "@element-plus/icons-vue";
105
153
  import { copyText } from "../../utils";
106
154
  import i18nMixin from "../../i18n/mixin";
155
+ import { getConfig } from "../../service/cms";
156
+
157
+ const APP_LOGO_FALLBACK = "https://cdn.jx3box.com/design/app/logo/jx3box-icon-512.png";
158
+ const APPLE_STORE_LINK = "https://apps.apple.com/cn/app/id6761459300";
159
+ const HARMONY_STORE_LINK = "https://appgallery.huawei.com/app/xxx";
160
+ const APP_CONFIG_KEYS = ["android_apk", "android_versions", "app_logo", "app_version_desc"];
161
+
107
162
  export default {
108
163
  name: "FooterResource",
109
164
  mixins: [i18nMixin],
110
165
  props: [],
111
- components: {},
166
+ components: { ArrowDown, ArrowUp, Download },
112
167
  data: function () {
113
168
  return {
169
+ downloadDialogVisible: false,
170
+ activePlatform: "appStore",
171
+ activePreviewDevice: "phone",
172
+ appDescExpanded: false,
173
+ appConfig: {},
174
+ appConfigLoading: false,
175
+ appConfigLoaded: false,
176
+ previewDevices: [
177
+ { key: "phone", label: "Phone" },
178
+ { key: "pad", label: "Pad" },
179
+ ],
180
+ previewImages: {
181
+ phone: [
182
+ "https://cdn.jx3box.com/design/app/phone/phone1.jpg",
183
+ "https://cdn.jx3box.com/design/app/phone/phone2.jpg",
184
+ "https://cdn.jx3box.com/design/app/phone/phone3.jpg",
185
+ ],
186
+ pad: [
187
+ "https://cdn.jx3box.com/design/app/pad/pad1.jpg",
188
+ "https://cdn.jx3box.com/design/app/pad/pad2.jpg",
189
+ "https://cdn.jx3box.com/design/app/pad/pad3.jpg",
190
+ ],
191
+ },
114
192
  downloadLinks: [
115
193
  {
116
194
  key: "appStore",
117
195
  name: "App Store",
118
- href: "",
119
196
  icon: require("../../assets/img/common/ios.svg"),
120
- // qrcode: require("../../assets/img/common/ios.jpg"),
121
- placeholder: "即将上线",
122
197
  },
123
198
  {
124
199
  key: "android",
125
200
  name: "Android",
126
- href: "",
127
201
  icon: require("../../assets/img/common/android.svg"),
128
- // qrcode: require("../../assets/img/common/android.jpg"),
129
- placeholder: "即将上线",
130
202
  },
131
203
  {
132
204
  key: "harmonyNext",
133
205
  name: "鸿蒙 NEXT",
134
- href: "",
135
206
  icon: require("../../assets/img/common/harmony.svg"),
136
- // qrcode: require("../../assets/img/common/harmony.jpg"),
137
- placeholder: "即将上线",
138
207
  },
139
208
  {
140
209
  key: "miniProgram",
141
210
  name: "小程序",
142
211
  labelKey: "jx3boxHelper",
143
212
  label: "JX3BOX小助手",
144
- href: "",
145
213
  icon: require("../../assets/img/common/miniprogram.svg"),
146
214
  qrcode: require("../../assets/img/common/miniprogram.jpg"),
147
215
  },
148
216
  ],
149
217
  };
150
218
  },
151
- computed: {},
219
+ computed: {
220
+ activePlatformItem() {
221
+ return this.downloadLinks.find((item) => item.key === this.activePlatform) || this.downloadLinks[0];
222
+ },
223
+ activePlatformLabel() {
224
+ return this.getDownloadName(this.activePlatformItem);
225
+ },
226
+ activeDialogTitle() {
227
+ const map = {
228
+ appStore: "App Store",
229
+ android: "Android",
230
+ harmonyNext: "鸿蒙NEXT",
231
+ };
232
+ return map[this.activePlatform] || this.activePlatformLabel;
233
+ },
234
+ appLogo() {
235
+ return this.appConfig.app_logo || APP_LOGO_FALLBACK;
236
+ },
237
+ appName() {
238
+ return "魔盒APP";
239
+ },
240
+ appVersion() {
241
+ return this.appConfig.android_versions || "";
242
+ },
243
+ appDescription() {
244
+ return this.appConfig.app_version_desc || "";
245
+ },
246
+ androidDownloadUrl() {
247
+ return this.appConfig.android_apk || "";
248
+ },
249
+ activeDownloadUrl() {
250
+ if (this.activePlatform === "android") return this.androidDownloadUrl;
251
+ if (this.activePlatform === "harmonyNext") return HARMONY_STORE_LINK;
252
+ return APPLE_STORE_LINK;
253
+ },
254
+ activeDownloadText() {
255
+ const map = {
256
+ appStore: "前往苹果商店",
257
+ android: "下载 Android",
258
+ harmonyNext: "前往鸿蒙商店",
259
+ };
260
+ return map[this.activePlatform] || "立即下载";
261
+ },
262
+ currentPreviewImages() {
263
+ return this.previewImages[this.activePreviewDevice] || [];
264
+ },
265
+ },
152
266
  watch: {},
153
267
  methods: {
154
268
  copyText,
155
269
  getDownloadName(item) {
156
- if (item?.key) {
157
- const k = item.key === "harmonyNext" ? "harmonyNext" : item.key;
158
- const maybe = `jx3boxUi.commonFooter.${k}`;
159
- return this.$jx3boxT(maybe, item.name || k);
160
- }
161
- return item?.name || "";
270
+ if (!item?.key) return item?.name || "";
271
+ return this.$jx3boxT(`jx3boxUi.commonFooter.${item.key}`, item.name || item.key);
162
272
  },
163
273
  getDownloadLabel(item) {
164
- if (item?.labelKey)
274
+ if (item?.labelKey) {
165
275
  return this.$jx3boxT(`jx3boxUi.commonFooter.${item.labelKey}`, item.label || item.labelKey);
276
+ }
166
277
  return item?.label || "";
167
278
  },
168
- handleLinkClick(e, item) {
169
- if (!item?.href) e.preventDefault();
279
+ openDownloadDialog(item) {
280
+ this.activePlatform = item.key;
281
+ this.activePreviewDevice = "phone";
282
+ this.appDescExpanded = false;
283
+ this.downloadDialogVisible = true;
284
+ this.loadAppConfig();
285
+ },
286
+ openActiveDownload() {
287
+ if (!this.activeDownloadUrl) return;
288
+ window.open(this.activeDownloadUrl, "_blank", "noopener,noreferrer");
289
+ },
290
+ loadAppConfig() {
291
+ const storeConfig = this.getStoreAppConfig();
292
+ if (storeConfig) {
293
+ this.appConfig = {
294
+ ...this.appConfig,
295
+ ...storeConfig,
296
+ };
297
+ this.appConfigLoaded = true;
298
+ return Promise.resolve();
299
+ }
300
+ if (this.appConfigLoading || this.appConfigLoaded) return Promise.resolve();
301
+ this.appConfigLoading = true;
302
+ return getConfig({ subtype: "app" })
303
+ .then((items) => {
304
+ this.appConfig = this.getAppConfigFromItems(items);
305
+ this.appConfigLoaded = true;
306
+ })
307
+ .catch(() => {
308
+ this.appConfig = {};
309
+ })
310
+ .finally(() => {
311
+ this.appConfigLoading = false;
312
+ });
313
+ },
314
+ getStoreAppConfig() {
315
+ const config = this.$store?.state?.config || {};
316
+ const appConfig = APP_CONFIG_KEYS.reduce((result, key) => {
317
+ if (config[key]) result[key] = config[key];
318
+ return result;
319
+ }, {});
320
+ return Object.keys(appConfig).length ? appConfig : null;
321
+ },
322
+ getAppConfigFromItems(items) {
323
+ if (!Array.isArray(items)) return {};
324
+ return items.reduce((config, item) => {
325
+ if (!item?.key || !APP_CONFIG_KEYS.includes(item.key)) return config;
326
+ if (item.subtype && item.subtype !== "app") return config;
327
+ config[item.key] = item.val;
328
+ return config;
329
+ }, {});
330
+ },
331
+ getDescLines(description) {
332
+ return String(description || "")
333
+ .replace(/\\r\\n|\\n|\\r/g, "\n")
334
+ .split(/\r?\n/);
335
+ },
336
+ hasDescFold(description) {
337
+ return this.getDescLines(description).length > 1;
338
+ },
339
+ getFoldedDesc(description, expanded = false) {
340
+ const lines = this.getDescLines(description);
341
+ return expanded ? lines.join("\n") : lines[0] || "";
342
+ },
343
+ toggleAppDesc() {
344
+ if (!this.hasDescFold(this.appDescription)) return;
345
+ this.appDescExpanded = !this.appDescExpanded;
346
+ },
347
+ handleLogoError(e) {
348
+ e.target.src = APP_LOGO_FALLBACK;
170
349
  },
171
350
  gotoQQbot() {
172
351
  window.open("/qqbot", "_blank");
@@ -190,7 +369,7 @@ export default {
190
369
  font-size: 14px;
191
370
  font-weight: 600;
192
371
  line-height: 20px;
193
- letter-spacing: 0.025em;
372
+ letter-spacing: 0;
194
373
  }
195
374
 
196
375
  &__grid {
@@ -225,17 +404,10 @@ export default {
225
404
  line-height: 16px;
226
405
  }
227
406
 
228
- &__placeholder {
229
- padding: 12px;
230
- text-align: center;
231
- font-size: 12px;
232
- font-weight: 600;
233
- line-height: 16px;
234
- }
235
-
236
407
  &__download {
237
408
  display: flex;
238
409
  align-items: center;
410
+ width: 100%;
239
411
  height: 48px;
240
412
  padding: 0 16px;
241
413
  border: 1px solid #374151;
@@ -244,12 +416,16 @@ export default {
244
416
  color: #d1d5db;
245
417
  font-size: 12px;
246
418
  line-height: 16px;
419
+ text-align: left;
247
420
  text-decoration: none;
248
- transition: background-color 0.2s ease, border-color 0.2s ease;
421
+ cursor: pointer;
422
+ transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
249
423
 
250
- &:hover {
424
+ &:hover,
425
+ &.is-active {
251
426
  border-color: #3b82f6;
252
427
  background: #374151;
428
+ color: #fff;
253
429
  }
254
430
  }
255
431
 
@@ -260,6 +436,7 @@ export default {
260
436
  width: 16px;
261
437
  height: 16px;
262
438
  margin-right: 10px;
439
+ flex-shrink: 0;
263
440
 
264
441
  img {
265
442
  display: block;
@@ -317,7 +494,7 @@ export default {
317
494
  font-size: 10px;
318
495
  font-weight: 700;
319
496
  line-height: 14px;
320
- letter-spacing: 0.05em;
497
+ letter-spacing: 0;
321
498
  text-transform: uppercase;
322
499
  }
323
500
 
@@ -346,4 +523,271 @@ export default {
346
523
  }
347
524
  }
348
525
  }
526
+
527
+ .m-footer-app-dialog {
528
+ margin-top: 5vh !important;
529
+ width: min(960px, calc(100vw - 32px)) !important;
530
+
531
+ .el-dialog__header {
532
+ margin-right: 0;
533
+ padding: 20px 24px 12px;
534
+ border-bottom: 1px solid #e5eaf4;
535
+ }
536
+
537
+ .el-dialog__title {
538
+ color: #1f2937;
539
+ font-size: 18px;
540
+ font-weight: 700;
541
+ }
542
+
543
+ .el-dialog__body {
544
+ padding: 24px;
545
+ }
546
+
547
+ .m-footer-app-download {
548
+ min-height: 280px;
549
+ }
550
+
551
+ .u-app-header {
552
+ display: flex;
553
+ align-items: flex-start;
554
+ gap: 16px;
555
+ padding: 0 0 24px;
556
+ border-bottom: 1px solid #e5eaf4;
557
+ }
558
+
559
+ .u-app-icon {
560
+ width: 72px;
561
+ height: 72px;
562
+ border: 1px solid #e5eaf4;
563
+ border-radius: 18px;
564
+ object-fit: cover;
565
+ flex-shrink: 0;
566
+ box-shadow: 0 10px 24px rgba(79, 115, 201, 0.14);
567
+ }
568
+
569
+ .u-app-meta {
570
+ flex: 1;
571
+ min-width: 0;
572
+ }
573
+
574
+ .u-app-name {
575
+ color: #111827;
576
+ font-size: 22px;
577
+ font-weight: 700;
578
+ line-height: 1.3;
579
+ word-break: break-word;
580
+ }
581
+
582
+ .u-app-version {
583
+ margin-top: 6px;
584
+ color: #2d63ef;
585
+ font-size: 14px;
586
+ font-weight: 600;
587
+ line-height: 1.5;
588
+ }
589
+
590
+ .u-app-desc-box {
591
+ display: flex;
592
+ align-items: flex-start;
593
+ gap: 6px;
594
+ margin-top: 10px;
595
+ color: #4b5563;
596
+
597
+ &.is-foldable {
598
+ cursor: pointer;
599
+ }
600
+
601
+ &.is-expanded {
602
+ .u-app-desc {
603
+ display: block;
604
+ white-space: pre-wrap;
605
+ }
606
+ }
607
+ }
608
+
609
+ .u-desc-toggle {
610
+ margin-top: 6px;
611
+ color: #8a94a6;
612
+ flex-shrink: 0;
613
+ }
614
+
615
+ .u-app-desc {
616
+ display: -webkit-box;
617
+ overflow: hidden;
618
+ color: #4b5563;
619
+ font-size: 14px;
620
+ line-height: 1.8;
621
+ white-space: pre-wrap;
622
+ word-break: break-word;
623
+ -webkit-box-orient: vertical;
624
+ -webkit-line-clamp: 1;
625
+ }
626
+
627
+ .u-app-download-action {
628
+ margin-left: auto;
629
+ flex-shrink: 0;
630
+ display: flex;
631
+ flex-direction: column;
632
+ align-items: flex-end;
633
+ gap: 8px;
634
+ }
635
+
636
+ .u-app-store-button {
637
+ flex-shrink: 0;
638
+
639
+ .u-button-icon {
640
+ margin-right: 6px;
641
+ }
642
+ }
643
+
644
+ .u-app-download-tip {
645
+ color: #8a94a6;
646
+ font-size: 12px;
647
+ line-height: 18px;
648
+ white-space: nowrap;
649
+ }
650
+
651
+ .u-app-preview {
652
+ padding-top: 24px;
653
+ }
654
+
655
+ .u-app-preview-tabs {
656
+ display: flex;
657
+ justify-content: center;
658
+ gap: 12px;
659
+ margin-bottom: 20px;
660
+ }
661
+
662
+ .u-preview-tab {
663
+ min-width: 108px;
664
+ padding: 10px 18px;
665
+ border: 1px solid #d7e1f5;
666
+ border-radius: 999px;
667
+ background: #f7f9fe;
668
+ color: #5c6b84;
669
+ font-size: 14px;
670
+ font-weight: 600;
671
+ cursor: pointer;
672
+ transition: all 0.2s ease;
673
+
674
+ &:hover {
675
+ border-color: #9ab6ff;
676
+ color: #2d63ef;
677
+ }
678
+
679
+ &.is-active {
680
+ border-color: #4f73c9;
681
+ background: linear-gradient(135deg, #eff4ff 0%, #dfe9ff 100%);
682
+ color: #2349a6;
683
+ box-shadow: 0 10px 20px rgba(79, 115, 201, 0.16);
684
+ }
685
+ }
686
+
687
+ .u-app-preview-list {
688
+ display: grid;
689
+ grid-template-columns: repeat(3, minmax(0, 1fr));
690
+ gap: 16px;
691
+
692
+ &.is-phone {
693
+ .u-app-preview-card {
694
+ aspect-ratio: 9 / 16;
695
+ }
696
+ }
697
+
698
+ &.is-pad {
699
+ .u-app-preview-card {
700
+ aspect-ratio: 3 / 4;
701
+ }
702
+ }
703
+ }
704
+
705
+ .u-app-preview-card {
706
+ padding: 10px;
707
+ border: 1px solid #e0e8f7;
708
+ border-radius: 18px;
709
+ background: linear-gradient(180deg, #f9fbff 0%, #eef3ff 100%);
710
+ box-shadow: 0 10px 24px rgba(79, 115, 201, 0.08);
711
+ }
712
+
713
+ .u-app-preview-image {
714
+ display: block;
715
+ width: 100%;
716
+ height: 100%;
717
+ overflow: hidden;
718
+ border-radius: 12px;
719
+ background: #fff;
720
+ cursor: pointer;
721
+
722
+ .el-image__inner {
723
+ width: 100%;
724
+ height: 100%;
725
+ }
726
+ }
727
+ }
728
+
729
+ @media screen and (max-width: @phone) {
730
+ .m-footer-app-dialog {
731
+ width: calc(100vw - 24px) !important;
732
+
733
+ .el-dialog__header {
734
+ padding: 18px 16px 12px;
735
+ }
736
+
737
+ .el-dialog__body {
738
+ padding: 16px;
739
+ }
740
+
741
+ .u-app-header {
742
+ display: grid;
743
+ grid-template-columns: 64px minmax(0, 1fr);
744
+ gap: 14px;
745
+ padding-bottom: 18px;
746
+ }
747
+
748
+ .u-app-icon {
749
+ width: 64px;
750
+ height: 64px;
751
+ border-radius: 16px;
752
+ }
753
+
754
+ .u-app-name {
755
+ font-size: 20px;
756
+ }
757
+
758
+ .u-app-download-action {
759
+ grid-column: 1 / -1;
760
+ width: 100%;
761
+ margin-left: 0;
762
+ align-items: stretch;
763
+ }
764
+
765
+ .u-app-store-button {
766
+ width: 100%;
767
+ }
768
+
769
+ .u-app-download-tip {
770
+ text-align: center;
771
+ }
772
+
773
+ .u-app-preview {
774
+ padding-top: 18px;
775
+ }
776
+
777
+ .u-app-preview-tabs {
778
+ gap: 10px;
779
+ margin-bottom: 16px;
780
+ }
781
+
782
+ .u-preview-tab {
783
+ min-width: 0;
784
+ flex: 1;
785
+ padding: 10px 12px;
786
+ }
787
+
788
+ .u-app-preview-list {
789
+ grid-template-columns: 1fr;
790
+ }
791
+ }
792
+ }
349
793
  </style>
@@ -16,7 +16,7 @@
16
16
  :client="client"
17
17
  />
18
18
  <Like :postId="postId" :postType="postType"></Like>
19
- <fav :postId="postId" :postType="postType" :postTitle="postTitle" :author_id="userId"></fav>
19
+ <fav :postId="postId" :postType="postType" :postTitle="postTitle" :author_id="postAuthorId"></fav>
20
20
  <boxcoin-user
21
21
  :postId="postId"
22
22
  :postType="postType"
@@ -96,6 +96,10 @@ export default {
96
96
  type: [Number, String],
97
97
  default: 0,
98
98
  },
99
+ authorId: {
100
+ type: [Number, String],
101
+ default: 0,
102
+ },
99
103
  authors: {
100
104
  type: Array,
101
105
  default: () => [],
@@ -150,6 +154,9 @@ export default {
150
154
  post_keys: function () {
151
155
  return [this.postId, this.postType];
152
156
  },
157
+ postAuthorId: function () {
158
+ return this.authorId || this.userId;
159
+ },
153
160
  },
154
161
  watch: {
155
162
  post_keys: {
@@ -30,6 +30,7 @@ export const Default = {
30
30
  avatar: 'https://cdn.jx3box.com/upload/avatar/2022/3/2/8_9860765.png',
31
31
  author_id: 8,
32
32
  },
33
+ author_id: 8,
33
34
  subscribeType: '',
34
35
  postType: '',
35
36
  id: '',
@@ -13,6 +13,7 @@ const meta = {
13
13
  postId: { control: 'number' },
14
14
  postType: { control: 'text' },
15
15
  postTitle: { control: 'text' },
16
+ authorId: { control: 'number' },
16
17
  showComment: { control: 'boolean' },
17
18
  },
18
19
  };
@@ -24,6 +25,7 @@ export const Default = {
24
25
  postId: 80449,
25
26
  postType: 'bbs',
26
27
  postTitle: '轻剑驭风版本攻略示例',
28
+ authorId: 8,
27
29
  showComment: true,
28
30
  },
29
31
  render: (args) => ({
@@ -58,6 +60,7 @@ export const Default = {
58
60
  { name: 'postId', type: 'String | Number', default: '""', description: '当前文章 ID,用于收藏查询。' },
59
61
  { name: 'postType', type: 'String', default: '""', description: '文章类型,例如 bbs / pvx。' },
60
62
  { name: 'postTitle', type: 'String', default: '""', description: '文章标题,用于收藏请求。' },
63
+ { name: 'authorId', type: 'String | Number', default: '""', description: '收藏请求携带的作者 UID。' },
61
64
  { name: 'showComment', type: 'Boolean | Number', default: 'false', description: '是否展示评论入口按钮。' },
62
65
  ],
63
66
  };
@@ -22,6 +22,7 @@ export const Default = {
22
22
  { name: 'postType', type: 'String', default: '""', required: true, description: '文章类型。' },
23
23
  { name: 'postTitle', type: 'String', default: '""', description: '文章标题。' },
24
24
  { name: 'userId', type: 'Number | String', default: '0', description: '作者 UID。' },
25
+ { name: 'authorId', type: 'Number | String', default: '0', description: '收藏请求携带的作者 UID,未传时回退到 userId。' },
25
26
  { name: 'adminBoxcoinEnable / userBoxcoinEnable', type: 'Boolean', default: 'false', description: '控制打赏区显示。' },
26
27
  { name: 'presetConfig', type: 'Object', default: '{}', description: 'Storybook 下可直接注入打赏配置,避免空态。' },
27
28
  ],
@@ -30,7 +31,7 @@ export const Default = {
30
31
  template: `
31
32
  <div style="display:grid;gap:24px;">
32
33
  <section style="border-radius:20px;background:rgba(255,255,255,0.92);padding:24px;box-shadow:0 24px 60px rgba(15,23,42,0.08);">
33
- <SimpleThx post-type="bbs" post-title="轻剑驭风版本攻略示例" :post-id="80449" :user-id="8" :admin-boxcoin-enable="true" :user-boxcoin-enable="true" :allow-gift="true" :preset-config="presetConfig" />
34
+ <SimpleThx post-type="bbs" post-title="轻剑驭风版本攻略示例" :post-id="80449" :user-id="8" :author-id="8" :admin-boxcoin-enable="true" :user-boxcoin-enable="true" :allow-gift="true" :preset-config="presetConfig" />
34
35
  </section>
35
36
  <StoryPropsTable title="SimpleThx" description="简化互动条组件。" :items="propsInfo" />
36
37
  </div>