管理后台前端搭建

This commit is contained in:
huangdeliang 2021-05-31 19:34:04 +08:00
parent 7e3311ad64
commit 0e08a1fef8
9 changed files with 439 additions and 70 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -9,11 +9,13 @@
<result column="topic_id" property="topicId"/> <result column="topic_id" property="topicId"/>
<result column="topic_type" property="topicType"/> <result column="topic_type" property="topicType"/>
<result column="content" property="content"/> <result column="content" property="content"/>
<result column="name" property="name"/> <result column="uid" property="uid"/>
<result column="role" property="role"/>
<result column="read" property="read"/> <result column="read" property="read"/>
<result column="img" property="img" typeHandler="com.stdiet.custom.typehandler.ArrayJsonHandler"/> <result column="img" property="img" typeHandler="com.stdiet.custom.typehandler.ArrayJsonHandler"/>
<result column="create_time" property="createTime"/> <result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/> <result column="update_time" property="updateTime"/>
<association column="{uid=uid,role=role}" property="name" select="selectUserInfo"/>
</resultMap> </resultMap>
@ -21,21 +23,9 @@
<select id="selectSysServicesTopicByUserIdAndRole" parameterType="SysServicesTopic" <select id="selectSysServicesTopicByUserIdAndRole" parameterType="SysServicesTopic"
resultMap="SysServicesTopicResult"> resultMap="SysServicesTopicResult">
SELECT * FROM ( SELECT * FROM (
SELECT * FROM sys_services_topic_status WHERE role = #{role} AND uid = #{uid} SELECT topic_id, id, `read`, create_time, update_time, 'customer' AS role FROM sys_services_topic_status WHERE role = #{role} AND uid = #{uid}
) AS status ) AS status
LEFT JOIN sys_services_topic USING(topic_id) LEFT JOIN sys_services_topic USING(topic_id)
<choose>
<when test="role == 'customer'">
LEFT JOIN (
SELECT name, id AS uid FROM sys_customer WHERE id = #{uid}
) AS customer ON status.uid = customer.uid
</when>
<otherwise>
LEFT JOIN (
SELECT user_id AS uid, nick_name AS name FROM sys_user WHERE user_id = #{uid}
) AS user ON status.uid = user.uid
</otherwise>
</choose>
ORDER BY `read` ASC, update_time DESC ORDER BY `read` ASC, update_time DESC
</select> </select>
@ -92,10 +82,12 @@
<select id="selectServicesTopicCommentByTopicId" resultMap="ServicesTopicCommentResult"> <select id="selectServicesTopicCommentByTopicId" resultMap="ServicesTopicCommentResult">
select * from sys_services_topic_comment where topic_id = #{topic_id} select * from sys_services_topic_comment where topic_id = #{topic_id}
order by create_time asc
</select> </select>
<select id="selectServicesTopicCommentReplyByCommentId" resultMap="ServicesTopicCommentReplyResult"> <select id="selectServicesTopicCommentReplyByCommentId" resultMap="ServicesTopicCommentReplyResult">
select * from sys_services_topic_reply where comment_id = #{id} order by create_time asc select * from sys_services_topic_reply where comment_id = #{id}
order by create_time asc
</select> </select>

View File

@ -20,7 +20,7 @@ export function postTopicReply(data) {
return request({ return request({
url: "/services/topic/reply", url: "/services/topic/reply",
method: "post", method: "post",
body: data data
}); });
} }
@ -28,6 +28,6 @@ export function postTopicComment(data) {
return request({ return request({
url: "/services/topic/comment", url: "/services/topic/comment",
method: "post", method: "post",
body: data data
}); });
} }

View File

@ -8,9 +8,11 @@ const getters = {
avatar: state => state.user.avatar, avatar: state => state.user.avatar,
name: state => state.user.name, name: state => state.user.name,
introduction: state => state.user.introduction, introduction: state => state.user.introduction,
roles: state => state.user.roles, // roles: state => state.user.roles,
roles: state => ["dietician"],
permissions: state => state.user.permissions, permissions: state => state.user.permissions,
userId: state => state.user.userId, // userId: state => state.user.userId,
userId: state => 131,
userRemark: state => state.user.remark, userRemark: state => state.user.remark,
permission_routes: state => state.permission.routes, permission_routes: state => state.permission.routes,
// //

View File

@ -6,11 +6,15 @@ import {
} from "@/api/custom/message"; } from "@/api/custom/message";
const oriState = { const oriState = {
topicList: undefined, topicList: [],
detailData: undefined detailData: {},
selTopicId: ""
}; };
const mutations = { const mutations = {
updateSelTopicId(state, payload) {
state.selTopicId = payload.selTopicId;
},
save(state, payload) { save(state, payload) {
Object.keys(payload).forEach(key => { Object.keys(payload).forEach(key => {
state[key] = payload[key]; state[key] = payload[key];
@ -24,13 +28,59 @@ const mutations = {
}; };
const actions = { const actions = {
async init({ rootGetters, commit }, payload) { async init({ rootGetters, commit, dispatch }, payload) {
const { const {
roles: [role], roles: [role],
userId userId
} = rootGetters; } = rootGetters;
const result = await fetchTopicList({ role: "dietician", uid: 131 }); const result = await fetchTopicList({ role, uid: userId });
console.log({ result }); if (result.code === 200) {
const [defTopic] = result.rows;
dispatch("fetchTopicDetailActions", {
topicId: defTopic.topicId,
id: defTopic.id
});
commit("save", {
topicList: result.rows
});
}
},
async fetchTopicDetailActions({ commit }, payload) {
const { topicId, id } = payload;
commit("save", { selTopicId: topicId });
const result = await fetchTopicDetail({ topicId, id });
if (result.code === 200) {
commit("save", { detailData: result.data[0] });
}
},
async postTopicReplyActions(
{ commit, rootGetters, dispatch, state },
payload
) {
const {
roles: [role],
userId
} = rootGetters;
const { detailData, topicList } = state;
const params = { ...payload, fromRole: role, fromUid: userId };
const result = payload.commentId
? await postTopicReply(params)
: await postTopicComment(params);
if (result.code === 200) {
const tarTopic = topicList.find(
obj => obj.topicId === detailData.topicId
);
if (tarTopic) {
dispatch("fetchTopicDetailActions", {
topicId: tarTopic.topicId,
id: tarTopic.id
});
}
}
return result;
} }
}; };

View File

@ -5,7 +5,7 @@
</div> </div>
</template> </template>
<script> <script>
import MessageBrowser from "./messageBrowser"; import MessageBrowser from "./messageBrowser/index";
export default { export default {
data() { data() {
return {}; return {};

View File

@ -1,45 +0,0 @@
<template>
<div class="message_browser_wrapper">
<div class="topic_list">
</div>
<div class="topic_detail"></div>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const {
mapActions,
mapState,
mapMutations,
mapGetters,
} = createNamespacedHelpers("message");
export default {
data() {
return {};
},
created() {
this.init();
},
computed: {
...mapState([]),
},
methods: {
...mapActions(["init"]),
...mapMutations(["clean"]),
},
};
</script>
<style lang="scss" scoped>
.message_browser_wrapper {
display: flex;
.topic_list {
flex: 2;
}
.topic_detail {
flex: 3;
background: gray;
}
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<div class="topic_comment_item">
<div class="comment_avatar">
<el-avatar size="medium">{{ data.fromName.substr(-1) }}</el-avatar>
</div>
<div class="comment_content">
<div class="content_title">
{{ getContentTitle(data) }}
</div>
<div class="content_type">{{ data.content }}</div>
<div class="content_time">
{{ formatDate(data.createTime) }}
<div
v-if="data.fromUid !== userId.toString()"
class="reply_btn"
@click="handOnClick(data)"
>
回复
</div>
</div>
</div>
</div>
</template>
<script>
import dayjs from "dayjs";
import { mapGetters } from "vuex";
export default {
data() {
return {
roleDict: {
customer: "客户",
dietician: "主营养师",
after_sale: "售后营养师",
dietician_assistant: "营养师助理",
},
};
},
props: ["data"],
computed: {
...mapGetters(["userId"]),
},
methods: {
formatDate(date) {
return dayjs(date).format("MM-DD HH:mm");
},
handOnClick(data) {
this.$emit("click", data);
},
getContentTitle(data) {
return `${this.roleDict[data.fromRole]} - ${data.fromName}${
data.commentId
? ` to ${this.roleDict[data.toRole]} - ${data.toName}`
: ""
}`;
},
},
};
</script>
<style lang="scss" scoped>
.topic_comment_item {
margin: 12px;
display: flex;
.comment_avatar {
flex: 0 0 36px;
}
.comment_content {
flex: 1 0 0;
margin-left: 8px;
.content_title {
font-size: 14px;
color: #909399;
}
.content_type {
padding: 4px 0;
}
.content_time {
display: flex;
font-size: 14px;
color: #8c8c8c;
.reply_btn {
margin-left: 16px;
cursor: pointer;
}
}
}
}
</style>

View File

@ -0,0 +1,276 @@
<template>
<div class="message_browser_wrapper">
<div class="topic_list">
<div
v-for="topic in topicList"
:key="topic.topicId"
:class="`topic_item ${
selTopicId === topic.topicId ? 'topic_item_sel' : ''
}`"
@click="handleOnTopicClick(topic)"
>
<div class="topic_status topic_status_read" />
<div class="topic_item_content">
<div class="topic_content">{{ topic.content }}</div>
<div class="topic_user_name">by {{ topic.name }}</div>
</div>
<div class="topic_info">
<el-tag size="small">{{ topicTypeDict[topic.topicType] }}</el-tag>
<div class="topic_time">{{ formatDate(topic.createTime) }}</div>
</div>
</div>
</div>
<div class="topic_detail">
<div class="topic_detail_list">
<div class="topic_detail_title">
<div>{{ detailData.content }}</div>
<div class="content_time" :style="{ marginTop: '4px' }">
{{ formatDate(detailData.createTime) }}
<div class="reply_btn" @click="handleOnReplyTopic(detailData)">
回复
</div>
</div>
</div>
<div v-for="comment in detailData.comments" :key="comment.id">
<Comment :data="comment" @click="handleOnReplyComment" />
<div v-if="!!comment.replys">
<div
v-for="reply in comment.replys"
:key="reply.id"
class="comment_reply_item"
>
<Comment :data="reply" @click="handleOnReplyReply" />
</div>
</div>
</div>
</div>
<div class="topic_detail_reply">
<div
:style="{ marginBottom: '8px', fontSize: '12px', color: '#8c8c8c' }"
>
回复{{ replyTarget }}
</div>
<el-input type="textarea" :rows="3" v-model="replyContent" />
<div class="send_btn_zone">
<el-button
type="primary"
@click="handleOnReply"
:disabled="!this.replyContent"
size="mini"
>发送</el-button
>
</div>
</div>
</div>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
import Comment from "./Comment";
import dayjs from "dayjs";
const {
mapActions,
mapState,
mapMutations,
mapGetters,
} = createNamespacedHelpers("message");
export default {
data() {
return {
topicTypeDict: {
0: "建议",
1: "食谱",
2: "咨询",
},
replyTarget: "",
replyContent: "",
replyObj: {},
};
},
components: { Comment },
created() {
this.init();
},
computed: {
...mapState(["topicList", "selTopicId", "detailData"]),
},
methods: {
formatDate(date) {
return dayjs(date).format("MM-DD HH:mm");
},
handleOnTopicClick(data) {
this.replyTarget = "";
this.replyContent = "";
this.replyObj = {};
this.fetchTopicDetailActions({ topicId: data.topicId, id: data.id });
},
handleOnReplyTopic(data) {
this.replyTarget = "主题";
this.replyObj = {
toRole: data.role,
toUid: data.uid,
topicId: data.topicId,
img: [],
};
},
handleOnReplyComment(data) {
this.replyTarget = data.fromName;
this.replyObj = {
toRole: data.fromRole,
toUid: data.fromUid,
commentId: data.id,
img: [],
};
},
handleOnReplyReply(data) {
this.replyTarget = data.fromName;
this.replyObj = {
toRole: data.fromRole,
toUid: data.fromUid,
commentId: data.commentId,
replyId: data.id,
img: [],
};
},
handleOnReply() {
if (this.replyTarget) {
this.postTopicReplyActions({
...this.replyObj,
content: this.replyContent,
}).then((res) => {
this.$message.success(res.msg);
this.replyContent = "";
this.replyTarget = "";
this.replyObj = {};
});
} else {
this.$message.error("请选择回复对象");
}
},
...mapActions(["init", "fetchTopicDetailActions", "postTopicReplyActions"]),
...mapMutations(["clean"]),
},
};
</script>
<style lang="scss" scoped>
.message_browser_wrapper {
display: flex;
.topic_list {
flex: 2;
.topic_item {
display: flex;
padding: 8px 16px;
cursor: pointer;
&:hover {
background: #dedede;
}
.topic_status {
flex: 0 0 20px;
display: flex;
justify-content: center;
padding: 7px 0;
&::before {
content: "";
width: 8px;
display: block;
height: 8px;
border-radius: 50%;
}
}
.topic_status_read::before {
background: #909399;
}
.topic_status_unread::before {
background: #d96969;
}
.topic_item_content {
flex: 1 0 0;
.topic_content {
width: 260px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.5;
}
.topic_user_name {
color: #8c8c8c;
font-size: 14px;
margin-top: 8px;
}
}
.topic_info {
flex: 0 0 80px;
text-align: center;
.topic_time {
font-size: 14px;
margin-top: 8px;
color: #8c8c8c;
}
}
}
.topic_item_sel {
background: #dedede;
}
}
.topic_detail {
flex: 3;
background: #fafafa;
padding: 16px;
.topic_detail_list {
height: calc(100vh - 300px);
overflow: auto;
.content_time {
display: flex;
font-size: 14px;
color: #8c8c8c;
.reply_btn {
margin-left: 16px;
cursor: pointer;
}
}
.topic_detail_title {
}
.comment_reply_item {
margin-left: 24px;
.topic_comment_item {
margin: 8px;
/deep/.el-avatar {
transform: scale(0.8);
}
}
}
}
.topic_detail_reply {
height: 160px;
background: white;
padding: 16px;
.send_btn_zone {
margin-top: 8px;
text-align: right;
}
}
}
}
</style>