ServiceContents.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <template>
  2. <div class="service-contents admin-page">
  3. <el-card>
  4. <div slot="header" class="admin-page-header">
  5. <span class="admin-page-title">服务内容管理</span>
  6. <div class="admin-page-actions">
  7. <el-input v-model="keyword" placeholder="搜索内容..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
  8. <el-button type="primary" size="small" @click="handleCreate">+ 添加内容</el-button>
  9. </div>
  10. </div>
  11. <el-form :inline="true">
  12. <el-form-item label="服务类型">
  13. <el-select v-model="query.typeId" placeholder="全部" clearable @change="handleQuery">
  14. <el-option v-for="t in serviceTypes" :key="t.id" :label="t.name" :value="t.id"></el-option>
  15. </el-select>
  16. </el-form-item>
  17. <el-form-item label="状态">
  18. <el-select v-model="query.status" placeholder="全部" clearable @change="handleQuery">
  19. <el-option label="启用" value="ACTIVE"></el-option>
  20. <el-option label="停用" value="DISABLED"></el-option>
  21. </el-select>
  22. </el-form-item>
  23. <el-form-item>
  24. <el-button @click="handleQuery">查询</el-button>
  25. </el-form-item>
  26. </el-form>
  27. <div class="table-scroll-wrap-sm">
  28. <el-table :data="filteredData" stripe>
  29. <el-table-column prop="id" label="ID" width="80"></el-table-column>
  30. <el-table-column prop="title" label="标题"></el-table-column>
  31. <el-table-column prop="typeId" label="类型" width="120">
  32. <template slot-scope="scope">
  33. {{ getTypeName(scope.row.typeId) }}
  34. </template>
  35. </el-table-column>
  36. <el-table-column prop="duration" label="时长(分钟)" width="100"></el-table-column>
  37. <el-table-column prop="ageRange" label="适用年龄" width="100"></el-table-column>
  38. <el-table-column prop="price" label="参考价格" width="100">
  39. <template slot-scope="scope">{{ formatPriceWithSymbol(scope.row.price) }}</template>
  40. </el-table-column>
  41. <el-table-column prop="status" label="状态" width="100">
  42. <template slot-scope="scope">
  43. <el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'danger'">
  44. {{ scope.row.status === 'ACTIVE' ? '启用' : '停用' }}
  45. </el-tag>
  46. </template>
  47. </el-table-column>
  48. <el-table-column label="操作" width="200" fixed="right">
  49. <template slot-scope="{ row }">
  50. <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
  51. <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
  52. <el-button size="mini">
  53. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  54. </el-button>
  55. <el-dropdown-menu slot="dropdown">
  56. <el-dropdown-item command="preview" icon="el-icon-view">预览</el-dropdown-item>
  57. <el-dropdown-item :command="row.status === 'ACTIVE' ? 'disable' : 'enable'" icon="el-icon-switch-button">{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-dropdown-item>
  58. </el-dropdown-menu>
  59. </el-dropdown>
  60. </template>
  61. </el-table-column>
  62. </el-table>
  63. </div>
  64. </el-card>
  65. <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑服务内容' : '添加服务内容'" width="800px">
  66. <el-form :model="form" label-width="100px">
  67. <el-form-item label="服务类型">
  68. <el-select v-model="form.typeId" placeholder="请选择服务类型">
  69. <el-option v-for="t in serviceTypes" :key="t.id" :label="t.name" :value="t.id"></el-option>
  70. </el-select>
  71. </el-form-item>
  72. <el-form-item label="标题">
  73. <el-input v-model="form.title"></el-input>
  74. </el-form-item>
  75. <el-form-item label="服务详情">
  76. <div class="rich-editor">
  77. <div class="editor-toolbar">
  78. <el-button size="small" @click="insertTag('b')">B</el-button>
  79. <el-button size="small" @click="insertTag('i')">I</el-button>
  80. <el-button size="small" @click="insertTag('u')">U</el-button>
  81. <el-button size="small" @click="insertTag('h3')">标题</el-button>
  82. <el-button size="small" @click="insertTag('ul')">列表</el-button>
  83. <el-button size="small" @click="insertTag('img')">图片</el-button>
  84. </div>
  85. <el-input
  86. type="textarea"
  87. v-model="form.content"
  88. :rows="10"
  89. placeholder="请输入服务详情,支持HTML标签"
  90. ></el-input>
  91. </div>
  92. </el-form-item>
  93. <el-form-item label="建议时长(分钟)">
  94. <el-input-number v-model="form.duration" :min="0"></el-input-number>
  95. </el-form-item>
  96. <el-form-item label="适用年龄">
  97. <el-input v-model="form.ageRange" placeholder="如: 3-12岁"></el-input>
  98. </el-form-item>
  99. <el-form-item label="参考价格(元)">
  100. <el-input-number v-model="form.price" :min="0" :precision="2"></el-input-number>
  101. </el-form-item>
  102. </el-form>
  103. <div slot="footer">
  104. <el-button @click="dialogVisible = false">取消</el-button>
  105. <el-button type="primary" @click="handleSubmit">保存</el-button>
  106. </div>
  107. </el-dialog>
  108. <el-dialog :visible.sync="previewVisible" title="服务内容预览" width="600px">
  109. <div class="preview-content" v-html="previewContent"></div>
  110. </el-dialog>
  111. </div>
  112. </template>
  113. <script>
  114. import { getServiceContents, createServiceContent, updateServiceContent } from '@/api/adminService'
  115. import { getServiceTypes } from '@/api/adminService'
  116. export default {
  117. name: 'ServiceContents',
  118. data() {
  119. return {
  120. contents: [],
  121. keyword: '',
  122. serviceTypes: [],
  123. query: {
  124. typeId: null,
  125. status: null
  126. },
  127. dialogVisible: false,
  128. previewVisible: false,
  129. previewContent: '',
  130. isEdit: false,
  131. form: {
  132. id: null,
  133. typeId: null,
  134. title: '',
  135. content: '',
  136. duration: 0,
  137. ageRange: '',
  138. price: 0,
  139. status: 'ACTIVE'
  140. }
  141. }
  142. },
  143. created() {
  144. this.loadTypes()
  145. this.loadData()
  146. },
  147. computed: {
  148. filteredData() {
  149. if (!this.keyword) return this.contents
  150. var kw = this.keyword.toLowerCase()
  151. return this.contents.filter(function(item) {
  152. return item.title && item.title.toLowerCase().indexOf(kw) !== -1
  153. })
  154. }
  155. },
  156. methods: {
  157. async loadTypes() {
  158. try {
  159. const res = await getServiceTypes()
  160. if (res.data) {
  161. this.serviceTypes = res.data
  162. }
  163. } catch (e) {
  164. this.serviceTypes = [
  165. { id: 1, name: 'DAN测评' },
  166. { id: 2, name: '专属咨询' },
  167. { id: 3, name: '成长报告' },
  168. { id: 4, name: '家庭培训' }
  169. ]
  170. }
  171. },
  172. async loadData() {
  173. try {
  174. const res = await getServiceContents(this.query)
  175. if (res.data) {
  176. this.contents = res.data
  177. }
  178. } catch (e) {
  179. this.contents = [
  180. { id: 1, typeId: 1, title: '1对1 DAN测评', content: '<p>DAN测评是...</p>', duration: 60, ageRange: '3-12岁', price: 800, status: 'ACTIVE' },
  181. { id: 2, typeId: 1, title: 'DAN综合评估', content: '<p>综合评估...</p>', duration: 90, ageRange: '3-12岁', price: 1200, status: 'ACTIVE' },
  182. { id: 3, typeId: 2, title: '专属咨询', content: '<p>咨询服务...</p>', duration: 30, ageRange: '3-18岁', price: 580, status: 'ACTIVE' }
  183. ]
  184. }
  185. },
  186. getTypeName(typeId) {
  187. const type = this.serviceTypes.find(t => t.id === typeId)
  188. return type ? type.name : '-'
  189. },
  190. handleSearch() {},
  191. handleQuery() {
  192. this.loadData()
  193. },
  194. handleCreate() {
  195. this.isEdit = false
  196. this.form = {
  197. id: null,
  198. typeId: null,
  199. title: '',
  200. content: '',
  201. duration: 0,
  202. ageRange: '',
  203. price: 0,
  204. status: 'ACTIVE'
  205. }
  206. this.dialogVisible = true
  207. },
  208. handleEdit(row) {
  209. this.isEdit = true
  210. this.form = { ...row }
  211. this.dialogVisible = true
  212. },
  213. handlePreview(row) {
  214. this.previewContent = row.content
  215. this.previewVisible = true
  216. },
  217. async toggleStatus(row) {
  218. row.status = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
  219. try {
  220. await updateServiceContent(row.id, row)
  221. this.$message.success('状态已更新')
  222. } catch (e) {
  223. row.status = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
  224. this.$message.error('更新失败')
  225. }
  226. },
  227. async handleSubmit() {
  228. if (!this.form.typeId || !this.form.title) {
  229. this.$message.warning('请填写完整信息')
  230. return
  231. }
  232. try {
  233. if (this.isEdit) {
  234. await updateServiceContent(this.form.id, this.form)
  235. } else {
  236. await createServiceContent(this.form)
  237. }
  238. this.$message.success('保存成功')
  239. this.dialogVisible = false
  240. this.loadData()
  241. } catch (e) {
  242. this.$message.error('保存失败')
  243. }
  244. },
  245. insertTag(tag) {
  246. const tags = {
  247. b: '<strong></strong>',
  248. i: '<em></em>',
  249. u: '<u></u>',
  250. h3: '<h3></h3>',
  251. ul: '<ul><li></li></ul>',
  252. img: '<img src="" alt=""/>'
  253. }
  254. this.form.content += tags[tag]
  255. },
  256. handleActionCmd(row, cmd) {
  257. switch (cmd) {
  258. case 'preview':
  259. this.handlePreview(row)
  260. break
  261. case 'disable':
  262. row.status = 'DISABLED'
  263. this.toggleStatus(row)
  264. break
  265. case 'enable':
  266. row.status = 'ACTIVE'
  267. this.toggleStatus(row)
  268. break
  269. }
  270. }
  271. }
  272. }
  273. </script>
  274. <style scoped>
  275. .service-contents {
  276. padding: 20px;
  277. }
  278. .rich-editor {
  279. border: 1px solid #dcdfe6;
  280. border-radius: 4px;
  281. }
  282. .editor-toolbar {
  283. padding: 10px;
  284. border-bottom: 1px solid #dcdfe6;
  285. background: #f5f7fa;
  286. }
  287. .preview-content {
  288. padding: 20px;
  289. line-height: 1.8;
  290. }
  291. .preview-content h3 {
  292. margin: 16px 0 8px;
  293. }
  294. .preview-content ul {
  295. padding-left: 20px;
  296. }
  297. .preview-content img {
  298. max-width: 100%;
  299. }
  300. </style>