diff --git a/.DS_Store b/.DS_Store
index 20c255b4c..d343bcd39 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml b/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml
index 947d75729..688494714 100644
--- a/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml
+++ b/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml
@@ -9,11 +9,13 @@
         <result column="topic_id" property="topicId"/>
         <result column="topic_type" property="topicType"/>
         <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="img" property="img" typeHandler="com.stdiet.custom.typehandler.ArrayJsonHandler"/>
         <result column="create_time" property="createTime"/>
         <result column="update_time" property="updateTime"/>
+        <association column="{uid=uid,role=role}" property="name" select="selectUserInfo"/>
     </resultMap>
 
 
@@ -21,21 +23,9 @@
     <select id="selectSysServicesTopicByUserIdAndRole" parameterType="SysServicesTopic"
             resultMap="SysServicesTopicResult">
         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
         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
     </select>
 
@@ -92,10 +82,12 @@
 
     <select id="selectServicesTopicCommentByTopicId" resultMap="ServicesTopicCommentResult">
         select * from sys_services_topic_comment where topic_id = #{topic_id}
+        order by create_time asc
     </select>
 
     <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>
 
 
diff --git a/stdiet-ui/src/api/custom/message.js b/stdiet-ui/src/api/custom/message.js
index f453c905f..7b81d2223 100644
--- a/stdiet-ui/src/api/custom/message.js
+++ b/stdiet-ui/src/api/custom/message.js
@@ -20,7 +20,7 @@ export function postTopicReply(data) {
   return request({
     url: "/services/topic/reply",
     method: "post",
-    body: data
+    data
   });
 }
 
@@ -28,6 +28,6 @@ export function postTopicComment(data) {
   return request({
     url: "/services/topic/comment",
     method: "post",
-    body: data
+    data
   });
 }
diff --git a/stdiet-ui/src/store/getters.js b/stdiet-ui/src/store/getters.js
index 8550c09d2..ccedcde6d 100644
--- a/stdiet-ui/src/store/getters.js
+++ b/stdiet-ui/src/store/getters.js
@@ -9,8 +9,10 @@ const getters = {
   name: state => state.user.name,
   introduction: state => state.user.introduction,
   roles: state => state.user.roles,
+  // roles: state => ["dietician"],
   permissions: state => state.user.permissions,
   userId: state => state.user.userId,
+  // userId: state => 131,
   userRemark: state => state.user.remark,
   permission_routes: state => state.permission.routes,
   //
diff --git a/stdiet-ui/src/store/modules/message.js b/stdiet-ui/src/store/modules/message.js
index b65fc812f..bd01aa685 100644
--- a/stdiet-ui/src/store/modules/message.js
+++ b/stdiet-ui/src/store/modules/message.js
@@ -6,11 +6,15 @@ import {
 } from "@/api/custom/message";
 
 const oriState = {
-  topicList: undefined,
-  detailData: undefined
+  topicList: [],
+  detailData: {},
+  selTopicId: ""
 };
 
 const mutations = {
+  updateSelTopicId(state, payload) {
+    state.selTopicId = payload.selTopicId;
+  },
   save(state, payload) {
     Object.keys(payload).forEach(key => {
       state[key] = payload[key];
@@ -24,13 +28,59 @@ const mutations = {
 };
 
 const actions = {
-  async init({ rootGetters, commit }, payload) {
+  async init({ rootGetters, commit, dispatch }, payload) {
     const {
       roles: [role],
       userId
     } = rootGetters;
-    const result = await fetchTopicList({ role: "dietician", uid: 131 });
-    console.log({ result });
+    const result = await fetchTopicList({ role, uid: userId });
+    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;
   }
 };
 
diff --git a/stdiet-ui/src/views/custom/message/index.vue b/stdiet-ui/src/views/custom/message/index.vue
index 0df449440..a2239526f 100644
--- a/stdiet-ui/src/views/custom/message/index.vue
+++ b/stdiet-ui/src/views/custom/message/index.vue
@@ -5,7 +5,7 @@
   </div>
 </template>
 <script>
-import MessageBrowser from "./messageBrowser";
+import MessageBrowser from "./messageBrowser/index";
 export default {
   data() {
     return {};
diff --git a/stdiet-ui/src/views/custom/message/messageBrowser.vue b/stdiet-ui/src/views/custom/message/messageBrowser.vue
deleted file mode 100644
index bd861a855..000000000
--- a/stdiet-ui/src/views/custom/message/messageBrowser.vue
+++ /dev/null
@@ -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>
diff --git a/stdiet-ui/src/views/custom/message/messageBrowser/Comment.vue b/stdiet-ui/src/views/custom/message/messageBrowser/Comment.vue
new file mode 100644
index 000000000..db43c3cd8
--- /dev/null
+++ b/stdiet-ui/src/views/custom/message/messageBrowser/Comment.vue
@@ -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>
diff --git a/stdiet-ui/src/views/custom/message/messageBrowser/index.vue b/stdiet-ui/src/views/custom/message/messageBrowser/index.vue
new file mode 100644
index 000000000..3e0ba46ea
--- /dev/null
+++ b/stdiet-ui/src/views/custom/message/messageBrowser/index.vue
@@ -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>
diff --git a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue
index 0fd2adafa..2eb697373 100644
--- a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue
+++ b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue
@@ -59,8 +59,10 @@
               @click="handleOnResetCurrentDay"
               slot="reference"
             >
-              <div class="cus_name_hide" :id="`cus_name_${num}`">{{ name }}</div>
-              <div>{{ `第${numDay}天` }}</div>
+              <div class="cus_name_hide" :id="`cus_name_${num}`">
+                {{ name }}
+              </div>
+              <div :id="`day_num_${num}`">{{ `第${numDay}天` }}</div>
             </div>
           </el-popover>
         </template>
diff --git a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesHeaderCom/index.vue b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesHeaderCom/index.vue
index 73df723d5..3403859e2 100644
--- a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesHeaderCom/index.vue
+++ b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesHeaderCom/index.vue
@@ -24,7 +24,7 @@
           type="primary"
           style="margin-left: 12px"
           icon="el-icon-download"
-          @click="handleOnExportImg"
+          @click="handleOnExportStartNumConfig"
           v-loading="downloading"
           :disabled="downloading"
         >
@@ -126,6 +126,7 @@ export default {
         { value: 16, label: "16" },
         { value: 18, label: "18" },
       ],
+      oriStartNum: 0,
     };
   },
   updated() {},
@@ -174,7 +175,7 @@ export default {
         if (response.code === 200) {
           const { planId, id } = response.data;
           this.saveRecipes({
-            cusId: 0,
+            cusId: 0, // 模板特有
             planId,
             reviewStatus: 2, // 已审核
             callback: () => {
@@ -215,7 +216,18 @@ export default {
           return "info";
       }
     },
-    handleOnExportImg() {
+    handleOnExportStartNumConfig() {
+      this.oriStartNum = this.recipesData[0].numDay;
+      this.$prompt("食谱开始天数", "导出图片", {
+        confirmButtonText: "确定",
+        inputValue: this.oriStartNum,
+        inputPattern: /^[1-9]\d*$/,
+        inputErrorMessage: "请输入正整数",
+      }).then(({ value }) => {
+        this.handleOnExportImg(parseInt(value));
+      });
+    },
+    handleOnExportImg(startNum) {
       this.downloading = true;
       this.$nextTick(() => {
         const centerContentDom = document.getElementById("center_content");
@@ -234,6 +246,10 @@ export default {
           if (tmpElm) {
             tmpElm.classList = [];
           }
+          const tmpNum = document.getElementById(`day_num_${idx}`);
+          if (tmpNum) {
+            tmpNum.innerText = `第${startNum + idx}天`;
+          }
         });
         recipesDom.style.overflow = "visible";
         html2canvans(recipesDom, {
@@ -241,10 +257,11 @@ export default {
           height: recipesDom.scrollHeight,
         }).then((canvas) => {
           const { name } = this.healthyData;
-          const startNum = this.recipesData[0].numDay;
-          const endNum = this.recipesData[this.recipesData.length - 1].numDay;
+          // const startNum = this.recipesData[0].numDay;
+          // const endNum = this.recipesData[this.recipesData.length - 1].numDay;
+          const endNum = startNum + this.recipesData.length - 1;
           const link = document.createElement("a");
-          link.download = `${name}_第${startNum}至${endNum}天.jpeg`;
+          link.download = `${name || "匿名"}_第${startNum}至${endNum}天.jpeg`;
           link.href = canvas.toDataURL();
           link.click();
 
@@ -256,6 +273,10 @@ export default {
             if (tmpElm) {
               tmpElm.classList = ["cus_name_hide"];
             }
+            const tmpNum = document.getElementById(`day_num_${idx}`);
+            if (tmpNum) {
+              tmpNum.innerText = `第${this.oriStartNum + idx}天`;
+            }
           });
 
           this.downloading = false;