@jx3box/jx3box-ui 2.3.5 → 2.3.7
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/assets/css/common/left-sidebar.less +1 -1
- package/assets/css/single/author.less +54 -8
- package/assets/img/author/star.svg +1 -0
- package/package.json +2 -2
- package/service/cms.js +14 -0
- package/src/App.vue +5 -5
- package/src/author/AuthorInfo.vue +3 -4
- package/src/author/AuthorPosts.vue +1 -1
- package/src/author/AuthorRole.vue +189 -0
- package/src/author/AuthorRss.vue +6 -8
- package/src/comment/CommentContent.vue +10 -15
- package/src/comment/CommentWithReply.vue +20 -56
- package/src/single/Author.vue +93 -1
- package/src/single/Comment.vue +48 -1
- package/tests/author-pc-sidebar-skin.test.js +34 -0
- package/tests/comment-decoration-v2.test.js +14 -9
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/* assets/css/single/author.less */
|
|
2
2
|
.c-author {
|
|
3
|
+
|
|
4
|
+
padding:15px;
|
|
5
|
+
|
|
6
|
+
background-repeat: no-repeat;
|
|
7
|
+
background-size: 100% auto;
|
|
8
|
+
|
|
3
9
|
.u-tips {
|
|
4
10
|
.db;
|
|
5
11
|
color: #555;
|
|
@@ -67,13 +73,53 @@
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
.u-interact {
|
|
70
|
-
.mt(
|
|
76
|
+
.mt(14px);
|
|
71
77
|
display: flex;
|
|
72
78
|
align-items: center;
|
|
73
|
-
gap:
|
|
79
|
+
gap: 12px;
|
|
80
|
+
|
|
81
|
+
.el-button {
|
|
82
|
+
margin: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.u-btn.el-button {
|
|
86
|
+
min-width: 70px;
|
|
87
|
+
height: 28px;
|
|
88
|
+
padding: 0 12px;
|
|
89
|
+
border-radius: 5px;
|
|
90
|
+
border-color: #d6dbe4;
|
|
91
|
+
background: rgba(255, 255, 255, 0.72);
|
|
92
|
+
color: #4b5563;
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
font-weight: 700;
|
|
95
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
|
|
96
|
+
backdrop-filter: blur(6px);
|
|
97
|
+
|
|
98
|
+
&:hover,
|
|
99
|
+
&:focus {
|
|
100
|
+
border-color: fade(@v4primary, 40%);
|
|
101
|
+
background: rgba(255, 255, 255, 0.9);
|
|
102
|
+
color: @v4primary;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.el-icon {
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.u-btn.el-button.c-author-rss.is-subscribed {
|
|
111
|
+
border-color: #dcdfe6;
|
|
112
|
+
background: rgba(245, 247, 250, 0.9);
|
|
113
|
+
color: #a8abb2;
|
|
114
|
+
box-shadow: none;
|
|
74
115
|
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
&:hover,
|
|
117
|
+
&:focus {
|
|
118
|
+
border-color: #dcdfe6;
|
|
119
|
+
background: rgba(245, 247, 250, 0.9);
|
|
120
|
+
color: #909399;
|
|
121
|
+
box-shadow: none;
|
|
122
|
+
}
|
|
77
123
|
}
|
|
78
124
|
}
|
|
79
125
|
.u-links {
|
|
@@ -84,18 +130,18 @@
|
|
|
84
130
|
}
|
|
85
131
|
|
|
86
132
|
// .u-btn {
|
|
87
|
-
//
|
|
133
|
+
//
|
|
88
134
|
// &:hover {
|
|
89
|
-
//
|
|
135
|
+
//
|
|
90
136
|
// color: @primary;
|
|
91
137
|
// }
|
|
92
138
|
// }
|
|
93
139
|
}
|
|
94
140
|
|
|
95
141
|
.c-sidebar-left .c-author {
|
|
96
|
-
margin: 10px;
|
|
142
|
+
// margin: 10px;
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
.c-sidebar-right .c-author {
|
|
100
|
-
margin: 15px;
|
|
146
|
+
// margin: 15px;
|
|
101
147
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg clip-rule="evenodd" fill-rule="evenodd" height="512" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" viewBox="0 0 1707 1707" width="512" xmlns="http://www.w3.org/2000/svg"><g id="Layer_x0020_1"><path d="m1128 1066c-312 0-564-253-564-564 0 311-253 564-564 564 311 0 564 252 564 563 0-311 252-563 564-563z" fill="#ffd93b"/><g fill="#ffb030"><path d="m1707 1357c-171 0-309-139-309-309 0 170-139 309-309 309 170 0 309 138 309 309 0-171 138-309 309-309z"/><path d="m1529 417c-208 0-376-168-376-376 0 208-168 376-376 376 208 0 376 168 376 376 0-208 168-376 376-376z"/></g></g></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jx3box/jx3box-ui",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
4
4
|
"description": "JX3BOX Vue3 UI",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@element-plus/icons-vue": "^2.3.2",
|
|
35
35
|
"@jx3box/jx3box-common": "^9.2.5",
|
|
36
36
|
"@jx3box/jx3box-data": "^3.9.5",
|
|
37
|
-
"@jx3box/jx3box-editor": "^3.2.
|
|
37
|
+
"@jx3box/jx3box-editor": "^3.2.7",
|
|
38
38
|
"@jx3box/jx3box-emotion": "^1.3.1",
|
|
39
39
|
"@jx3box/jx3box-macro": "^1.0.4",
|
|
40
40
|
"@jx3box/jx3box-talent": "^1.3.14",
|
package/service/cms.js
CHANGED
|
@@ -24,6 +24,18 @@ function getDecorationV2(params) {
|
|
|
24
24
|
params,
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
+
function getUserSkin(user_id) {
|
|
28
|
+
return $cms().get(`/api/cms/user/skin`, {
|
|
29
|
+
params: {
|
|
30
|
+
user_id,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function getUserSkinBatch(params = {}) {
|
|
35
|
+
return $cms().get(`/api/cms/user/skin/batch`, {
|
|
36
|
+
params,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
27
39
|
function getDecorationJson() {
|
|
28
40
|
let url = JX3BOX.__cdn + "design/decoration/index.json";
|
|
29
41
|
return axios.get(url);
|
|
@@ -149,6 +161,8 @@ export {
|
|
|
149
161
|
upload,
|
|
150
162
|
getDecoration,
|
|
151
163
|
getDecorationV2,
|
|
164
|
+
getUserSkin,
|
|
165
|
+
getUserSkinBatch,
|
|
152
166
|
getDecorationJson,
|
|
153
167
|
checkTeamMember,
|
|
154
168
|
getSliders,
|
package/src/App.vue
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<AdminDrop :post="community" :isCommunity="false" :user-id="8" :showMove="true" />
|
|
19
19
|
</template>
|
|
20
20
|
</breadcrumb>
|
|
21
|
-
<LeftSidebar :open="true"
|
|
22
|
-
<Author :uid="
|
|
21
|
+
<LeftSidebar :open="true">
|
|
22
|
+
<Author :uid="8" />
|
|
23
23
|
</LeftSidebar>
|
|
24
24
|
<Main :withoutLeft="false" :withoutRight="false">
|
|
25
25
|
<el-tabs v-model="tab" type="card">
|
|
@@ -101,7 +101,8 @@
|
|
|
101
101
|
</el-tab-pane>
|
|
102
102
|
<el-tab-pane label="文章内容" name="content">
|
|
103
103
|
<el-radio-group v-model="post_id">
|
|
104
|
-
<el-radio value="
|
|
104
|
+
<el-radio value="19382">临时测试</el-radio>
|
|
105
|
+
<!-- <el-radio value="23240">临时测试</el-radio> -->
|
|
105
106
|
<el-radio value="35605">Markdown</el-radio>
|
|
106
107
|
<el-radio value="32035">仅小册</el-radio>
|
|
107
108
|
<el-radio value="30017">仅联合创作者</el-radio>
|
|
@@ -232,8 +233,7 @@ export default {
|
|
|
232
233
|
return {
|
|
233
234
|
tab: "content",
|
|
234
235
|
|
|
235
|
-
|
|
236
|
-
post_id: "99808",
|
|
236
|
+
post_id: "19382",
|
|
237
237
|
post: {},
|
|
238
238
|
client: location.href.includes("origin") ? "origin" : "std",
|
|
239
239
|
item1: null,
|
|
@@ -159,10 +159,9 @@ export default {
|
|
|
159
159
|
.c-author-info {
|
|
160
160
|
.u-author {
|
|
161
161
|
.flex(y);
|
|
162
|
-
gap:
|
|
163
|
-
// .db;
|
|
164
|
-
.mb(10px);
|
|
162
|
+
gap:12px;
|
|
165
163
|
.pr;
|
|
164
|
+
margin:5px 5px 10px 5px;
|
|
166
165
|
}
|
|
167
166
|
.u-avatar {
|
|
168
167
|
// .fl;
|
|
@@ -215,7 +214,7 @@ export default {
|
|
|
215
214
|
.u-bio {
|
|
216
215
|
.fz(12px, 2);
|
|
217
216
|
.break(3);
|
|
218
|
-
color: #
|
|
217
|
+
color: #9aa7b5;
|
|
219
218
|
padding: 0 5px;
|
|
220
219
|
white-space: pre-line;
|
|
221
220
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="c-author-role" v-if="roles.length">
|
|
3
|
+
<div class="u-label">
|
|
4
|
+
<img svg-inline src="../../assets/img/author/star.svg" />
|
|
5
|
+
<span>{{ $jx3boxT("jx3boxUi.authorRole.title", "作者角色") }}</span>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="u-roles">
|
|
8
|
+
<component
|
|
9
|
+
:is="item.url ? 'a' : 'span'"
|
|
10
|
+
class="u-role"
|
|
11
|
+
:href="item.url"
|
|
12
|
+
:target="item.url ? '_blank' : null"
|
|
13
|
+
:style="roleStyle(item)"
|
|
14
|
+
v-for="item in roles"
|
|
15
|
+
:key="item.key"
|
|
16
|
+
>
|
|
17
|
+
<img class="u-role-icon" v-if="item.icon" :src="item.icon" />
|
|
18
|
+
<span class="u-role-content">
|
|
19
|
+
<span class="u-role-name">{{ item.name }}</span>
|
|
20
|
+
<span class="u-role-server" v-if="item.server">@{{ item.server }}</span>
|
|
21
|
+
</span>
|
|
22
|
+
</component>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
29
|
+
import { showSchoolIcon } from "@jx3box/jx3box-common/js/utils";
|
|
30
|
+
import i18nMixin from "../../i18n/mixin";
|
|
31
|
+
|
|
32
|
+
const { __Root, __cdn } = JX3BOX;
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
name: "AuthorRole",
|
|
36
|
+
mixins: [i18nMixin],
|
|
37
|
+
props: {
|
|
38
|
+
data: {
|
|
39
|
+
type: Object,
|
|
40
|
+
default: function () {
|
|
41
|
+
return {};
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
computed: {
|
|
46
|
+
roles: function () {
|
|
47
|
+
const roles = this.normalizeRoles(this.data?.public_roles);
|
|
48
|
+
return roles.map(this.normalizeRole).filter((item) => item.name);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
methods: {
|
|
52
|
+
normalizeRoles: function (roles) {
|
|
53
|
+
if (!roles) return [];
|
|
54
|
+
if (Array.isArray(roles)) return roles;
|
|
55
|
+
if (typeof roles === "string") {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(roles);
|
|
58
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return roles
|
|
61
|
+
.split(/[,,]/)
|
|
62
|
+
.map((item) => item.trim())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [roles];
|
|
67
|
+
},
|
|
68
|
+
normalizeRole: function (role, index) {
|
|
69
|
+
if (typeof role === "string") {
|
|
70
|
+
return {
|
|
71
|
+
key: role + index,
|
|
72
|
+
name: role,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const info = role?.role_info || role?.info || {};
|
|
77
|
+
const name =
|
|
78
|
+
role?.name ||
|
|
79
|
+
role?.title ||
|
|
80
|
+
role?.label ||
|
|
81
|
+
role?.role_name ||
|
|
82
|
+
role?.display_name ||
|
|
83
|
+
role?.remark ||
|
|
84
|
+
info?.name ||
|
|
85
|
+
info?.title ||
|
|
86
|
+
info?.label ||
|
|
87
|
+
info?.role_name ||
|
|
88
|
+
"";
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
key: role?.id || role?.key || role?.val || name + index,
|
|
92
|
+
name,
|
|
93
|
+
server: role?.server || role?.role_server || role?.jx3_server || info?.server || info?.role_server || "",
|
|
94
|
+
desc: role?.desc || role?.description || role?.summary || info?.desc || info?.description || "",
|
|
95
|
+
url: this.normalizeLink(role?.url || role?.link || role?.href || info?.url || info?.link || ""),
|
|
96
|
+
icon: this.normalizeIcon(role, info),
|
|
97
|
+
color: role?.color || info?.color || "",
|
|
98
|
+
backgroundColor: role?.background_color || role?.bg_color || info?.background_color || info?.bg_color || "",
|
|
99
|
+
borderColor: role?.border_color || info?.border_color || "",
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
normalizeAsset: function (url) {
|
|
103
|
+
if (!url) return "";
|
|
104
|
+
url = String(url).trim();
|
|
105
|
+
if (/^(https?:)?\/\//.test(url)) return url;
|
|
106
|
+
return __cdn + url.replace(/^\/+/, "");
|
|
107
|
+
},
|
|
108
|
+
normalizeIcon: function (role, info) {
|
|
109
|
+
const mount = role?.mount || role?.school || role?.school_id || info?.mount || info?.school || info?.school_id || "";
|
|
110
|
+
if (mount) return showSchoolIcon(mount);
|
|
111
|
+
|
|
112
|
+
return this.normalizeAsset(role?.icon || role?.logo || role?.image || info?.icon || info?.logo || "");
|
|
113
|
+
},
|
|
114
|
+
normalizeLink: function (url) {
|
|
115
|
+
if (!url) return "";
|
|
116
|
+
url = String(url).trim();
|
|
117
|
+
if (/^(https?:)?\/\//.test(url)) return url;
|
|
118
|
+
return __Root + url.replace(/^\/+/, "");
|
|
119
|
+
},
|
|
120
|
+
roleStyle: function (role) {
|
|
121
|
+
return {
|
|
122
|
+
color: role.color || null,
|
|
123
|
+
backgroundColor: role.backgroundColor || null,
|
|
124
|
+
borderColor: role.borderColor || null,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<style lang="less">
|
|
132
|
+
/* src/author/AuthorRole.vue */
|
|
133
|
+
.c-author-role {
|
|
134
|
+
.u-roles {
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
gap: 7px;
|
|
138
|
+
margin-top: 2px;
|
|
139
|
+
}
|
|
140
|
+
.u-role {
|
|
141
|
+
.flex(y);
|
|
142
|
+
gap: 8px;
|
|
143
|
+
max-width: 100%;
|
|
144
|
+
min-height: 22px;
|
|
145
|
+
color: #56677a;
|
|
146
|
+
font-size: 13px;
|
|
147
|
+
line-height: 22px;
|
|
148
|
+
box-sizing: border-box;
|
|
149
|
+
text-decoration: none;
|
|
150
|
+
}
|
|
151
|
+
a.u-role {
|
|
152
|
+
&:hover {
|
|
153
|
+
.u-role-name {
|
|
154
|
+
color: @v4primary;
|
|
155
|
+
}
|
|
156
|
+
.u-role-server {
|
|
157
|
+
color: fade(@v4primary, 65%);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
.u-role-icon {
|
|
162
|
+
.size(22px);
|
|
163
|
+
flex-shrink: 0;
|
|
164
|
+
object-fit: contain;
|
|
165
|
+
}
|
|
166
|
+
.u-role-content {
|
|
167
|
+
min-width: 0;
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: baseline;
|
|
170
|
+
}
|
|
171
|
+
.u-role-name {
|
|
172
|
+
min-width: 0;
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
text-overflow: ellipsis;
|
|
175
|
+
white-space: nowrap;
|
|
176
|
+
color: #42566d;
|
|
177
|
+
font-weight: 400;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
margin-right: 2px;
|
|
180
|
+
}
|
|
181
|
+
.u-role-server {
|
|
182
|
+
flex-shrink: 0;
|
|
183
|
+
// color: #9aa7b5;
|
|
184
|
+
color: @v4yellow;
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
font-weight: 400;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
</style>
|
package/src/author/AuthorRss.vue
CHANGED
|
@@ -132,25 +132,23 @@ export default {
|
|
|
132
132
|
/* src/author/AuthorRss.vue */
|
|
133
133
|
.c-author-rss.el-button {
|
|
134
134
|
cursor: default;
|
|
135
|
+
transition: all 0.18s ease;
|
|
135
136
|
&:hover,
|
|
136
137
|
&.is-plain:hover,
|
|
137
138
|
&.is-plain:focus {
|
|
138
139
|
cursor: pointer;
|
|
139
|
-
background-color:
|
|
140
|
-
color:
|
|
141
|
-
border-color:
|
|
140
|
+
background-color: rgba(255, 51, 153, 0.08);
|
|
141
|
+
color: @v4primary;
|
|
142
|
+
border-color: fade(@v4primary, 36%);
|
|
143
|
+
box-shadow: 0 6px 14px rgba(255, 51, 153, 0.1);
|
|
142
144
|
}
|
|
143
145
|
.u-icon {
|
|
144
|
-
.size(
|
|
146
|
+
.size(11px);
|
|
145
147
|
fill: currentColor;
|
|
146
148
|
margin-right: 4px;
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
.c-author-rss.el-button.is-subscribed {
|
|
151
|
-
color: #999;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
152
|
@media screen and (max-width: @phone) {
|
|
155
153
|
.el-message-box {
|
|
156
154
|
max-width: 60%;
|
|
@@ -95,25 +95,24 @@
|
|
|
95
95
|
</time>
|
|
96
96
|
</div>
|
|
97
97
|
<div class="u-toolbar-right">
|
|
98
|
-
<el-button
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
<el-button
|
|
99
|
+
class="u-admin u-filter"
|
|
100
|
+
v-if="canAddWhite"
|
|
101
|
+
link
|
|
102
|
+
icon="View"
|
|
103
|
+
size="small"
|
|
104
|
+
@click="setWhiteComment(true)"
|
|
105
|
+
>
|
|
104
106
|
{{ $jx3boxT("jx3boxUi.commentContent.show", "显示") }}</el-button
|
|
105
107
|
>
|
|
106
108
|
<el-button
|
|
107
109
|
class="u-admin u-filter"
|
|
108
110
|
v-if="canRemoveWhite"
|
|
109
111
|
link
|
|
112
|
+
icon="Hide"
|
|
110
113
|
size="small"
|
|
111
114
|
@click="setWhiteComment(false)"
|
|
112
|
-
|
|
113
|
-
class="u-icon-filter"
|
|
114
|
-
src="../../assets/img/comment/hide.svg"
|
|
115
|
-
:alt="$jx3boxT('jx3boxUi.commentContent.hidden', '隐藏')"
|
|
116
|
-
/>{{ $jx3boxT("jx3boxUi.commentContent.hidden", "隐藏") }}</el-button
|
|
115
|
+
>{{ $jx3boxT("jx3boxUi.commentContent.hidden", "隐藏") }}</el-button
|
|
117
116
|
>
|
|
118
117
|
</div>
|
|
119
118
|
</div>
|
|
@@ -450,10 +449,6 @@ export default {
|
|
|
450
449
|
align-items: center;
|
|
451
450
|
gap: 5px;
|
|
452
451
|
}
|
|
453
|
-
.u-icon-filter {
|
|
454
|
-
width: 16px;
|
|
455
|
-
height: 16px;
|
|
456
|
-
}
|
|
457
452
|
}
|
|
458
453
|
.u-date {
|
|
459
454
|
color: #c0c4cc;
|
|
@@ -73,20 +73,26 @@ import CommentContent from "./CommentContent.vue";
|
|
|
73
73
|
import ReplyList from "./ReplyList.vue";
|
|
74
74
|
import { POST, DELETE, GET } from "../../service/comment";
|
|
75
75
|
import CommentAvatar from "../comment/Avatar.vue";
|
|
76
|
-
import { getDecorationV2 } from "../../service/cms";
|
|
77
76
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
78
77
|
import i18nMixin from "../../i18n/mixin";
|
|
79
78
|
const { __cdn } = JX3BOX;
|
|
80
|
-
const DECORATION_KEY = "decoration_comment_";
|
|
81
|
-
const DECORATION_EMPTY_VALUE = "no";
|
|
82
79
|
const DECORATION_POSITION_MAP = {
|
|
83
80
|
lt: "left top",
|
|
81
|
+
mt: "center top",
|
|
84
82
|
ct: "center top",
|
|
85
83
|
rt: "right top",
|
|
84
|
+
lm: "left center",
|
|
86
85
|
lc: "left center",
|
|
86
|
+
ml: "left center",
|
|
87
|
+
mm: "center center",
|
|
88
|
+
cm: "center center",
|
|
89
|
+
o: "center center",
|
|
87
90
|
cc: "center center",
|
|
91
|
+
rm: "right center",
|
|
92
|
+
mr: "right center",
|
|
88
93
|
rc: "right center",
|
|
89
94
|
lb: "left bottom",
|
|
95
|
+
mb: "center bottom",
|
|
90
96
|
cb: "center bottom",
|
|
91
97
|
rb: "right bottom",
|
|
92
98
|
};
|
|
@@ -117,6 +123,10 @@ export default {
|
|
|
117
123
|
type: Boolean,
|
|
118
124
|
default: false,
|
|
119
125
|
},
|
|
126
|
+
skin: {
|
|
127
|
+
type: Object,
|
|
128
|
+
default: null,
|
|
129
|
+
},
|
|
120
130
|
},
|
|
121
131
|
components: {
|
|
122
132
|
CommentContent,
|
|
@@ -133,8 +143,6 @@ export default {
|
|
|
133
143
|
pageTotal: 1,
|
|
134
144
|
total: 0,
|
|
135
145
|
},
|
|
136
|
-
decoration: null,
|
|
137
|
-
decorationPosition: "",
|
|
138
146
|
};
|
|
139
147
|
},
|
|
140
148
|
computed: {
|
|
@@ -142,10 +150,13 @@ export default {
|
|
|
142
150
|
return this.item.userId;
|
|
143
151
|
},
|
|
144
152
|
decorationStyles() {
|
|
145
|
-
|
|
153
|
+
const image = this.normalizeDecorationImage(this.skin?.image);
|
|
154
|
+
return image
|
|
146
155
|
? {
|
|
147
|
-
backgroundImage: `url(${
|
|
148
|
-
backgroundPosition: this.
|
|
156
|
+
backgroundImage: `url(${image})`,
|
|
157
|
+
backgroundPosition: this.resolveDecorationPosition(this.skin?.position),
|
|
158
|
+
backgroundRepeat: "no-repeat",
|
|
159
|
+
backgroundSize: "100% auto",
|
|
149
160
|
borderRadius: "8px",
|
|
150
161
|
}
|
|
151
162
|
: null;
|
|
@@ -153,17 +164,8 @@ export default {
|
|
|
153
164
|
},
|
|
154
165
|
created() {
|
|
155
166
|
this.replyList = this.item.reply || [];
|
|
156
|
-
this.getDecoration();
|
|
157
167
|
},
|
|
158
168
|
methods: {
|
|
159
|
-
resolveDecorationDetail(decoration) {
|
|
160
|
-
if (!decoration) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const decorations = Array.isArray(decoration.decorations) ? decoration.decorations : [];
|
|
165
|
-
return decorations.find((item) => item && item.image) || null;
|
|
166
|
-
},
|
|
167
169
|
normalizeDecorationImage(image) {
|
|
168
170
|
if (!image) {
|
|
169
171
|
return "";
|
|
@@ -177,45 +179,7 @@ export default {
|
|
|
177
179
|
return __cdn + url.replace(/^\/+/, "");
|
|
178
180
|
},
|
|
179
181
|
resolveDecorationPosition(position) {
|
|
180
|
-
return DECORATION_POSITION_MAP[position] ||
|
|
181
|
-
},
|
|
182
|
-
setDecoration(decoration) {
|
|
183
|
-
const decorationDetail = this.resolveDecorationDetail(decoration);
|
|
184
|
-
const image = this.normalizeDecorationImage(decorationDetail?.image);
|
|
185
|
-
if (!image) {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
this.decoration = image;
|
|
190
|
-
this.decorationPosition = this.resolveDecorationPosition(decorationDetail.position);
|
|
191
|
-
return true;
|
|
192
|
-
},
|
|
193
|
-
getDecoration() {
|
|
194
|
-
let decoration_local = sessionStorage.getItem(DECORATION_KEY + this.uid);
|
|
195
|
-
if (decoration_local == DECORATION_EMPTY_VALUE) return;
|
|
196
|
-
if (decoration_local) {
|
|
197
|
-
try {
|
|
198
|
-
let decoration_parse = JSON.parse(decoration_local);
|
|
199
|
-
if (decoration_parse && this.setDecoration(decoration_parse)) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
sessionStorage.removeItem(DECORATION_KEY + this.uid);
|
|
203
|
-
} catch (err) {
|
|
204
|
-
sessionStorage.removeItem(DECORATION_KEY + this.uid);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
getDecorationV2({ using: 1, user_id: this.uid, type: "comment", subtype: "pc_comment" }).then((res) => {
|
|
208
|
-
let decorationList = res.data.data || [];
|
|
209
|
-
let decoration = decorationList.find(
|
|
210
|
-
(item) => item.type == "comment" && this.resolveDecorationDetail(item)
|
|
211
|
-
);
|
|
212
|
-
if (decoration && this.setDecoration(decoration)) {
|
|
213
|
-
sessionStorage.setItem(DECORATION_KEY + this.uid, JSON.stringify(decoration));
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
sessionStorage.setItem(DECORATION_KEY + this.uid, DECORATION_EMPTY_VALUE);
|
|
218
|
-
});
|
|
182
|
+
return DECORATION_POSITION_MAP[position] || "right top";
|
|
219
183
|
},
|
|
220
184
|
profileLink: function (uid) {
|
|
221
185
|
return authorLink(uid);
|
package/src/single/Author.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="c-author">
|
|
2
|
+
<div class="c-author" :style="decorationStyles">
|
|
3
3
|
<AuthorInfo :uid="uid" :anonymous="anonymous" @ready="installModules" />
|
|
4
4
|
<template v-if="data">
|
|
5
5
|
<div class="u-interact">
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
>
|
|
13
13
|
</div>
|
|
14
14
|
<!-- <AuthorLink class="u-block u-links" :uid="uid" :data="data" /> -->
|
|
15
|
+
<AuthorRole class="u-block u-roles" :data="data" />
|
|
15
16
|
<AuthorMedals class="u-block u-trophy" :uid="uid" />
|
|
16
17
|
<!-- <AuthorTeams class="u-block u-teams" :uid="uid" /> -->
|
|
17
18
|
<!-- <AuthorFans class="u-block u-fans" :uid="uid" /> -->
|
|
@@ -27,12 +28,33 @@ import AuthorInfo from "../author/AuthorInfo.vue";
|
|
|
27
28
|
// import AuthorFollow from "../author/AuthorFollow.vue";
|
|
28
29
|
// import AuthorGift from "../author/AuthorGift.vue";
|
|
29
30
|
// import AuthorFans from "../author/AuthorFans.vue";
|
|
31
|
+
import AuthorRole from "../author/AuthorRole.vue";
|
|
30
32
|
import AuthorMedals from "../author/AuthorMedals.vue";
|
|
31
33
|
// import AuthorTeams from "../author/AuthorTeams.vue";
|
|
32
34
|
import AuthorPosts from "../author/AuthorPosts.vue";
|
|
33
35
|
import AuthorRss from "../author/AuthorRss.vue";
|
|
36
|
+
import { getUserSkin } from "../../service/cms";
|
|
34
37
|
import i18nMixin from "../../i18n/mixin";
|
|
35
38
|
const jx3box = require("@jx3box/jx3box-common/data/jx3box.json");
|
|
39
|
+
const { __cdn } = jx3box;
|
|
40
|
+
const SKIN_POSITION_MAP = {
|
|
41
|
+
lt: "left top",
|
|
42
|
+
ct: "center top",
|
|
43
|
+
mt: "center top",
|
|
44
|
+
rt: "right top",
|
|
45
|
+
lc: "left center",
|
|
46
|
+
ml: "left center",
|
|
47
|
+
cc: "center center",
|
|
48
|
+
cm: "center center",
|
|
49
|
+
mm: "center center",
|
|
50
|
+
o: "center center",
|
|
51
|
+
rc: "right center",
|
|
52
|
+
mr: "right center",
|
|
53
|
+
lb: "left bottom",
|
|
54
|
+
cb: "center bottom",
|
|
55
|
+
mb: "center bottom",
|
|
56
|
+
rb: "right bottom",
|
|
57
|
+
};
|
|
36
58
|
export default {
|
|
37
59
|
name: "AuthorComp",
|
|
38
60
|
mixins: [i18nMixin],
|
|
@@ -49,8 +71,30 @@ export default {
|
|
|
49
71
|
data: function () {
|
|
50
72
|
return {
|
|
51
73
|
data: "",
|
|
74
|
+
sidebarSkin: "",
|
|
75
|
+
sidebarSkinPosition: "",
|
|
52
76
|
};
|
|
53
77
|
},
|
|
78
|
+
computed: {
|
|
79
|
+
decorationStyles() {
|
|
80
|
+
return this.sidebarSkin
|
|
81
|
+
? {
|
|
82
|
+
backgroundImage: `url(${this.sidebarSkin})`,
|
|
83
|
+
backgroundPosition: this.sidebarSkinPosition,
|
|
84
|
+
}
|
|
85
|
+
: null;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
watch: {
|
|
89
|
+
uid() {
|
|
90
|
+
this.sidebarSkin = "";
|
|
91
|
+
this.sidebarSkinPosition = "";
|
|
92
|
+
this.loadSidebarSkin();
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
created() {
|
|
96
|
+
this.loadSidebarSkin();
|
|
97
|
+
},
|
|
54
98
|
methods: {
|
|
55
99
|
installModules: function (data) {
|
|
56
100
|
if (this.anonymous == 1) {
|
|
@@ -61,12 +105,60 @@ export default {
|
|
|
61
105
|
onMessage: function () {
|
|
62
106
|
window.open("/dashboard/letter?receiver=" + this.uid, "_blank");
|
|
63
107
|
},
|
|
108
|
+
resolveSkinDetail(record) {
|
|
109
|
+
if (!record) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const skins = Array.isArray(record.skins) ? record.skins : [];
|
|
114
|
+
return skins.find((item) => item && item.subtype === "pc_sidebar" && item.image) || null;
|
|
115
|
+
},
|
|
116
|
+
normalizeSkinImage(image) {
|
|
117
|
+
if (!image) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const url = String(image).trim();
|
|
122
|
+
if (/^(https?:)?\/\//.test(url)) {
|
|
123
|
+
return url;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return __cdn + url.replace(/^\/+/, "");
|
|
127
|
+
},
|
|
128
|
+
resolveSkinPosition(position) {
|
|
129
|
+
return SKIN_POSITION_MAP[position] || position || "";
|
|
130
|
+
},
|
|
131
|
+
setSidebarSkin(record) {
|
|
132
|
+
const detail = this.resolveSkinDetail(record);
|
|
133
|
+
const image = this.normalizeSkinImage(detail?.image);
|
|
134
|
+
if (!image) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.sidebarSkin = image;
|
|
139
|
+
this.sidebarSkinPosition = this.resolveSkinPosition(detail.position);
|
|
140
|
+
return true;
|
|
141
|
+
},
|
|
142
|
+
loadSidebarSkin() {
|
|
143
|
+
if (!this.uid || this.anonymous == 1) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getUserSkin(this.uid).then((res) => {
|
|
148
|
+
const records = res.data.data || [];
|
|
149
|
+
const record = records.find((item) => this.resolveSkinDetail(item));
|
|
150
|
+
if (record && this.setSidebarSkin(record)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
},
|
|
64
155
|
},
|
|
65
156
|
components: {
|
|
66
157
|
AuthorInfo,
|
|
67
158
|
// AuthorLink,
|
|
68
159
|
// AuthorFollow,
|
|
69
160
|
// AuthorGift,
|
|
161
|
+
AuthorRole,
|
|
70
162
|
AuthorMedals,
|
|
71
163
|
// AuthorTeams,
|
|
72
164
|
AuthorPosts,
|
package/src/single/Comment.vue
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
:category="category"
|
|
46
46
|
:power="commentPower"
|
|
47
47
|
:support-video="supportVideo"
|
|
48
|
+
:skin="commentSkins[item.userId]"
|
|
48
49
|
@deleteComment="deleteComment"
|
|
49
50
|
@setTopComment="setTopComment"
|
|
50
51
|
@setStarComment="setStarComment"
|
|
@@ -87,9 +88,11 @@ import { showAvatar, authorLink } from "@jx3box/jx3box-common/js/utils";
|
|
|
87
88
|
import CommentInputForm from "../comment/CommentInputForm.vue";
|
|
88
89
|
import CommentWithReply from "../comment/CommentWithReply.vue";
|
|
89
90
|
import { GET, POST, DELETE, PUT, getOrderMode, setOrderMode } from "../../service/comment";
|
|
90
|
-
import { getConfig } from "../../service/cms";
|
|
91
|
+
import { getConfig, getUserSkinBatch } from "../../service/cms";
|
|
91
92
|
import User from "@jx3box/jx3box-common/js/user";
|
|
92
93
|
import i18nMixin from "../../i18n/mixin";
|
|
94
|
+
const COMMENT_SKIN_TYPE = "comment";
|
|
95
|
+
const COMMENT_SKIN_SUBTYPE = "pc_comment";
|
|
93
96
|
export default {
|
|
94
97
|
name: "CommentComp",
|
|
95
98
|
mixins: [i18nMixin],
|
|
@@ -138,6 +141,7 @@ export default {
|
|
|
138
141
|
orderByLikes: false,
|
|
139
142
|
openWhiteList: false,
|
|
140
143
|
loading: false,
|
|
144
|
+
commentSkins: {},
|
|
141
145
|
|
|
142
146
|
showMask: false,
|
|
143
147
|
};
|
|
@@ -239,6 +243,7 @@ export default {
|
|
|
239
243
|
.then((resp) => {
|
|
240
244
|
this.commentList = resp.data || [];
|
|
241
245
|
this.pager = resp.page;
|
|
246
|
+
this.loadCommentSkins(this.commentList);
|
|
242
247
|
})
|
|
243
248
|
.catch(() => {})
|
|
244
249
|
.finally(() => {
|
|
@@ -281,6 +286,48 @@ export default {
|
|
|
281
286
|
showAvatar: function (val) {
|
|
282
287
|
return showAvatar(val, 144);
|
|
283
288
|
},
|
|
289
|
+
collectCommentUserIds(list = []) {
|
|
290
|
+
return Array.from(
|
|
291
|
+
new Set(
|
|
292
|
+
list
|
|
293
|
+
.map((item) => Number(item?.userId || 0))
|
|
294
|
+
.filter((uid) => Number.isInteger(uid) && uid > 0)
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
selectCommentSkin(records = []) {
|
|
299
|
+
const skins = records.flatMap((record) => (Array.isArray(record?.skins) ? record.skins : []));
|
|
300
|
+
return (
|
|
301
|
+
skins.find((item) => item?.theme === "all" && item?.image) ||
|
|
302
|
+
skins.find((item) => !item?.theme && item?.image) ||
|
|
303
|
+
skins.find((item) => item?.image) ||
|
|
304
|
+
null
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
loadCommentSkins(list = []) {
|
|
308
|
+
const userIds = this.collectCommentUserIds(list);
|
|
309
|
+
if (!userIds.length) {
|
|
310
|
+
this.commentSkins = {};
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getUserSkinBatch({
|
|
315
|
+
user_ids: userIds.join(","),
|
|
316
|
+
type: COMMENT_SKIN_TYPE,
|
|
317
|
+
subtype: COMMENT_SKIN_SUBTYPE,
|
|
318
|
+
})
|
|
319
|
+
.then((res) => {
|
|
320
|
+
const data = res?.data?.data || {};
|
|
321
|
+
this.commentSkins = userIds.reduce((acc, uid) => {
|
|
322
|
+
const records = data[uid] || data[String(uid)] || [];
|
|
323
|
+
acc[uid] = this.selectCommentSkin(records);
|
|
324
|
+
return acc;
|
|
325
|
+
}, {});
|
|
326
|
+
})
|
|
327
|
+
.catch(() => {
|
|
328
|
+
this.commentSkins = {};
|
|
329
|
+
});
|
|
330
|
+
},
|
|
284
331
|
reloadPower() {
|
|
285
332
|
GET(`${this.baseAPI}/i-am-author`)
|
|
286
333
|
.then((power) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const root = path.resolve(__dirname, "..");
|
|
5
|
+
const service = fs.readFileSync(path.join(root, "service/cms.js"), "utf8");
|
|
6
|
+
const author = fs.readFileSync(path.join(root, "src/single/Author.vue"), "utf8");
|
|
7
|
+
const authorInfo = fs.readFileSync(path.join(root, "src/author/AuthorInfo.vue"), "utf8");
|
|
8
|
+
const authorStyle = fs.readFileSync(path.join(root, "assets/css/single/author.less"), "utf8");
|
|
9
|
+
|
|
10
|
+
function assert(condition, message) {
|
|
11
|
+
if (!condition) {
|
|
12
|
+
throw new Error(message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
assert(service.includes("/api/cms/user/skin"), "user skin service should request the active skin endpoint");
|
|
17
|
+
|
|
18
|
+
assert(author.includes("getUserSkin"), "Author wrapper should request user skin data directly");
|
|
19
|
+
|
|
20
|
+
assert(!authorInfo.includes("getUserSkin"), "AuthorInfo should not request user skin data");
|
|
21
|
+
|
|
22
|
+
assert(author.includes('item.subtype === "pc_sidebar"'), "Author should consume the pc_sidebar skin part");
|
|
23
|
+
|
|
24
|
+
assert(author.includes("record.skins"), "Author should resolve active skin resources from skins[]");
|
|
25
|
+
|
|
26
|
+
assert(author.includes("backgroundPosition: this.sidebarSkinPosition"), "Author should apply skin position");
|
|
27
|
+
|
|
28
|
+
assert(author.includes('rt: "right top"'), "Author should map rt to right top");
|
|
29
|
+
|
|
30
|
+
assert(authorStyle.includes("background-repeat: no-repeat"), "Author skin background should not repeat");
|
|
31
|
+
|
|
32
|
+
assert(authorStyle.includes("background-size: 100% auto"), "Author skin background should use full width and auto height");
|
|
33
|
+
|
|
34
|
+
console.log("author pc sidebar skin checks passed");
|
|
@@ -3,9 +3,9 @@ const path = require("path");
|
|
|
3
3
|
|
|
4
4
|
const root = path.resolve(__dirname, "..");
|
|
5
5
|
const service = fs.readFileSync(path.join(root, "service/cms.js"), "utf8");
|
|
6
|
+
const commentList = fs.readFileSync(path.join(root, "src/single/Comment.vue"), "utf8");
|
|
6
7
|
const component = fs.readFileSync(path.join(root, "src/comment/CommentWithReply.vue"), "utf8");
|
|
7
8
|
const leftSidebar = fs.readFileSync(path.join(root, "src/LeftSidebar.vue"), "utf8");
|
|
8
|
-
const app = fs.readFileSync(path.join(root, "src/App.vue"), "utf8");
|
|
9
9
|
|
|
10
10
|
function assert(condition, message) {
|
|
11
11
|
if (!condition) {
|
|
@@ -15,11 +15,21 @@ function assert(condition, message) {
|
|
|
15
15
|
|
|
16
16
|
assert(service.includes("/api/cms/user/decoration/v2"), "getDecoration should support the decoration v2 endpoint");
|
|
17
17
|
|
|
18
|
-
assert(
|
|
18
|
+
assert(service.includes("/api/cms/user/skin/batch"), "comment skin should support the batch user skin endpoint");
|
|
19
19
|
|
|
20
|
-
assert(
|
|
20
|
+
assert(commentList.includes("getUserSkinBatch"), "comment list should request user skins in batch");
|
|
21
21
|
|
|
22
|
-
assert(
|
|
22
|
+
assert(commentList.includes('type: COMMENT_SKIN_TYPE'), "comment skin request should include the comment type");
|
|
23
|
+
|
|
24
|
+
assert(commentList.includes('subtype: COMMENT_SKIN_SUBTYPE'), "comment skin request should include the pc_comment subtype");
|
|
25
|
+
|
|
26
|
+
assert(commentList.includes(":skin=\"commentSkins[item.userId]\""), "comment list should pass per-user skin to items");
|
|
27
|
+
|
|
28
|
+
assert(!component.includes("getDecorationV2"), "comment item should not request the legacy decoration v2 endpoint");
|
|
29
|
+
|
|
30
|
+
assert(component.includes("skin:"), "comment item should accept a skin prop");
|
|
31
|
+
|
|
32
|
+
assert(!component.includes("resolveDecorationDetail"), "comment item should not resolve legacy v2 decorations payload");
|
|
23
33
|
|
|
24
34
|
assert(component.includes("normalizeDecorationImage"), "comment decoration should normalize returned image URLs");
|
|
25
35
|
|
|
@@ -48,9 +58,4 @@ assert(
|
|
|
48
58
|
"left sidebar decoration should not depend on the old val/sidebar.png path"
|
|
49
59
|
);
|
|
50
60
|
|
|
51
|
-
assert(
|
|
52
|
-
app.includes('<LeftSidebar :open="true" :uid="8719">') && app.includes('<Author :uid="8719" />'),
|
|
53
|
-
"local author fixture should pass the same 8719 uid to LeftSidebar and Author"
|
|
54
|
-
);
|
|
55
|
-
|
|
56
61
|
console.log("decoration v2 checks passed");
|