SuccessCaseList.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <template>
  2. <div class="success-case-list-container">
  3. <h2 class="page-title">成功案例管理</h2>
  4. <el-card shadow="never" class="toolbar-card">
  5. <el-row :gutter="20">
  6. <el-col :span="18">
  7. <el-space wrap>
  8. <el-button type="primary" icon="Plus" @click="$router.push('/success-case/create')">新增案例</el-button>
  9. <el-button icon="Refresh" @click="loadList">刷新</el-button>
  10. </el-space>
  11. </el-col>
  12. <el-col :span="6">
  13. <!-- 预留搜索区域 -->
  14. </el-col>
  15. </el-row>
  16. </el-card>
  17. <el-card shadow="never" class="table-card">
  18. <el-table v-loading="loading" :data="list" stripe>
  19. <el-table-column type="index" label="序号" width="60" :index="indexMethod" />
  20. <el-table-column label="案例编号" width="140">
  21. <template #default="{ row }">
  22. {{ row.caseNo || row.case_no || '-' }}
  23. </template>
  24. </el-table-column>
  25. <el-table-column prop="maleUserNickname" label="男方昵称" width="120" />
  26. <el-table-column prop="femaleUserNickname" label="女方昵称" width="120" />
  27. <el-table-column prop="quote" label="案例引言" min-width="200" show-overflow-tooltip />
  28. <el-table-column prop="marriageDate" label="结婚日期" width="120" />
  29. <el-table-column prop="matchmakerName" label="红娘" width="100" />
  30. <el-table-column prop="caseStatus" label="状态" width="100">
  31. <template #default="{ row }">
  32. <el-switch v-model="row.caseStatus" :active-value="1" :inactive-value="0" @change="handleStatusChange(row)" />
  33. </template>
  34. </el-table-column>
  35. <el-table-column prop="createdAt" label="创建时间" width="180" />
  36. <el-table-column label="操作" width="200" fixed="right">
  37. <template #default="{ row }">
  38. <el-button type="info" size="small" link @click="handleViewDetail(row)">查看</el-button>
  39. <el-button type="primary" size="small" link @click="$router.push(`/success-case/edit/${row.caseId || row.case_id}`)">编辑</el-button>
  40. <el-button type="danger" size="small" link @click="handleDelete(row)">删除</el-button>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. <div class="pagination-container">
  45. <el-pagination
  46. v-model:current-page="currentPage"
  47. v-model:page-size="pageSize"
  48. :total="total"
  49. :page-sizes="[10, 20, 50, 100]"
  50. layout="total, sizes, prev, pager, next, jumper"
  51. @size-change="loadList"
  52. @current-change="loadList"
  53. />
  54. </div>
  55. </el-card>
  56. <!-- 案例详情对话框 -->
  57. <el-dialog v-model="detailVisible" title="案例详情" width="900px" :close-on-click-modal="false">
  58. <div v-loading="detailLoading" class="detail-container">
  59. <el-descriptions :column="2" border>
  60. <el-descriptions-item label="案例编号" :span="2">
  61. <el-tag type="primary">{{ detailData.caseNo }}</el-tag>
  62. </el-descriptions-item>
  63. <el-descriptions-item label="男方昵称">{{ detailData.maleUserNickname }}</el-descriptions-item>
  64. <el-descriptions-item label="女方昵称">{{ detailData.femaleUserNickname }}</el-descriptions-item>
  65. <el-descriptions-item label="结婚日期">{{ detailData.marriageDate }}</el-descriptions-item>
  66. <el-descriptions-item label="红娘">{{ detailData.matchmakerName }}</el-descriptions-item>
  67. <el-descriptions-item label="状态" :span="2">
  68. <el-tag :type="detailData.caseStatus === 1 ? 'success' : 'info'">
  69. {{ detailData.caseStatus === 1 ? '展示中' : '隐藏' }}
  70. </el-tag>
  71. </el-descriptions-item>
  72. <el-descriptions-item label="案例引言" :span="2">
  73. {{ detailData.quote || '暂无' }}
  74. </el-descriptions-item>
  75. <el-descriptions-item label="案例故事" :span="2">
  76. <div class="story-content">{{ detailData.story || '暂无' }}</div>
  77. </el-descriptions-item>
  78. <el-descriptions-item label="创建时间" :span="2">
  79. {{ detailData.createdAt }}
  80. </el-descriptions-item>
  81. </el-descriptions>
  82. <!-- 时间线 -->
  83. <div v-if="detailData.timeline && detailData.timeline.length > 0" class="timeline-section">
  84. <h3 class="section-title">案例时间线</h3>
  85. <el-timeline>
  86. <el-timeline-item
  87. v-for="item in detailData.timeline"
  88. :key="item.timelineId"
  89. :timestamp="item.timelineDate"
  90. placement="top"
  91. >
  92. <el-card>
  93. <h4>{{ item.timelineTitle }}</h4>
  94. <p>{{ item.timelineDesc }}</p>
  95. </el-card>
  96. </el-timeline-item>
  97. </el-timeline>
  98. </div>
  99. </div>
  100. <template #footer>
  101. <el-button @click="detailVisible = false">关闭</el-button>
  102. <el-button type="primary" @click="handleEditFromDetail">编辑此案例</el-button>
  103. </template>
  104. </el-dialog>
  105. </div>
  106. </template>
  107. <script setup>
  108. import { ref, onMounted, computed } from 'vue'
  109. import { useRouter } from 'vue-router'
  110. import { ElMessage, ElMessageBox } from 'element-plus'
  111. import request from '@/utils/request'
  112. import { API_ENDPOINTS } from '@/config/api'
  113. const router = useRouter()
  114. const loading = ref(false)
  115. const currentPage = ref(1)
  116. const pageSize = ref(10)
  117. const total = ref(0)
  118. const list = ref([])
  119. // 详情对话框
  120. const detailVisible = ref(false)
  121. const detailLoading = ref(false)
  122. const detailData = ref({})
  123. // 序号计算方法
  124. const indexMethod = (index) => {
  125. return (currentPage.value - 1) * pageSize.value + index + 1
  126. }
  127. const loadList = async () => {
  128. loading.value = true
  129. try {
  130. const response = await request.get(API_ENDPOINTS.CASE_LIST, {
  131. params: {
  132. page: currentPage.value,
  133. pageSize: pageSize.value
  134. }
  135. })
  136. console.log('案例列表响应:', response)
  137. if (response.code === 200) {
  138. let dataList = response.data.list || response.data || []
  139. // 兼容处理:如果后端返回的是下划线格式,转换为驼峰格式
  140. list.value = dataList.map(item => ({
  141. ...item,
  142. caseId: item.caseId || item.case_id,
  143. caseNo: item.caseNo || item.case_no,
  144. caseStatus: item.caseStatus !== undefined ? item.caseStatus : item.case_status,
  145. maleUserNickname: item.maleUserNickname || item.male_user_nickname,
  146. femaleUserNickname: item.femaleUserNickname || item.female_user_nickname,
  147. marriageDate: item.marriageDate || item.marriage_date,
  148. matchmakerName: item.matchmakerName || item.matchmaker_name,
  149. quote: item.quote,
  150. createdAt: item.createdAt || item.created_at
  151. }))
  152. total.value = response.data.total || list.value.length
  153. console.log('案例列表数据:', list.value)
  154. console.log('总数:', total.value)
  155. } else {
  156. ElMessage.error(response.message || '加载失败')
  157. }
  158. } catch (error) {
  159. console.error('加载失败:', error)
  160. ElMessage.error('加载失败:' + (error.message || '网络错误'))
  161. } finally {
  162. loading.value = false
  163. }
  164. }
  165. const handleStatusChange = async (row) => {
  166. // 兼容处理:支持 caseId 和 case_id 两种格式
  167. const caseId = row.caseId || row.case_id
  168. if (!row || !caseId) {
  169. ElMessage.error('案例ID不存在,无法更新状态')
  170. return
  171. }
  172. try {
  173. // 获取当前状态值(兼容驼峰和下划线格式)
  174. const currentStatus = row.caseStatus !== undefined ? row.caseStatus : row.case_status
  175. // 由于实体类添加了 @JsonProperty 注解,发送驼峰格式
  176. const response = await request.put(`${API_ENDPOINTS.CASE_UPDATE}/${caseId}`, {
  177. caseStatus: currentStatus
  178. })
  179. if (response.code === 200) {
  180. ElMessage.success('状态更新成功')
  181. } else {
  182. // 回滚状态
  183. row.caseStatus = currentStatus === 1 ? 0 : 1
  184. ElMessage.error(response.message || '状态更新失败')
  185. }
  186. } catch (error) {
  187. console.error('状态更新失败:', error)
  188. // 回滚状态
  189. const currentStatus = row.caseStatus !== undefined ? row.caseStatus : row.case_status
  190. row.caseStatus = currentStatus === 1 ? 0 : 1
  191. ElMessage.error('状态更新失败')
  192. }
  193. }
  194. const handleViewDetail = async (row) => {
  195. detailVisible.value = true
  196. detailLoading.value = true
  197. detailData.value = {}
  198. try {
  199. // 兼容处理:支持 caseId 和 case_id 两种格式
  200. const caseId = row.caseId || row.case_id
  201. if (!caseId) {
  202. ElMessage.error('案例ID不存在')
  203. detailVisible.value = false
  204. return
  205. }
  206. const response = await request.get(`${API_ENDPOINTS.CASE_DETAIL}/${caseId}`)
  207. console.log('案例详情响应:', response)
  208. if (response.code === 200) {
  209. const data = response.data || {}
  210. // 兼容处理:如果后端返回的是下划线格式,转换为驼峰格式
  211. detailData.value = {
  212. ...data,
  213. caseId: data.caseId || data.case_id,
  214. caseNo: data.caseNo || data.case_no,
  215. caseStatus: data.caseStatus !== undefined ? data.caseStatus : data.case_status,
  216. maleUserNickname: data.maleUserNickname || data.male_user_nickname,
  217. femaleUserNickname: data.femaleUserNickname || data.female_user_nickname,
  218. marriageDate: data.marriageDate || data.marriage_date,
  219. matchmakerName: data.matchmakerName || data.matchmaker_name,
  220. quote: data.quote,
  221. story: data.story,
  222. createdAt: data.createdAt || data.created_at
  223. }
  224. console.log('案例详情数据:', detailData.value)
  225. } else {
  226. ElMessage.error(response.message || '加载详情失败')
  227. detailVisible.value = false
  228. }
  229. } catch (error) {
  230. console.error('加载详情失败:', error)
  231. ElMessage.error('加载详情失败:' + (error.message || '网络错误'))
  232. detailVisible.value = false
  233. } finally {
  234. detailLoading.value = false
  235. }
  236. }
  237. const handleEditFromDetail = () => {
  238. if (detailData.value.caseId) {
  239. detailVisible.value = false
  240. router.push(`/success-case/edit/${detailData.value.caseId}`)
  241. }
  242. }
  243. const handleDelete = async (row) => {
  244. try {
  245. // 兼容处理:支持 caseNo 和 case_no 两种格式
  246. const caseNo = row.caseNo || row.case_no || '该案例'
  247. await ElMessageBox.confirm(
  248. `确定要删除案例"${caseNo}"吗?删除后将无法恢复!`,
  249. '删除确认',
  250. {
  251. type: 'warning',
  252. confirmButtonText: '确定删除',
  253. cancelButtonText: '取消'
  254. }
  255. )
  256. // 兼容处理:支持 caseId 和 case_id 两种格式
  257. const caseId = row.caseId || row.case_id
  258. if (!caseId) {
  259. ElMessage.error('案例ID不存在')
  260. return
  261. }
  262. const response = await request.delete(`${API_ENDPOINTS.CASE_DELETE}/${caseId}`)
  263. if (response.code === 200) {
  264. ElMessage.success('删除成功')
  265. // 如果当前页只有一条数据且不是第一页,则返回上一页
  266. if (list.value.length === 1 && currentPage.value > 1) {
  267. currentPage.value--
  268. }
  269. loadList()
  270. } else {
  271. ElMessage.error(response.message || '删除失败')
  272. }
  273. } catch (error) {
  274. if (error !== 'cancel') {
  275. console.error('删除失败:', error)
  276. ElMessage.error('删除失败')
  277. }
  278. }
  279. }
  280. onMounted(() => {
  281. loadList()
  282. })
  283. </script>
  284. <style scoped>
  285. .success-case-list-container {
  286. padding: 20px;
  287. }
  288. .page-title {
  289. margin: 0 0 20px 0;
  290. font-size: 24px;
  291. font-weight: 600;
  292. color: #303133;
  293. }
  294. .toolbar-card {
  295. margin-bottom: 20px;
  296. }
  297. .table-card {
  298. margin-bottom: 20px;
  299. }
  300. .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }
  301. /* 详情对话框样式 */
  302. .detail-container {
  303. max-height: 70vh;
  304. overflow-y: auto;
  305. }
  306. .story-content {
  307. line-height: 1.8;
  308. white-space: pre-wrap;
  309. word-break: break-all;
  310. }
  311. .section-title {
  312. font-size: 16px;
  313. font-weight: bold;
  314. color: #333;
  315. margin: 20px 0 15px;
  316. padding-bottom: 10px;
  317. border-bottom: 2px solid #409eff;
  318. }
  319. /* 时间线样式 */
  320. .timeline-section {
  321. margin-top: 20px;
  322. }
  323. .timeline-section h4 {
  324. margin: 0 0 8px;
  325. font-size: 14px;
  326. color: #333;
  327. }
  328. .timeline-section p {
  329. margin: 0;
  330. font-size: 13px;
  331. color: #666;
  332. line-height: 1.6;
  333. }
  334. </style>