@jx3box/jx3box-ui 2.0.6 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ import WikiRevisions from '../../wiki/WikiRevisions.vue';
2
+ import StorybookShell from '../layout/StorybookShell.vue';
3
+ import StoryPropsTable from '../components/StoryPropsTable.vue';
4
+
5
+ const meta = {
6
+ title: 'Wiki/WikiRevisions',
7
+ component: WikiRevisions,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ },
11
+ argTypes: {
12
+ type: { control: 'text' },
13
+ sourceId: { control: 'number' },
14
+ isGame: { control: 'boolean' },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+
20
+ export const Default = {
21
+ args: {
22
+ type: 'achievement',
23
+ sourceId: 1001,
24
+ isGame: false,
25
+ },
26
+ render: (args) => ({
27
+ components: { WikiRevisions, StorybookShell, StoryPropsTable },
28
+ setup() {
29
+ return {
30
+ args,
31
+ propsInfo: [
32
+ { name: 'type', type: 'String', default: '""', description: '百科条目类型。' },
33
+ { name: 'sourceId', type: 'Number | String', default: '""', description: '百科条目源 ID。' },
34
+ { name: 'isGame', type: 'Boolean | Number', default: 'false', description: '是否按游戏内链接前缀生成跳转地址。' },
35
+ ],
36
+ };
37
+ },
38
+ template: `
39
+ <StorybookShell placement="main" :show-right-sidebar="false">
40
+ <div style="display:grid;gap:24px;">
41
+ <WikiRevisions v-bind="args" />
42
+ <StoryPropsTable title="WikiRevisions" description="百科历史版本列表。" :items="propsInfo" />
43
+ </div>
44
+ </StorybookShell>
45
+ `,
46
+ }),
47
+ };
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <span class="c-game-price" :class="{ 'is-align': align }">
3
+ <span class="u-neg" v-if="numericPrice < 0">- </span>
4
+ <span v-for="part in priceParts" :key="part.unit" :class="`u-${part.unit}`">
5
+ <span class="u-value">{{ part.value }}</span>
6
+ <img :src="part.icon" :alt="part.alt" />
7
+ </span>
8
+ </span>
9
+ </template>
10
+
11
+ <script>
12
+ import zhuanIcon from "../../assets/img/price/zhuan.png";
13
+ import jinIcon from "../../assets/img/price/jin.png";
14
+ import yinIcon from "../../assets/img/price/yin.png";
15
+ import tongIcon from "../../assets/img/price/tong.png";
16
+
17
+ const PRICE_UNITS = [
18
+ { unit: "zhuan", base: 100000000, icon: zhuanIcon, alt: "砖" },
19
+ { unit: "jin", base: 10000, icon: jinIcon, alt: "金" },
20
+ { unit: "yin", base: 100, icon: yinIcon, alt: "银" },
21
+ { unit: "tong", base: 1, icon: tongIcon, alt: "铜" },
22
+ ];
23
+
24
+ export default {
25
+ name: "GamePrice",
26
+ props: {
27
+ price: {
28
+ type: [Number, String],
29
+ default: 0,
30
+ },
31
+ align: {
32
+ type: Boolean,
33
+ default: false,
34
+ },
35
+ },
36
+ computed: {
37
+ numericPrice() {
38
+ const value = Number(this.price);
39
+ return Number.isFinite(value) ? Math.trunc(value) : 0;
40
+ },
41
+ priceParts() {
42
+ const value = Math.abs(this.numericPrice);
43
+ let remainder = value;
44
+
45
+ const parts = PRICE_UNITS.map((item) => {
46
+ const amount = Math.floor(remainder / item.base);
47
+ remainder %= item.base;
48
+ return {
49
+ ...item,
50
+ value: amount,
51
+ };
52
+ });
53
+
54
+ const visibleParts = parts.filter((item) => item.value > 0);
55
+
56
+ if (!visibleParts.length) {
57
+ const tong = parts.find((item) => item.unit === "tong");
58
+ return tong ? [{ ...tong, value: 0 }] : [];
59
+ }
60
+
61
+ return visibleParts;
62
+ },
63
+ },
64
+ };
65
+ </script>
66
+
67
+ <style lang="less">
68
+ .c-game-price {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ flex-wrap: nowrap;
72
+ white-space: nowrap;
73
+
74
+ .u-neg,
75
+ .u-zhuan,
76
+ .u-jin,
77
+ .u-yin,
78
+ .u-tong {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ flex-shrink: 0;
82
+ }
83
+
84
+ img {
85
+ display: inline-block;
86
+ .y;
87
+ }
88
+
89
+ &.is-align {
90
+ .u-jin {
91
+ .u-value {
92
+ display: inline-block;
93
+ width: 4ch;
94
+ text-align: right;
95
+ }
96
+ }
97
+ .u-yin,
98
+ .u-tong {
99
+ .u-value {
100
+ display: inline-block;
101
+ width: 2ch;
102
+ text-align: right;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ </style>
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <ul class="u-comments">
3
+ <li class="u-comment-panel" v-for="(comment, key) in comments" :key="key">
4
+ <div class="u-comment">
5
+ <!-- 评论内容 -->
6
+ <div class="u-nickname-panel">
7
+ <a
8
+ class="u-nickname"
9
+ :href="comment.user_id ? author_url(comment.user_id) : null"
10
+ target="_blank"
11
+ v-text="comment.user_nickname"
12
+ ></a>
13
+ <template v-if="comment.parent_id">
14
+ <span>&nbsp;回复&nbsp;</span>
15
+ <a
16
+ class="u-nickname"
17
+ :href="comment.parent.user_id ? author_url(comment.parent.user_id) : null"
18
+ target="_blank"
19
+ v-text="comment.parent.user_nickname"
20
+ ></a>
21
+ </template>
22
+ <span class="u-mark u-top" v-if="comment.is_top">
23
+ <i class="el-icon-download"></i>
24
+ 置顶
25
+ </span>
26
+ <span class="u-mark u-star" v-if="comment.is_star">
27
+ <i class="el-icon-star-on"></i>
28
+ 精华
29
+ </span>
30
+ </div>
31
+ <p class="u-content" v-html="comment.content"></p>
32
+ <!-- 其他 -->
33
+ <div class="m-reply">
34
+ <!-- 展开、收起 -->
35
+ <el-button
36
+ type="default"
37
+ v-if="comment.reply_form && comment.reply_form.show"
38
+ class="u-reply"
39
+ @click="comment.reply_form.show = !comment.reply_form.show"
40
+ >
41
+ <i class="el-icon-arrow-up"></i>
42
+ <span>收起</span>
43
+ </el-button>
44
+ <el-button
45
+ type="primary"
46
+ plain
47
+ v-else
48
+ class="u-reply"
49
+ @click="comment.reply_form.show = !comment.reply_form.show"
50
+ icon="ChatDotRound"
51
+ >
52
+ <span>回复</span>
53
+ </el-button>
54
+ <template v-if="isEditor && !comment.parent_id">
55
+ <el-button
56
+ type="primary"
57
+ class="u-reply"
58
+ @click="onStar(comment)"
59
+ plain
60
+ :icon="comment.is_star ? 'StarFilled' : 'Star'"
61
+ >{{ comment.is_star ? "取消加精" : "加精" }}</el-button
62
+ >
63
+ <el-button type="primary" class="u-reply" @click="onTop(comment)" plain icon="Top">{{
64
+ comment.is_top ? "取消置顶" : "置顶"
65
+ }}</el-button>
66
+ </template>
67
+ <!-- 更新时间 -->
68
+ <span class="u-time" v-text="ts2str(comment.updated)"></span>
69
+ </div>
70
+ <!-- 评论回复表单 -->
71
+ <div class="m-reply-form" v-if="comment.reply_form && comment.reply_form.show">
72
+ <textarea class="u-reply-content" v-model="comment.reply_form.content"></textarea>
73
+ <div class="u-author">
74
+ <span>昵称:</span>
75
+ <input v-model="comment.reply_form.user_nickname" type="text" />
76
+ </div>
77
+ <el-button type="primary" class="u-submit" @click="create_comment(comment.reply_form, comment.id)">
78
+ <i class="el-icon-check"></i>
79
+ <span>提交</span>
80
+ </el-button>
81
+ </div>
82
+ </div>
83
+ <WikiComment v-if="comment.children.length" :comments="comment.children" :source-id="sourceId" />
84
+ </li>
85
+ </ul>
86
+ </template>
87
+
88
+ <script>
89
+ import { authorLink, ts2str } from "@jx3box/jx3box-common/js/utils";
90
+ import User from "@jx3box/jx3box-common/js/user";
91
+
92
+ export default {
93
+ name: "WikiComment",
94
+ props: {
95
+ comments: {
96
+ type: Array,
97
+ default: () => [],
98
+ },
99
+ sourceId: {
100
+ type: [Number, String],
101
+ default: 0,
102
+ },
103
+ },
104
+ computed: {
105
+ isEditor() {
106
+ return User.isEditor();
107
+ },
108
+ },
109
+ methods: {
110
+ author_url: authorLink,
111
+ ts2str,
112
+ create_comment(form, parent_id) {
113
+ let app = this.$parent;
114
+ if (!app.create_comment) app = app.$parent;
115
+ if (!app.create_comment) {
116
+ this.$message({
117
+ message: "发布评论异常,请联系管理员",
118
+ type: "warning",
119
+ });
120
+ return;
121
+ }
122
+ app.create_comment(form, parent_id);
123
+ },
124
+ isParent(comment) {
125
+ return !comment.parent_id;
126
+ },
127
+ onStar(comment) {
128
+ let app = this.$parent;
129
+ if (!app.star_comment) app = app.$parent;
130
+ if (!app.star_comment) {
131
+ this.$message({
132
+ message: "操作异常,请联系管理员",
133
+ type: "warning",
134
+ });
135
+ return;
136
+ }
137
+ app.star_comment(comment, comment.is_star ? 0 : 1);
138
+ },
139
+ onTop(comment) {
140
+ let app = this.$parent;
141
+ if (!app.top_comment) app = app.$parent;
142
+ if (!app.top_comment) {
143
+ this.$message({
144
+ message: "操作异常,请联系管理员",
145
+ type: "warning",
146
+ });
147
+ return;
148
+ }
149
+ app.top_comment(comment, comment.is_top ? 0 : 1);
150
+ },
151
+ },
152
+ };
153
+ </script>
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <WikiPanel class="c-wiki-comments" scene="detail">
3
+ <template #head-title>
4
+ <i class="el-icon-chat-line-round"></i>
5
+ <span>百科评论</span>
6
+ </template>
7
+ <template #body>
8
+ <div class="m-comments-panel" v-loading="loading">
9
+ <div class="u-empty" v-if="!comments || !comments.length">
10
+ <span v-if="comments === null">🎉 数据加载中...</span>
11
+ <span v-if="comments === false">⚠️ 数据加载异常</span>
12
+ <span v-if="comments && !comments.length">💧 暂无评论</span>
13
+ </div>
14
+ <!-- 递归评论组件 -->
15
+ <Comment :comments="comments" :source-id="sourceId" />
16
+ <el-pagination
17
+ class="u-pagination-box"
18
+ background
19
+ hide-on-single-page
20
+ :current-page="page"
21
+ :total="total"
22
+ :page-size="pageSize"
23
+ layout="prev, pager, next, total"
24
+ @current-change="handleCurrentChange"
25
+ ></el-pagination>
26
+ <!-- 回复表单 -->
27
+ <div id="m-reply-form" class="m-reply-form">
28
+ <h4 class="u-title">
29
+ <i class="el-icon-chat-dot-round"></i>
30
+ <span>回复</span>
31
+ </h4>
32
+ <textarea class="u-reply-content" v-model="reply_form.content"></textarea>
33
+ <div class="u-author">
34
+ <span>昵称:</span>
35
+ <input v-model="reply_form.user_nickname" type="text" />
36
+ </div>
37
+ <el-button type="primary" class="u-submit" @click="create_comment(reply_form)">
38
+ <i class="el-icon-check"></i>
39
+ <span>提交</span>
40
+ </el-button>
41
+ </div>
42
+ </div>
43
+ </template>
44
+ </WikiPanel>
45
+ </template>
46
+
47
+ <script>
48
+ import WikiPanel from "./WikiPanel";
49
+ import Comment from "./WikiComment.vue";
50
+ import { wikiComment } from "@jx3box/jx3box-common/js/wiki";
51
+ import User from "@jx3box/jx3box-common/js/user";
52
+
53
+ export default {
54
+ name: "WikiComments",
55
+ props: {
56
+ type: {
57
+ type: String,
58
+ default: "",
59
+ },
60
+ sourceId: {
61
+ type: [Number, String],
62
+ default: 0,
63
+ },
64
+ },
65
+ data() {
66
+ return {
67
+ comments: null,
68
+ reply_form: {
69
+ content: "",
70
+ user_nickname: User.getInfo().name,
71
+ },
72
+ page: 1,
73
+ pageSize: 10,
74
+ total: 0,
75
+ loading: false,
76
+ };
77
+ },
78
+ computed: {
79
+ client: function () {
80
+ return location.href.includes("classic") || location.href.includes("origin") ? "origin" : "std";
81
+ },
82
+ },
83
+ methods: {
84
+ get_comments() {
85
+ if (!this.type || !this.sourceId) return;
86
+ this.loading = true;
87
+ wikiComment
88
+ .list({ type: this.type, id: this.sourceId }, { client: this.client, page: this.page })
89
+ .then((res) => {
90
+ res = res.data;
91
+ let comments = res.data.list;
92
+ for (let i = 0; i < comments.length; i++) {
93
+ comments[i]["reply_form"] = {
94
+ show: false,
95
+ content: "",
96
+ user_nickname: User.getInfo().name,
97
+ };
98
+ }
99
+ this.page = res.data.page;
100
+ this.total = res.data.total;
101
+ this.comments = filter(comments, 0);
102
+ // this.comments = comments;
103
+ this.loading = false;
104
+ });
105
+
106
+ function filter(comments, parent) {
107
+ let outputs = [];
108
+ for (let index in comments) {
109
+ let c = comments[index];
110
+ if (!c) continue;
111
+ if (c.parent_id === parent) {
112
+ // 递归
113
+ let children = filter(c.children, c.id);
114
+ c.children = children.map((item) => {
115
+ item.parent = {
116
+ user_id: c.user_id,
117
+ user_nickname: c.user_nickname,
118
+ id: c.id,
119
+ };
120
+ item.reply_form = {
121
+ show: false,
122
+ content: "",
123
+ user_nickname: User.getInfo().name,
124
+ };
125
+ return item;
126
+ });
127
+ outputs.push(c);
128
+ }
129
+ }
130
+ return outputs;
131
+ }
132
+ },
133
+ create_comment(form, parent_id = 0) {
134
+ // 校验评论内容
135
+ if (!form.content) {
136
+ this.$message({
137
+ message: "请先填写评论内容再尝试提交",
138
+ type: "warning",
139
+ });
140
+ return;
141
+ }
142
+ const data = {
143
+ type: this.type,
144
+ source_id: this.sourceId,
145
+ parent_id: parent_id,
146
+ user_nickname: form.user_nickname || User.getInfo().name,
147
+ content: form.content,
148
+ client: this.client,
149
+ };
150
+ wikiComment
151
+ .post(data)
152
+ .then((res) => {
153
+ res = res.data;
154
+ form.content = "";
155
+ this.$message({
156
+ message: "提交成功,请等待审核",
157
+ type: "success",
158
+ });
159
+ })
160
+ .finally(() => {
161
+ form.show = false;
162
+ });
163
+ },
164
+ handleCurrentChange(page) {
165
+ this.page = page;
166
+ this.get_comments();
167
+ },
168
+ star_comment(comment, is_star) {
169
+ wikiComment
170
+ .star(comment.id, {
171
+ is_star,
172
+ })
173
+ .then(() => {
174
+ this.page = 1; // 重置页码
175
+ this.get_comments();
176
+ });
177
+ },
178
+ top_comment(comment, is_top) {
179
+ wikiComment
180
+ .top(comment.id, {
181
+ is_top,
182
+ })
183
+ .then(() => {
184
+ this.page = 1; // 重置页码
185
+ this.get_comments();
186
+ });
187
+ },
188
+ },
189
+ components: {
190
+ WikiPanel,
191
+ Comment,
192
+ },
193
+ watch: {
194
+ sourceId: {
195
+ immediate: true,
196
+ handler() {
197
+ this.get_comments();
198
+ },
199
+ },
200
+ },
201
+ };
202
+ </script>
203
+
204
+ <style lang="less">
205
+ @import "../../assets/css/wiki/wiki-comments.less";
206
+ </style>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div
3
+ class="c-wiki-panel"
4
+ :class="{
5
+ 'border-none': borderNone,
6
+ 'm-detail-scene': wikiPost || scene === 'detail',
7
+ }"
8
+ >
9
+ <div class="m-panel-head">
10
+ <slot name="head-before"></slot>
11
+ <div class="m-panel-actions">
12
+ <QRcode v-if="wikiPost && showQR" class="u-qr" />
13
+ <slot name="head-actions"></slot>
14
+ </div>
15
+ <div class="m-panel-title">
16
+ <slot name="head-title"></slot>
17
+ </div>
18
+ <slot name="head-after"></slot>
19
+ </div>
20
+ <div class="m-panel-body">
21
+ <slot name="body-before"></slot>
22
+ <div class="m-wiki-metas" v-if="wikiPost && wikiPost.post">
23
+ <!-- 参与贡献 -->
24
+ <div class="u-meta" v-if="wikiPost.users && wikiPost.users.length">
25
+ <em class="u-label">参与贡献</em>
26
+ <a
27
+ class="u-value u-creator"
28
+ v-for="(user, key) in wikiPost.users"
29
+ :key="key"
30
+ target="_blank"
31
+ :href="user.id ? author_url(user.id) : null"
32
+ >
33
+ <img :src="thumbnail_url(user.avatar)" :alt="user.nickname" :title="user.nickname" />
34
+ </a>
35
+ </div>
36
+ <!-- 综合难度 -->
37
+ <div class="u-meta" v-if="wikiPost.post && wikiPost.post.level">
38
+ <em class="u-label">综合难度</em>
39
+ <span class="u-value">
40
+ <i class="el-icon-star-on" v-for="i in wikiPost.post.level" :key="i"></i>
41
+ </span>
42
+ </div>
43
+ <!-- 热度 -->
44
+ <div class="u-meta" v-if="stat">
45
+ <em class="u-label">热度</em>
46
+ <span class="u-value" v-text="stat.views"></span>
47
+ </div>
48
+ <!-- 更新时间 -->
49
+ <div class="u-meta" v-if="wikiPost.post && wikiPost.post.updated">
50
+ <em class="u-label">更新时间</em>
51
+ <span class="u-value" v-text="ts2str(wikiPost.post.updated)"></span>
52
+ </div>
53
+ </div>
54
+ <slot name="body"></slot>
55
+ <slot name="body-after"></slot>
56
+ </div>
57
+ </div>
58
+ </template>
59
+
60
+ <script>
61
+ import _ from "lodash";
62
+ import QRcode from "../interact/QRcode";
63
+ import { authorLink, ts2str, showAvatar } from "@jx3box/jx3box-common/js/utils";
64
+ import { getStat } from "@jx3box/jx3box-common/js/stat";
65
+ export default {
66
+ name: "WikiPost",
67
+ props: {
68
+ wikiPost: {
69
+ type: Object,
70
+ default: null,
71
+ },
72
+ scene: {
73
+ type: String,
74
+ default: "default",
75
+ },
76
+ borderNone: {
77
+ type: Boolean,
78
+ default: false,
79
+ },
80
+ showQR: {
81
+ type: Boolean,
82
+ default: true,
83
+ },
84
+ },
85
+ data() {
86
+ return {
87
+ stat: null,
88
+ };
89
+ },
90
+ watch: {
91
+ wikiPost: {
92
+ immediate: true,
93
+ handler() {
94
+ if (!this.wikiPost) return;
95
+ // 获取热度信息
96
+ if (this.wikiPost.type && this.wikiPost.source_id) {
97
+ let type = this.wikiPost.type;
98
+ if (type === "achievement") type = "cj";
99
+ getStat(type, this.wikiPost.source_id).then((data) => {
100
+ if (data.status === 200) this.stat = data.data;
101
+ });
102
+ }
103
+ },
104
+ },
105
+ },
106
+ methods: {
107
+ author_url: authorLink,
108
+ ts2str,
109
+ thumbnail_url: function (val) {
110
+ return showAvatar(val);
111
+ },
112
+ },
113
+ components: {
114
+ QRcode,
115
+ },
116
+ };
117
+ </script>
118
+
119
+ <style lang="less">
120
+ @import "../../assets/css/wiki/wiki-panel.less";
121
+ </style>