TeacherConsult.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <div class="teacher-consult-container page-container">
  3. <el-card>
  4. <div slot="header">
  5. <span>家长咨询管理</span>
  6. <el-button style="float: right" size="small" @click="loadMessages" :loading="loading">刷新</el-button>
  7. </div>
  8. <!-- 搜索筛选 -->
  9. <el-form :inline="true" style="margin-bottom: 15px">
  10. <el-form-item label="状态">
  11. <el-select v-model="searchForm.status" placeholder="全部" clearable @change="loadMessages">
  12. <el-option label="待回复" value="pending"></el-option>
  13. <el-option label="已回复" value="replied"></el-option>
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item>
  17. <el-button type="primary" @click="loadMessages">搜索</el-button>
  18. </el-form-item>
  19. </el-form>
  20. <!-- 咨询列表 -->
  21. <el-table :max-height="tableHeight" :data="messages" border v-loading="loading">
  22. <el-table-column prop="id" label="ID" width="60">
  23. <template slot="header">
  24. <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
  25. </template>
  26. </el-table-column>
  27. <el-table-column prop="familyId" label="家庭ID" width="80">
  28. <template slot="header">
  29. <span class="sort-header" @click.stop="onSortClick('familyId', $event)">家庭ID<span v-if="sortIndex('familyId') !== null" class="sort-index" :class="sortClass('familyId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyId') }}</span>{{ sortIcon('familyId') }}</span>
  30. </template>
  31. </el-table-column>
  32. <el-table-column prop="content" label="咨询内容" min-width="200">
  33. <template slot="header">
  34. <span class="sort-header" @click.stop="onSortClick('content', $event)">咨询内容<span v-if="sortIndex('content') !== null" class="sort-index" :class="sortClass('content') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('content') }}</span>{{ sortIcon('content') }}</span>
  35. </template>
  36. </el-table-column>
  37. <el-table-column prop="status" label="状态" width="80">
  38. <template slot-scope="{ row }">
  39. <el-tag :type="row.status === 'pending' ? 'warning' : 'success'" size="small">
  40. {{ row.status === 'pending' ? '待回复' : '已回复' }}
  41. </el-tag>
  42. </template>
  43. <template slot="header">
  44. <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
  45. </template>
  46. </el-table-column>
  47. <el-table-column prop="createdAt" label="咨询时间" width="160">
  48. <template slot-scope="{ row }">{{ formatDate(row.createdAt) }}</template>
  49. <template slot="header">
  50. <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">咨询时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
  51. </template>
  52. </el-table-column>
  53. <el-table-column label="操作" width="150" fixed="right">
  54. <template slot-scope="{ row }">
  55. <el-button type="text" size="small" @click="showReplyDialog(row)">
  56. {{ row.status === 'pending' ? '回复' : '查看' }}
  57. </el-button>
  58. </template>
  59. </el-table-column>
  60. </el-table>
  61. <!-- 分页 -->
  62. <el-pagination
  63. v-if="total > 0"
  64. style="margin-top: 20px; text-align: right"
  65. :current-page="pagination.page"
  66. :page-size="pagination.size"
  67. :total="total"
  68. layout="total, prev, pager, next"
  69. @current-change="handlePageChange"
  70. />
  71. </el-card>
  72. <!-- 回复弹窗 -->
  73. <el-dialog :title="replyDialogTitle" :visible.sync="replyDialogVisible" width="500px">
  74. <div v-if="currentMessage">
  75. <div class="message-content">
  76. <div class="label">咨询内容:</div>
  77. <div class="content">{{ currentMessage.content }}</div>
  78. </div>
  79. <el-divider></el-divider>
  80. <div v-if="currentMessage.status === 'replied'" class="reply-content">
  81. <div class="label">我的回复:</div>
  82. <div class="content">{{ currentMessage.replyContent }}</div>
  83. </div>
  84. <el-form v-else>
  85. <el-form-item label="回复内容">
  86. <el-input type="textarea" v-model="replyForm.replyContent" rows="4" placeholder="请输入回复内容"></el-input>
  87. </el-form-item>
  88. </el-form>
  89. </div>
  90. <div slot="footer">
  91. <el-button @click="replyDialogVisible = false">关闭</el-button>
  92. <el-button v-if="currentMessage && currentMessage.status === 'pending'" type="primary" @click="submitReply" :loading="replyLoading">提交回复</el-button>
  93. </div>
  94. </el-dialog>
  95. </div>
  96. </template>
  97. <script>
  98. import SortMixin from '@/mixins/SortMixin'
  99. import { getConsultMessages, replyConsultMessage } from '@/api/teacherMessage'
  100. export default {
  101. name: 'TeacherConsult',
  102. mixins: [SortMixin],
  103. data() {
  104. return {
  105. loading: false,
  106. replyLoading: false,
  107. messages: [],
  108. total: 0,
  109. sortableColumns: { id: 'ID', familyId: '家庭ID', content: '咨询内容', status: '状态', createdAt: '咨询时间' },
  110. searchForm: {
  111. status: ''
  112. },
  113. pagination: {
  114. page: 1,
  115. size: 10
  116. },
  117. replyDialogVisible: false,
  118. currentMessage: null,
  119. replyForm: {
  120. replyContent: ''
  121. }
  122. }
  123. },
  124. computed: {
  125. tableHeight() {
  126. return window.innerHeight - 340
  127. },
  128. replyDialogTitle() {
  129. if (!this.currentMessage) return '回复咨询'
  130. return this.currentMessage.status === 'pending' ? '回复咨询' : '查看回复'
  131. }
  132. },
  133. mounted() {
  134. this.loadMessages()
  135. },
  136. methods: {
  137. loadList() {
  138. this.loadMessages()
  139. },
  140. async loadMessages() {
  141. this.loading = true
  142. try {
  143. const res = await getConsultMessages(this.pagination.page, this.pagination.size, this.searchForm.status, this.sortState.length > 0 ? this.sortState : null)
  144. this.messages = res.data.records || []
  145. this.total = res.data.total || 0
  146. } catch (e) {
  147. this.$message.error('加载失败')
  148. } finally {
  149. this.loading = false
  150. }
  151. },
  152. handlePageChange(page) {
  153. this.pagination.page = page
  154. this.loadMessages()
  155. },
  156. showReplyDialog(row) {
  157. this.currentMessage = row
  158. this.replyForm.replyContent = ''
  159. this.replyDialogVisible = true
  160. },
  161. async submitReply() {
  162. if (!this.replyForm.replyContent.trim()) {
  163. this.$message.warning('请输入回复内容')
  164. return
  165. }
  166. this.replyLoading = true
  167. try {
  168. await replyConsultMessage(this.currentMessage.id, this.replyForm.replyContent)
  169. this.$message.success('回复成功')
  170. this.replyDialogVisible = false
  171. this.loadMessages()
  172. } catch (e) {
  173. this.$message.error('回复失败')
  174. } finally {
  175. this.replyLoading = false
  176. }
  177. },
  178. formatDate(date) {
  179. if (!date) return '-'
  180. return new Date(date).toLocaleString()
  181. }
  182. }
  183. }
  184. </script>
  185. <style scoped>
  186. .teacher-consult-container { padding: 20px; }
  187. .message-content, .reply-content {
  188. margin-bottom: 15px;
  189. }
  190. .message-content .label, .reply-content .label {
  191. font-weight: bold;
  192. margin-bottom: 8px;
  193. color: #606266;
  194. }
  195. .message-content .content, .reply-content .content {
  196. padding: 10px;
  197. background: #f5f7fa;
  198. border-radius: 4px;
  199. }
  200. .sort-header {
  201. cursor: pointer;
  202. user-select: none;
  203. font-weight: 500;
  204. font-size: 12px;
  205. white-space: nowrap;
  206. }
  207. .sort-header:hover {
  208. color: #409eff;
  209. }
  210. </style>