Tasks.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <template>
  2. <div class="page-container">
  3. <el-card>
  4. <div slot="header">
  5. <span>任务管理</span>
  6. <el-button
  7. type="primary"
  8. size="small"
  9. style="float: right"
  10. @click="handleRefresh"
  11. :loading="loading"
  12. >
  13. 刷新
  14. </el-button>
  15. </div>
  16. <!-- 搜索筛选 -->
  17. <el-form :inline="true" :model="searchForm" style="margin-bottom: 20px">
  18. <el-form-item label="任务标题">
  19. <el-input
  20. v-model="searchForm.title"
  21. placeholder="请输入任务标题"
  22. clearable
  23. @keyup.enter.native="handleSearch"
  24. ></el-input>
  25. </el-form-item>
  26. <el-form-item label="状态">
  27. <el-select v-model="searchForm.status" placeholder="全部状态" clearable @change="handleSearch">
  28. <el-option label="进行中" value="pending"></el-option>
  29. <el-option label="已完成" value="completed"></el-option>
  30. <el-option label="已逾期" value="overdue"></el-option>
  31. <el-option label="已取消" value="cancelled"></el-option>
  32. </el-select>
  33. </el-form-item>
  34. <el-form-item label="家庭">
  35. <el-input
  36. v-model="searchForm.familyName"
  37. placeholder="点击选择家庭"
  38. readonly
  39. @click.native="showFamilySelector"
  40. >
  41. <el-button
  42. slot="append"
  43. icon="el-icon-close"
  44. v-if="searchForm.familyId"
  45. @click.stop="clearFamily"
  46. style="padding: 8px"
  47. ></el-button>
  48. <i slot="suffix" class="el-input__icon el-icon-arrow-down" @click.stop="showFamilySelector"></i>
  49. </el-input>
  50. </el-form-item>
  51. <el-form-item>
  52. <el-button type="primary" @click="handleSearch">搜索</el-button>
  53. <el-button @click="handleReset">重置</el-button>
  54. </el-form-item>
  55. </el-form>
  56. <!-- 家庭选择对话框 -->
  57. <el-dialog title="选择家庭" :visible.sync="familyDialogVisible" width="600px">
  58. <el-table :data="familyList" border v-loading="familyLoading" @row-click="selectFamily" style="cursor: pointer" :max-height="300">
  59. <el-table-column prop="id" label="ID" width="80"></el-table-column>
  60. <el-table-column prop="name" label="家庭名称" min-width="150"></el-table-column>
  61. <el-table-column prop="inviteCode" label="邀请码" width="120"></el-table-column>
  62. <el-table-column prop="createdAt" label="创建时间" width="180">
  63. <template slot-scope="scope">
  64. {{ formatDate(scope.row.createdAt) }}
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <el-pagination
  69. style="margin-top: 15px; text-align: right"
  70. @current-change="loadFamilies"
  71. :current-page="familyPagination.current"
  72. :page-size="familyPagination.size"
  73. :total="familyPagination.total"
  74. layout="total, prev, pager, next"
  75. ></el-pagination>
  76. </el-dialog>
  77. <!-- 统计卡片 -->
  78. <el-row :gutter="20" style="margin-bottom: 20px">
  79. <el-col :span="6">
  80. <el-card class="stat-card">
  81. <div class="stat-label">总任务数</div>
  82. <div class="stat-value">{{ stats.total || 0 }}</div>
  83. </el-card>
  84. </el-col>
  85. <el-col :span="6">
  86. <el-card class="stat-card">
  87. <div class="stat-label">今日任务</div>
  88. <div class="stat-value">{{ stats.today || 0 }}</div>
  89. </el-card>
  90. </el-col>
  91. </el-row>
  92. <!-- 任务列表 -->
  93. <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
  94. <el-table-column prop="id" label="ID" width="80"></el-table-column>
  95. <el-table-column prop="title" label="任务名称" min-width="200"></el-table-column>
  96. <el-table-column prop="points" label="积分" width="80">
  97. <template slot-scope="scope">
  98. <el-tag type="success">{{ scope.row.points }}</el-tag>
  99. </template>
  100. </el-table-column>
  101. <el-table-column prop="category" label="分类" width="100"></el-table-column>
  102. <el-table-column prop="deadline" label="截止时间" width="180">
  103. <template slot-scope="scope">
  104. {{ formatDate(scope.row.deadline) }}
  105. </template>
  106. </el-table-column>
  107. <el-table-column prop="status" label="状态" width="100">
  108. <template slot-scope="scope">
  109. <el-tag :type="getStatusType(scope.row.status)" size="small">
  110. {{ getStatusText(scope.row.status) }}
  111. </el-tag>
  112. </template>
  113. </el-table-column>
  114. <el-table-column prop="needReview" label="需审核" width="80">
  115. <template slot-scope="scope">
  116. <el-tag :type="scope.row.needReview ? 'warning' : 'info'" size="small">
  117. {{ scope.row.needReview ? '是' : '否' }}
  118. </el-tag>
  119. </template>
  120. </el-table-column>
  121. <el-table-column prop="childId" label="孩子ID" width="80"></el-table-column>
  122. <el-table-column prop="createdAt" label="创建时间" width="180">
  123. <template slot-scope="scope">
  124. {{ formatDate(scope.row.createdAt) }}
  125. </template>
  126. </el-table-column>
  127. </el-table>
  128. <!-- 分页 -->
  129. <el-pagination
  130. style="margin-top: 20px; text-align: right"
  131. @size-change="handleSizeChange"
  132. @current-change="handleCurrentChange"
  133. :current-page="pagination.current"
  134. :page-sizes="[10, 20, 50, 100]"
  135. :page-size="pagination.size"
  136. :total="pagination.total"
  137. layout="total, sizes, prev, pager, next, jumper"
  138. ></el-pagination>
  139. </el-card>
  140. </div>
  141. </template>
  142. <script>
  143. import { getTasksList, getTaskStats, getFamilyList } from '@/api/admin'
  144. export default {
  145. name: 'Tasks',
  146. computed: {
  147. tableHeight() {
  148. return window.innerHeight - 340
  149. }
  150. },
  151. data() {
  152. return {
  153. loading: false,
  154. tableData: [],
  155. stats: {
  156. total: 0,
  157. today: 0
  158. },
  159. searchForm: {
  160. title: '',
  161. status: '',
  162. familyName: '',
  163. familyId: ''
  164. },
  165. pagination: {
  166. current: 1,
  167. size: 10,
  168. total: 0
  169. },
  170. // 家庭选择对话框
  171. familyDialogVisible: false,
  172. familyLoading: false,
  173. familyList: [],
  174. familyPagination: {
  175. current: 1,
  176. size: 10,
  177. total: 0
  178. }
  179. }
  180. },
  181. mounted() {
  182. this.loadTasks()
  183. this.loadStats()
  184. },
  185. methods: {
  186. async loadTasks() {
  187. this.loading = true
  188. try {
  189. const params = {
  190. page: this.pagination.current,
  191. size: this.pagination.size,
  192. status: this.searchForm.status,
  193. familyId: this.searchForm.familyId || undefined
  194. }
  195. const res = await getTasksList(params)
  196. this.tableData = res.data.records || res.data || []
  197. this.pagination.total = res.data.total || this.tableData.length
  198. } catch (error) {
  199. this.$message.error(error.message || '加载任务列表失败')
  200. } finally {
  201. this.loading = false
  202. }
  203. },
  204. async loadStats() {
  205. try {
  206. const res = await getTaskStats()
  207. this.stats = res.data || { total: 0, today: 0 }
  208. } catch (error) {
  209. console.error('加载统计失败:', error)
  210. }
  211. },
  212. // 显示家庭选择对话框
  213. async showFamilySelector() {
  214. this.familyDialogVisible = true
  215. this.loadFamilies()
  216. },
  217. async loadFamilies(page = 1) {
  218. this.familyLoading = true
  219. try {
  220. const res = await getFamilyList({
  221. page: page,
  222. size: this.familyPagination.size
  223. })
  224. this.familyList = res.data.records || res.data || []
  225. this.familyPagination.total = res.data.total || this.familyList.length
  226. this.familyPagination.current = page
  227. } catch (error) {
  228. this.$message.error(error.message || '加载家庭列表失败')
  229. } finally {
  230. this.familyLoading = false
  231. }
  232. },
  233. selectFamily(row) {
  234. this.searchForm.familyId = row.id
  235. this.searchForm.familyName = row.name
  236. this.familyDialogVisible = false
  237. this.handleSearch()
  238. },
  239. clearFamily() {
  240. this.searchForm.familyId = ''
  241. this.searchForm.familyName = ''
  242. this.handleSearch()
  243. },
  244. handleSearch() {
  245. this.pagination.current = 1
  246. this.loadTasks()
  247. },
  248. handleReset() {
  249. this.searchForm = {
  250. title: '',
  251. status: '',
  252. familyName: '',
  253. familyId: ''
  254. }
  255. this.handleSearch()
  256. },
  257. handleRefresh() {
  258. this.loadTasks()
  259. this.loadStats()
  260. },
  261. handleSizeChange(val) {
  262. this.pagination.size = val
  263. this.pagination.current = 1
  264. this.loadTasks()
  265. },
  266. handleCurrentChange(val) {
  267. this.pagination.current = val
  268. this.loadTasks()
  269. },
  270. getStatusType(status) {
  271. const types = {
  272. pending: 'warning',
  273. completed: 'success',
  274. overdue: 'danger',
  275. cancelled: 'info'
  276. }
  277. return types[status] || 'info'
  278. },
  279. getStatusText(status) {
  280. const texts = {
  281. pending: '进行中',
  282. completed: '已完成',
  283. overdue: '已逾期',
  284. cancelled: '已取消'
  285. }
  286. return texts[status] || status
  287. },
  288. formatDate(dateStr) {
  289. if (!dateStr) return '-'
  290. const date = new Date(dateStr)
  291. return date.toLocaleString('zh-CN')
  292. }
  293. }
  294. }
  295. </script>
  296. <style scoped>
  297. .stat-card {
  298. text-align: center;
  299. }
  300. .stat-label {
  301. color: #909399;
  302. font-size: 14px;
  303. margin-bottom: 10px;
  304. }
  305. .stat-value {
  306. font-size: 28px;
  307. font-weight: bold;
  308. color: #409EFF;
  309. }
  310. </style>