@jx3box/jx3box-ui 2.0.3 → 2.0.5

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.
@@ -0,0 +1,208 @@
1
+ const path = require('path');
2
+
3
+ const lessResourcePatterns = [
4
+ path.resolve(__dirname, '../node_modules/@jx3box/jx3box-common/css/var.less'),
5
+ path.resolve(__dirname, '../node_modules/@jx3box/jx3box-common/css/mixin.less'),
6
+ path.resolve(__dirname, '../assets/css/var.less'),
7
+ path.resolve(__dirname, '../assets/css/mixin.less'),
8
+ path.resolve(__dirname, '../node_modules/csslab/base.less'),
9
+ ];
10
+
11
+ function walkRules(rules, visitor) {
12
+ if (!Array.isArray(rules)) return;
13
+ rules.forEach((rule) => {
14
+ visitor(rule);
15
+ if (Array.isArray(rule.oneOf)) walkRules(rule.oneOf, visitor);
16
+ if (Array.isArray(rule.rules)) walkRules(rule.rules, visitor);
17
+ });
18
+ }
19
+
20
+ function ensureVueSvgInlineLoader(config) {
21
+ walkRules(config.module?.rules, (rule) => {
22
+ if (!Array.isArray(rule.use)) return;
23
+
24
+ const hasVueLoader = rule.use.some((use) => {
25
+ const loader = typeof use === 'string' ? use : use?.loader;
26
+ return loader && loader.includes('vue-loader');
27
+ });
28
+
29
+ if (!hasVueLoader) return;
30
+
31
+ const hasSvgInlineLoader = rule.use.some((use) => {
32
+ const loader = typeof use === 'string' ? use : use?.loader;
33
+ return loader && loader.includes('vue-svg-inline-loader');
34
+ });
35
+
36
+ if (!hasSvgInlineLoader) {
37
+ rule.use.push({
38
+ loader: require.resolve('vue-svg-inline-loader'),
39
+ });
40
+ }
41
+ });
42
+ }
43
+
44
+ function ensureLessGlobalResources(config) {
45
+ walkRules(config.module?.rules, (rule) => {
46
+ if (!Array.isArray(rule.use)) return;
47
+
48
+ for (let i = 0; i < rule.use.length; i += 1) {
49
+ const currentUse = rule.use[i];
50
+ const loader = typeof currentUse === 'string' ? currentUse : currentUse?.loader;
51
+
52
+ if (!loader || !loader.includes('less-loader')) continue;
53
+
54
+ const hasStyleResource = rule.use.some((use) => {
55
+ const useLoader = typeof use === 'string' ? use : use?.loader;
56
+ return useLoader && useLoader.includes('style-resources-loader');
57
+ });
58
+
59
+ if (!hasStyleResource) {
60
+ rule.use.splice(i + 1, 0, {
61
+ loader: require.resolve('style-resources-loader'),
62
+ options: {
63
+ patterns: lessResourcePatterns,
64
+ },
65
+ });
66
+ }
67
+
68
+ break;
69
+ }
70
+ });
71
+ }
72
+
73
+ function removeVueDocgenLoader(config) {
74
+ walkRules(config.module?.rules, (rule) => {
75
+ const directLoader = typeof rule.loader === 'string' ? rule.loader : '';
76
+ if (directLoader.includes('vue-docgen-loader')) {
77
+ rule.__remove = true;
78
+ return;
79
+ }
80
+
81
+ if (!Array.isArray(rule.use)) return;
82
+ rule.use = rule.use.filter((use) => {
83
+ const loader = typeof use === 'string' ? use : use?.loader;
84
+ return !(loader && loader.includes('vue-docgen-loader'));
85
+ });
86
+ });
87
+
88
+ if (Array.isArray(config.module?.rules)) {
89
+ config.module.rules = config.module.rules.filter((rule) => !rule.__remove);
90
+ }
91
+ }
92
+
93
+ function ensureStyleRules(config) {
94
+ const rules = config.module?.rules || [];
95
+ const hasLessRule = rules.some((rule) => String(rule.test).includes('less'));
96
+ const hasScssRule = rules.some((rule) => String(rule.test).includes('scss'));
97
+ const hasCssRule = rules.some((rule) => String(rule.test).includes('\\.css$'));
98
+
99
+ if (hasCssRule) {
100
+ walkRules(rules, (rule) => {
101
+ if (!String(rule.test).includes('\\.css$') || !Array.isArray(rule.use)) return;
102
+ const hasPostcss = rule.use.some((use) => {
103
+ const loader = typeof use === 'string' ? use : use?.loader;
104
+ return loader && loader.includes('postcss-loader');
105
+ });
106
+ if (!hasPostcss) {
107
+ rule.use.push({
108
+ loader: require.resolve('postcss-loader'),
109
+ options: {
110
+ postcssOptions: {
111
+ config: path.resolve(__dirname, '../postcss.config.js'),
112
+ },
113
+ },
114
+ });
115
+ }
116
+ });
117
+ }
118
+
119
+ if (!hasLessRule) {
120
+ rules.push({
121
+ test: /\.less$/i,
122
+ use: [
123
+ require.resolve('style-loader'),
124
+ {
125
+ loader: require.resolve('css-loader'),
126
+ options: { importLoaders: 1 },
127
+ },
128
+ {
129
+ loader: require.resolve('postcss-loader'),
130
+ options: {
131
+ postcssOptions: {
132
+ config: path.resolve(__dirname, '../postcss.config.js'),
133
+ },
134
+ },
135
+ },
136
+ {
137
+ loader: require.resolve('less-loader'),
138
+ options: {
139
+ lessOptions: {
140
+ javascriptEnabled: true,
141
+ },
142
+ },
143
+ },
144
+ {
145
+ loader: require.resolve('style-resources-loader'),
146
+ options: {
147
+ patterns: lessResourcePatterns,
148
+ },
149
+ },
150
+ ],
151
+ });
152
+ }
153
+
154
+ if (!hasScssRule) {
155
+ rules.push({
156
+ test: /\.s[ac]ss$/i,
157
+ use: [
158
+ require.resolve('style-loader'),
159
+ require.resolve('css-loader'),
160
+ {
161
+ loader: require.resolve('postcss-loader'),
162
+ options: {
163
+ postcssOptions: {
164
+ config: path.resolve(__dirname, '../postcss.config.js'),
165
+ },
166
+ },
167
+ },
168
+ require.resolve('sass-loader'),
169
+ ],
170
+ });
171
+ }
172
+
173
+ config.module.rules = rules;
174
+ }
175
+
176
+ /** @type { import('@storybook/vue3-webpack5').StorybookConfig } */
177
+ module.exports = {
178
+ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
179
+ addons: [
180
+ {
181
+ name: '@storybook/addon-essentials',
182
+ options: {
183
+ docs: false,
184
+ },
185
+ },
186
+ '@storybook/addon-interactions',
187
+ ],
188
+ framework: {
189
+ name: '@storybook/vue3-webpack5',
190
+ options: {
191
+ docgen: false,
192
+ },
193
+ },
194
+ webpackFinal: async (config) => {
195
+ config.resolve = config.resolve || {};
196
+ config.resolve.alias = {
197
+ ...(config.resolve.alias || {}),
198
+ '@': path.resolve(__dirname, '../src'),
199
+ };
200
+
201
+ removeVueDocgenLoader(config);
202
+ ensureStyleRules(config);
203
+ ensureVueSvgInlineLoader(config);
204
+ ensureLessGlobalResources(config);
205
+
206
+ return config;
207
+ },
208
+ };
@@ -0,0 +1,84 @@
1
+ import { setup } from '@storybook/vue3';
2
+ import ElementPlus from 'element-plus';
3
+ import zhCn from 'element-plus/es/locale/lang/zh-cn';
4
+ import * as ElementPlusIconsVue from '@element-plus/icons-vue';
5
+ import axios from 'axios';
6
+
7
+ import { Jx3boxUiI18n } from '../i18n';
8
+ import navData from '../assets/data/nav.json';
9
+ import boxData from '../assets/data/box.json';
10
+
11
+ import '@jx3box/jx3box-common/css/normalize.css';
12
+ import '@jx3box/jx3box-common/css/font.css';
13
+ import 'element-plus/dist/index.css';
14
+ import '@jx3box/jx3box-common/css/tailwind.css';
15
+
16
+ let axiosPatched = false;
17
+
18
+ function patchAxiosForStorybook() {
19
+ if (axiosPatched) return;
20
+
21
+ const originalGet = axios.get.bind(axios);
22
+ axios.get = (url, ...args) => {
23
+ if (typeof url === 'string' && url.includes('/config/global.json')) {
24
+ return Promise.resolve({
25
+ data: {
26
+ token_version: 'storybook',
27
+ },
28
+ });
29
+ }
30
+ return originalGet(url, ...args);
31
+ };
32
+
33
+ axiosPatched = true;
34
+ }
35
+
36
+ function bootstrapRuntimeData() {
37
+ try {
38
+ sessionStorage.setItem('nav', JSON.stringify(navData));
39
+ sessionStorage.setItem('box', JSON.stringify(boxData));
40
+ } catch (e) {
41
+ // ignore session storage failures in restricted contexts
42
+ }
43
+
44
+ try {
45
+ localStorage.removeItem('token');
46
+ localStorage.removeItem('uid');
47
+ localStorage.removeItem('group');
48
+ localStorage.removeItem('created_at');
49
+ localStorage.removeItem('name');
50
+ localStorage.removeItem('status');
51
+ localStorage.removeItem('avatar');
52
+ } catch (e) {
53
+ // ignore local storage failures in restricted contexts
54
+ }
55
+ }
56
+
57
+ setup((app) => {
58
+ app.use(Jx3boxUiI18n, { locale: 'zh-CN' });
59
+ app.use(ElementPlus, { locale: zhCn });
60
+
61
+ for (const [name, component] of Object.entries(ElementPlusIconsVue)) {
62
+ app.component(name, component);
63
+ }
64
+ });
65
+
66
+ patchAxiosForStorybook();
67
+
68
+ export const decorators = [
69
+ (story) => {
70
+ bootstrapRuntimeData();
71
+ return story();
72
+ },
73
+ ];
74
+
75
+ export const parameters = {
76
+ actions: { argTypesRegex: '^on[A-Z].*' },
77
+ controls: {
78
+ matchers: {
79
+ color: /(background|color)$/i,
80
+ date: /Date$/i,
81
+ },
82
+ },
83
+ layout: 'fullscreen',
84
+ };
package/README.md CHANGED
@@ -5,10 +5,49 @@
5
5
 
6
6
  ## 模块
7
7
 
8
+ ### 全局
9
+ - CommonHeader 公共头
10
+ - Breadcrumb 面包屑
11
+ - Main 默认内容框架(非必须)
12
+ - LeftSidebar 左侧边栏(非必须)
13
+ - LeftSideToggle 左侧边栏触发器(非必须,可独立使用)
14
+ - RightSidebar 右侧边栏(非必须)
15
+ - CommonFooter 公共底
16
+ - SuspendCommon 移动侧浮窗底(此组件需要手动引入)
17
+
18
+ ### 内容单页 single
19
+
20
+ 主要是用于详情页的展示内容
21
+
22
+ - CmsSingle 单页框架
23
+ - Author 侧边栏作者信息整合
24
+ - PostDirectory 目录
25
+ - PostHeader 创作信息
26
+ - Creators 联合创作
27
+ - Collection 小册
28
+ - Article 文章内容
29
+ - Thx 交互组件
30
+ - SimpleThx 交互组件简化版(仅PVP栏目使用,需要手动引入)
31
+ - Comment 评论
32
+
33
+ ### 筛选模块 list
34
+
35
+ 主要用于各栏目的列表筛选
36
+
37
+ - clientBy 客户端筛选
38
+ - markBy 标记筛选
39
+ - menuBy 通用筛选
40
+ - orderBy 排序筛选
41
+ - tagBy 标签筛选
42
+ - topicBy 主题筛选
43
+ - versionBy 版本筛选
44
+ - zlpBy 资料片筛选
45
+
8
46
  ### 个人模块 author
9
47
 
10
- 一般用于文章详情的左侧边栏
48
+ 一般用于文章详情的左侧边栏,个人主页等
11
49
 
50
+ - Avatar 头像
12
51
  - AuthorFans 粉丝榜
13
52
  - AuthorFollow 关注用户(区分rss,此处用户更新不会收到消息)
14
53
  - AuthorGift 打赏用户
@@ -19,34 +58,6 @@
19
58
  - AuthorPosts 用户作品
20
59
  - AuthorRss 订阅用户
21
60
  - AuthorTeams 所在团队
22
- - Avatar 头像
23
61
  - UserPop 搜索用户弹窗
24
- -
25
-
26
- ### 管理员模块 bread
27
62
 
28
- 一般用于文章详情的右上角管理按钮
29
63
 
30
- - AdminDrop 管理模块触发器
31
- - Admin 文章管理模块(管理员)
32
- - AdminDirectMessage 管理员私信
33
- - Crumb 栏目动态
34
- - DesignTask 设计任务
35
-
36
-
37
- ### 评论模块 comment
38
-
39
- 文章评论模块,用途包括反馈中心,文章详情评论
40
-
41
- ### 筛选模块 filters
42
-
43
- 主要用于各栏目的列表筛选
44
-
45
- - clientBy 客户端筛选
46
- - markBy 标记筛选
47
- - menuBy 通用筛选
48
- - orderBy 排序筛选
49
- - tagBy
50
- - topicBy 主题筛选
51
- - versionBy 版本筛选
52
- - zlpBy 资料片筛选
@@ -55,6 +55,7 @@ export default {
55
55
  panel: {
56
56
  manageCenter: "Admin",
57
57
  },
58
+ all: "All",
58
59
  },
59
60
  footer: {
60
61
  slogan1: "All-in-one tools & resources for JX3.",
@@ -55,6 +55,7 @@ export default {
55
55
  panel: {
56
56
  manageCenter: "Quản trị",
57
57
  },
58
+ all: "Tất cả",
58
59
  },
59
60
  footer: {
60
61
  slogan1: "Tổng hợp công cụ & tài nguyên JX3.",
@@ -55,6 +55,7 @@ export default {
55
55
  panel: {
56
56
  manageCenter: "管理中心",
57
57
  },
58
+ all: "全部",
58
59
  },
59
60
  footer: {
60
61
  slogan1: "一站式剑三工具与资源聚合站。",
@@ -55,6 +55,7 @@ export default {
55
55
  panel: {
56
56
  manageCenter: "管理中心",
57
57
  },
58
+ all: "全部",
58
59
  },
59
60
  footer: {
60
61
  slogan1: "一站式劍三工具與資源聚合站。",
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-ui",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "JX3BOX Vue3 UI",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "dev": "vue-cli-service serve --mode development",
8
8
  "serve": "vue-cli-service serve --mode production",
9
9
  "debug": "vue-cli-service serve --mode debug",
10
+ "storybook": "storybook dev -p 6006",
11
+ "build-storybook": "storybook build",
10
12
  "build": "npm run build:prod",
11
13
  "build:dev": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vue-cli-service build --mode development",
12
14
  "build:prod": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vue-cli-service build --mode production",
@@ -31,7 +33,7 @@
31
33
  },
32
34
  "dependencies": {
33
35
  "@element-plus/icons-vue": "^2.3.2",
34
- "@jx3box/jx3box-common": "^9.1.0",
36
+ "@jx3box/jx3box-common": "^9.1.1",
35
37
  "@jx3box/jx3box-data": "^3.9.2",
36
38
  "@jx3box/jx3box-emotion": "^1.3.0",
37
39
  "@jx3box/jx3box-macro": "^1.0.3",
@@ -60,6 +62,10 @@
60
62
  "devDependencies": {
61
63
  "@babel/core": "^7.12.16",
62
64
  "@babel/eslint-parser": "^7.28.6",
65
+ "@storybook/addon-essentials": "^8.6.18",
66
+ "@storybook/addon-interactions": "^8.6.18",
67
+ "@storybook/test": "^8.6.18",
68
+ "@storybook/vue3-webpack5": "^8.6.18",
63
69
  "@tailwindcss/postcss": "^4.2.1",
64
70
  "@vue/cli-plugin-babel": "~5.0.0",
65
71
  "@vue/cli-plugin-eslint": "~5.0.0",
@@ -77,6 +83,7 @@
77
83
  "prettier": "2.7.1",
78
84
  "sass": "^1.97.3",
79
85
  "sass-loader": "^16.0.7",
86
+ "storybook": "^8.6.18",
80
87
  "style-resources-loader": "^1.5.0",
81
88
  "tailwindcss": "^4.2.1",
82
89
  "vue-svg-inline-loader": "^2.1.3"
package/src/App.vue CHANGED
@@ -78,7 +78,7 @@
78
78
  </template>
79
79
 
80
80
  <script>
81
- import singlebox from "./single/cms-single.vue";
81
+ import singlebox from "./single/CmsSingle.vue";
82
82
  import UploadAlum from "./editor/UploadAlum.vue";
83
83
  import Author from "./single/Author.vue";
84
84
  import SimpleThxVue from "./single/SimpleThx.vue";
@@ -3,10 +3,10 @@
3
3
  <div class="mx-auto w-full" style="max-width: 92rem">
4
4
  <div class="grid grid-cols-1 gap-10 pb-10 lg:grid-cols-12 lg:gap-8">
5
5
  <section class="lg:col-span-3">
6
- <div class="flex items-center space-x-3">
6
+ <a class="flex items-center space-x-3 cursor-pointer" href="/">
7
7
  <img class="u-logo h-9 w-9" svg-inline src="../assets/img/common/logo.svg" alt="JX3BOX" />
8
8
  <span class="text-2xl font-bold tracking-tight text-white">JX3BOX</span>
9
- </div>
9
+ </a>
10
10
  <p class="mt-5 text-sm leading-7 text-gray-400" style="max-width: 20rem">
11
11
  {{ $jx3boxT("jx3boxUi.footer.slogan1", "一站式剑三工具与资源聚合站。") }}<br />
12
12
  {{ $jx3boxT("jx3boxUi.footer.slogan2", "江湖路远,幸甚有你。") }}
@@ -213,27 +213,27 @@ export default {
213
213
  default: "",
214
214
  },
215
215
  hasReply: {
216
- type: Boolean,
216
+ type: [Boolean, Number],
217
217
  default: false,
218
218
  },
219
219
  canDelete: {
220
- type: Boolean,
220
+ type: [Boolean, Number],
221
221
  default: false,
222
222
  },
223
223
  canSetTop: {
224
- type: Boolean,
224
+ type: [Boolean, Number],
225
225
  default: false,
226
226
  },
227
227
  canCancelTop: {
228
- type: Boolean,
228
+ type: [Boolean, Number],
229
229
  default: false,
230
230
  },
231
231
  canHide: {
232
- type: Boolean,
232
+ type: [Boolean, Number],
233
233
  default: false,
234
234
  },
235
235
  isLike: {
236
- type: Boolean,
236
+ type: [Boolean, Number],
237
237
  default: false,
238
238
  },
239
239
  likes: {
@@ -241,11 +241,11 @@ export default {
241
241
  default: 0,
242
242
  },
243
243
  canSetStar: {
244
- type: Boolean,
244
+ type: [Boolean, Number],
245
245
  default: false,
246
246
  },
247
247
  canCancelStar: {
248
- type: Boolean,
248
+ type: [Boolean, Number],
249
249
  default: false,
250
250
  },
251
251
  attachments: {
@@ -257,11 +257,11 @@ export default {
257
257
  default: 0,
258
258
  },
259
259
  canAddWhite: {
260
- type: Boolean,
260
+ type: [Boolean, Number],
261
261
  default: false,
262
262
  },
263
263
  canRemoveWhite: {
264
- type: Boolean,
264
+ type: [Boolean, Number],
265
265
  default: false,
266
266
  },
267
267
  },
@@ -17,9 +17,35 @@
17
17
  <template #reference>
18
18
  <a
19
19
  class="flex h-12 items-center rounded-xl border border-gray-700 bg-gray-800 px-4 text-xs text-gray-300 transition hover:border-blue-500 hover:bg-gray-700"
20
- :href="item.href || 'javascript:;'"
20
+ :href="item.href || '#'"
21
21
  :target="item.href ? '_blank' : null"
22
22
  :rel="item.href ? 'noopener noreferrer' : null"
23
+ @click="handleLinkClick($event, item)"
24
+ >
25
+ <span class="mr-2.5 flex h-4 w-4 items-center justify-center">
26
+ <img class="h-4 w-4" :src="item.icon" :alt="getDownloadName(item)" />
27
+ </span>
28
+ <span>{{ getDownloadName(item) }}</span>
29
+ </a>
30
+ </template>
31
+ </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="p-3 text-center text-xs font-semibold">
40
+ {{ item.placeholder }}
41
+ </div>
42
+ <template #reference>
43
+ <a
44
+ class="flex h-12 items-center rounded-xl border border-gray-700 bg-gray-800 px-4 text-xs text-gray-300 transition hover:border-blue-500 hover:bg-gray-700"
45
+ :href="item.href || '#'"
46
+ :target="item.href ? '_blank' : null"
47
+ :rel="item.href ? 'noopener noreferrer' : null"
48
+ @click="handleLinkClick($event, item)"
23
49
  >
24
50
  <span class="mr-2.5 flex h-4 w-4 items-center justify-center">
25
51
  <img class="h-4 w-4" :src="item.icon" :alt="getDownloadName(item)" />
@@ -31,9 +57,10 @@
31
57
  <a
32
58
  v-else
33
59
  class="flex h-12 items-center rounded-xl border border-gray-700 bg-gray-800 px-4 text-xs text-gray-300 transition hover:border-blue-500 hover:bg-gray-700"
34
- :href="item.href || 'javascript:;'"
60
+ :href="item.href || '#'"
35
61
  :target="item.href ? '_blank' : null"
36
62
  :rel="item.href ? 'noopener noreferrer' : null"
63
+ @click="handleLinkClick($event, item)"
37
64
  >
38
65
  <span class="mr-2.5 flex h-4 w-4 items-center justify-center">
39
66
  <img class="h-4 w-4" :src="item.icon" :alt="getDownloadName(item)" />
@@ -62,7 +89,7 @@
62
89
  :alt="$jx3boxT('jx3boxUi.footer.qqBot', 'QQ机器人')"
63
90
  />
64
91
  </div>
65
- <div>
92
+ <div @click="copyText('3889010020')" class="cursor-pointer">
66
93
  <p class="font-bold uppercase tracking-wider text-gray-500" style="font-size: 10px">
67
94
  {{ $jx3boxT("jx3boxUi.footer.qqBotService", "QQ 机器人服务") }}
68
95
  </p>
@@ -82,6 +109,7 @@
82
109
  </template>
83
110
 
84
111
  <script>
112
+ import { copyText } from "../../utils";
85
113
  import i18nMixin from "../../i18n/mixin";
86
114
  export default {
87
115
  name: "FooterResource",
@@ -97,6 +125,7 @@ export default {
97
125
  href: "",
98
126
  icon: require("../../assets/img/common/ios.svg"),
99
127
  // qrcode: require("../../assets/img/common/ios.jpg"),
128
+ placeholder: "即将上线",
100
129
  },
101
130
  {
102
131
  key: "android",
@@ -104,6 +133,7 @@ export default {
104
133
  href: "",
105
134
  icon: require("../../assets/img/common/android.svg"),
106
135
  // qrcode: require("../../assets/img/common/android.jpg"),
136
+ placeholder: "即将上线",
107
137
  },
108
138
  {
109
139
  key: "harmonyNext",
@@ -111,6 +141,7 @@ export default {
111
141
  href: "",
112
142
  icon: require("../../assets/img/common/harmony.svg"),
113
143
  // qrcode: require("../../assets/img/common/harmony.jpg"),
144
+ placeholder: "即将上线",
114
145
  },
115
146
  {
116
147
  key: "miniProgram",
@@ -127,6 +158,7 @@ export default {
127
158
  computed: {},
128
159
  watch: {},
129
160
  methods: {
161
+ copyText,
130
162
  getDownloadName(item) {
131
163
  if (item?.key) {
132
164
  const k = item.key === "harmonyNext" ? "harmonyNext" : item.key;
@@ -139,6 +171,9 @@ export default {
139
171
  if (item?.labelKey) return this.$jx3boxT(`jx3boxUi.footer.${item.labelKey}`, item.label || item.labelKey);
140
172
  return item?.label || "";
141
173
  },
174
+ handleLinkClick(e, item) {
175
+ if (!item?.href) e.preventDefault();
176
+ },
142
177
  },
143
178
  created: function () {},
144
179
  mounted: function () {},
@@ -42,7 +42,7 @@
42
42
  </template>
43
43
 
44
44
  <script>
45
- import Bus from "./bus";
45
+ import Bus from "../../utils/bus";
46
46
  import { showAvatar } from "@jx3box/jx3box-common/js/utils";
47
47
  import dayjs from "dayjs";
48
48
  import User from "@jx3box/jx3box-common/js/user";
@@ -75,7 +75,7 @@ export default {
75
75
  },
76
76
  },
77
77
  mounted() {
78
- Bus.$on("showAlternate", () => {
78
+ Bus.on("showAlternate", () => {
79
79
  this.visible = true;
80
80
  });
81
81
  this.init();
@@ -30,7 +30,7 @@
30
30
  <script>
31
31
  import search from "./search.vue";
32
32
  import _ from "lodash";
33
- import Bus from "./bus";
33
+ import Bus from "../../utils/bus";
34
34
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
35
35
  import i18nMixin from "../../i18n/mixin";
36
36
  import box from "../../assets/data/box.json";
@@ -76,7 +76,7 @@ export default {
76
76
  },
77
77
  methods: {
78
78
  closeBox: function () {
79
- Bus.$emit("toggleBox", false);
79
+ Bus.emit("toggleBox", false);
80
80
  },
81
81
  matchedClient: function (client) {
82
82
  return client == "all" ? true : client == this.client;
@@ -126,7 +126,7 @@ export default {
126
126
  this.loadMenu();
127
127
  },
128
128
  mounted: function () {
129
- Bus.$on("toggleBox", (status) => {
129
+ Bus.on("toggleBox", (status) => {
130
130
  if (status == undefined) {
131
131
  this.status = !this.status;
132
132
  } else {
@@ -134,7 +134,7 @@ export default {
134
134
  }
135
135
  });
136
136
  document.addEventListener("click", function () {
137
- Bus.$emit("toggleBox", false);
137
+ Bus.emit("toggleBox", false);
138
138
  });
139
139
  },
140
140
  components: {
@@ -41,6 +41,14 @@
41
41
  <span class="u-txt">{{ item.abbr }}</span>
42
42
  </a>
43
43
  </li>
44
+ <li v-if="allMatched && filteredList.length > 0">
45
+ <a class="u-item" href="/app">
46
+ <div class="u-icon-wrap">
47
+ <img class="u-pic" svg-inline :src="allicon" />
48
+ </div>
49
+ <span class="u-txt">{{ $jx3boxT("jx3boxUi.header.all", "全部") }}</span>
50
+ </a>
51
+ </li>
44
52
  </ul>
45
53
  <div v-if="searchQuery && !homeMatched && filteredList.length === 0" class="u-empty">
46
54
  未找到匹配应用
@@ -52,13 +60,15 @@
52
60
  </template>
53
61
 
54
62
  <script>
55
- import Bus from "./bus";
63
+ import Bus from "../../utils/bus";
56
64
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
57
65
  import i18nMixin from "../../i18n/mixin";
58
66
  import box from "../../assets/data/box.json";
59
67
  import { getMenu } from "../../service/header.js";
68
+ import axios from "axios";
69
+ import { flatten } from "lodash";
60
70
 
61
- const { __imgPath, __cdn } = JX3BOX;
71
+ const { __cdn, __imgPath } = JX3BOX;
62
72
 
63
73
  export default {
64
74
  name: "Box2",
@@ -67,17 +77,32 @@ export default {
67
77
  return {
68
78
  status: false,
69
79
  searchQuery: "",
70
- data: box,
80
+ data: [],
71
81
  client: location.href.includes("origin") ? "origin" : "std",
72
82
  };
73
83
  },
74
84
  computed: {
75
85
  homeicon: function () {
76
- return __imgPath + "image/box/home.svg";
86
+ return this.getBoxIcon("home.svg");
87
+ },
88
+ allicon: function () {
89
+ return this.getBoxIcon("more.svg");
77
90
  },
78
91
  list: function () {
79
92
  return this.data.filter((item) => {
80
- return item.status && (item.client == this.client || item.client == "all");
93
+ const currentClient = String(this.client || "").toLowerCase();
94
+ const rawClient = item.client;
95
+ const clients = !rawClient
96
+ ? []
97
+ : Array.isArray(rawClient)
98
+ ? rawClient.map((c) => String(c || "").toLowerCase())
99
+ : String(rawClient || "")
100
+ .split(",")
101
+ .map((c) => String(c || "").trim().toLowerCase())
102
+ .filter(Boolean);
103
+ const matchedClient =
104
+ !clients.length || clients.includes("all") || clients.includes(currentClient);
105
+ return matchedClient;
81
106
  });
82
107
  },
83
108
  filteredList: function () {
@@ -95,7 +120,12 @@ export default {
95
120
  homeMatched: function () {
96
121
  const query = (this.searchQuery || "").toLowerCase().trim();
97
122
  if (!query) return true;
98
- return "首页".includes(query);
123
+ return this.$jx3boxT("jx3boxUi.header.home", "首页").includes(query);
124
+ },
125
+ allMatched: function () {
126
+ const query = (this.searchQuery || "").toLowerCase().trim();
127
+ if (!query) return true;
128
+ return this.$jx3boxT("jx3boxUi.header.all", "全部").includes(query);
99
129
  },
100
130
  },
101
131
  methods: {
@@ -135,18 +165,26 @@ export default {
135
165
  },
136
166
  loadMenu() {
137
167
  try {
138
- const _box = JSON.parse(sessionStorage.getItem("box"));
139
- if (_box) {
140
- this.data = _box;
168
+ const data = JSON.parse(sessionStorage.getItem("box"));
169
+ if (data) {
170
+ this.data = data;
141
171
  } else {
142
- getMenu("box").then((res) => {
143
- this.data = res.data?.data?.val;
172
+ axios.get(__imgPath + "logo/app.json").then((res) => {
173
+ const _data = flatten(Object.values(res.data || {}));
174
+ this.data = _data.map((item) => ({
175
+ img: item.key + ".svg",
176
+ uuid: item.key,
177
+ abbr: item.label,
178
+ href: item.link,
179
+ client: item.client || ["std", "origin"],
180
+ }));
181
+
182
+ if (!this.data.length) this.data = box;
144
183
  sessionStorage.setItem("box", JSON.stringify(this.data));
145
184
  });
146
185
  }
147
186
  } catch (e) {
148
187
  this.data = box;
149
- console.log("loadBox2 error", e);
150
188
  }
151
189
  },
152
190
  onEsc(e) {
@@ -166,12 +204,12 @@ export default {
166
204
  this.status = !!status;
167
205
  }
168
206
  };
169
- Bus.$on("toggleBox", this.__toggleHandler);
207
+ Bus.on("toggleBox", this.__toggleHandler);
170
208
  window.addEventListener("keydown", this.onEsc);
171
209
  },
172
210
  beforeUnmount() {
173
211
  if (this.__toggleHandler) {
174
- Bus.$off("toggleBox", this.__toggleHandler);
212
+ Bus.off("toggleBox", this.__toggleHandler);
175
213
  this.__toggleHandler = null;
176
214
  }
177
215
  window.removeEventListener("keydown", this.onEsc);
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <div class="c-header-panel c-lang-switcher" id="c-header-lang-switcher">
3
3
  <span class="u-translator" href="/dashboard/boxcoin">
4
- <!-- <langIcon class="u-icon" /> -->
5
4
  <img
6
5
  class="u-icon"
7
6
  svg-inline
@@ -86,7 +85,7 @@ export default {
86
85
  if (!normalized) return;
87
86
 
88
87
  const ok = setJx3boxUiLocale(normalized);
89
- if (!ok) location.reload();
88
+ if (ok) location.reload();
90
89
  },
91
90
  },
92
91
  };
@@ -14,7 +14,7 @@
14
14
  <script>
15
15
  // import { useLayoutStore } from "@/store/layout";
16
16
  import i18nMixin from "../../i18n/mixin";
17
- import Bus from "./bus";
17
+ import Bus from "../../utils/bus";
18
18
 
19
19
  export default {
20
20
  name: "c-header-logo",
@@ -34,7 +34,7 @@ export default {
34
34
  // 盒子
35
35
  toggleBox: function (e) {
36
36
  e.stopPropagation();
37
- Bus.$emit("toggleBox");
37
+ Bus.emit("toggleBox");
38
38
  },
39
39
  },
40
40
  mounted: function () {},
@@ -86,7 +86,7 @@
86
86
 
87
87
  <div class="u-other">
88
88
  <a href="/dashboard/role" class="u-item"
89
- ><el-icon><Sunny /></el-icon>{{ $jx3boxT("jx3boxUi.header.roleManage", "角色管理") }}
89
+ ><el-icon><User /></el-icon>{{ $jx3boxT("jx3boxUi.header.roleManage", "角色管理") }}
90
90
  </a>
91
91
  <a href="/dashboard/fav" class="u-item"
92
92
  ><el-icon><Star /></el-icon>{{ $jx3boxT("jx3boxUi.header.favorites", "收藏订阅") }}
@@ -95,7 +95,7 @@
95
95
  ><el-icon><Memo /></el-icon>{{ $jx3boxT("jx3boxUi.header.orderCenter", "订单中心") }}
96
96
  </a>
97
97
  <a href="/dashboard/config" class="u-item"
98
- ><el-icon><Box /></el-icon>{{ $jx3boxT("jx3boxUi.header.dashboard", "个人中心") }}
98
+ ><el-icon><Help /></el-icon>{{ $jx3boxT("jx3boxUi.header.dashboard", "个人中心") }}
99
99
  </a>
100
100
  <hr />
101
101
  <a href="/dashboard/profile" class="u-item"
@@ -125,7 +125,7 @@ import { showDate } from "@jx3box/jx3box-common/js/moment";
125
125
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
126
126
  import { copyText } from "./utils";
127
127
  import { getMenu } from "../../service/header";
128
- import Bus from "./bus";
128
+ import Bus from "../../utils/bus";
129
129
  import alternate from "./alternate.vue";
130
130
  import i18nMixin from "../../i18n/mixin";
131
131
 
@@ -272,7 +272,7 @@ export default {
272
272
  }
273
273
  },
274
274
  changeAlternate: function () {
275
- Bus.$emit("showAlternate");
275
+ Bus.emit("showAlternate");
276
276
  },
277
277
  },
278
278
  };
@@ -0,0 +1,22 @@
1
+ import CommonFooter from '../../CommonFooter.vue';
2
+
3
+ const meta = {
4
+ title: 'Layout/CommonFooter',
5
+ component: CommonFooter,
6
+ parameters: {
7
+ layout: 'fullscreen',
8
+ },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ export const Default = {
14
+ render: () => ({
15
+ components: { CommonFooter },
16
+ template: `
17
+ <div style="min-height: 100vh; display: flex; align-items: flex-end; background: #111827;">
18
+ <CommonFooter style="width: 100%;" />
19
+ </div>
20
+ `,
21
+ }),
22
+ };
@@ -0,0 +1,39 @@
1
+ import CommonHeader from '../../CommonHeader.vue';
2
+
3
+ const meta = {
4
+ title: 'Layout/CommonHeader',
5
+ component: CommonHeader,
6
+ parameters: {
7
+ layout: 'fullscreen',
8
+ },
9
+ argTypes: {
10
+ overlayEnable: { control: 'boolean' },
11
+ },
12
+ };
13
+
14
+ export default meta;
15
+
16
+ export const Default = {
17
+ args: {
18
+ overlayEnable: false,
19
+ },
20
+ render: (args) => ({
21
+ components: { CommonHeader },
22
+ setup() {
23
+ return { args };
24
+ },
25
+ template: `
26
+ <div style="min-height: 200vh; background: linear-gradient(180deg, #0b1220 0%, #1f2937 100%);">
27
+ <CommonHeader v-bind="args" />
28
+ <div style="height: 1600px;"></div>
29
+ </div>
30
+ `,
31
+ }),
32
+ };
33
+
34
+ export const Overlay = {
35
+ args: {
36
+ overlayEnable: true,
37
+ },
38
+ render: Default.render,
39
+ };
package/src/header/bus.js DELETED
@@ -1,9 +0,0 @@
1
- import mitt from "mitt";
2
-
3
- const emitter = mitt();
4
-
5
- export default {
6
- $on: emitter.on,
7
- $off: emitter.off,
8
- $emit: emitter.emit,
9
- };
File without changes
File without changes