Children.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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.nickname"
  21. placeholder="请输入昵称"
  22. clearable
  23. @keyup.enter.native="handleSearch"
  24. ></el-input>
  25. </el-form-item>
  26. <el-form-item label="家庭ID">
  27. <el-input
  28. v-model="searchForm.familyId"
  29. placeholder="请输入家庭ID"
  30. clearable
  31. @keyup.enter.native="handleSearch"
  32. ></el-input>
  33. </el-form-item>
  34. <el-form-item>
  35. <el-button type="primary" @click="handleSearch">搜索</el-button>
  36. <el-button @click="handleReset">重置</el-button>
  37. </el-form-item>
  38. </el-form>
  39. <!-- 孩子列表 -->
  40. <div class="table-scroll-wrap">
  41. <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
  42. <el-table-column prop="id" label="ID" width="80"></el-table-column>
  43. <el-table-column prop="nickname" label="昵称" width="150"></el-table-column>
  44. <el-table-column prop="age" label="年龄" width="80"></el-table-column>
  45. <el-table-column prop="totalPoints" label="当前积分" width="120">
  46. <template slot-scope="scope">
  47. <el-tag type="success">{{ scope.row.totalPoints || 0 }}</el-tag>
  48. </template>
  49. </el-table-column>
  50. <el-table-column prop="streakDays" label="连续打卡" width="100">
  51. <template slot-scope="scope">
  52. <span>{{ scope.row.streakDays || 0 }}天</span>
  53. </template>
  54. </el-table-column>
  55. <el-table-column prop="familyId" label="家庭ID" width="100"></el-table-column>
  56. <el-table-column prop="penaltyEnabled" label="扣分" width="80">
  57. <template slot-scope="scope">
  58. <el-tag :type="scope.row.penaltyEnabled ? 'danger' : 'info'" size="small">
  59. {{ scope.row.penaltyEnabled ? '开启' : '关闭' }}
  60. </el-tag>
  61. </template>
  62. </el-table-column>
  63. <el-table-column prop="createdAt" label="创建时间" width="180" sortable="custom" :sort-orders="['ascending','descending']" sortable="custom" :sort-orders="['ascending','descending']" >
  64. <template slot-scope="scope">
  65. {{ formatDate(scope.row.createdAt) }}
  66. </template>
  67. </el-table-column>
  68. <el-table-column label="操作" width="180" fixed="right">
  69. <template slot-scope="{ row }">
  70. <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
  71. <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
  72. <el-button size="mini">
  73. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  74. </el-button>
  75. <el-dropdown-menu slot="dropdown">
  76. <el-dropdown-item command="adjustPoints" icon="el-icon-edit">调分</el-dropdown-item>
  77. </el-dropdown-menu>
  78. </el-dropdown>
  79. </template>
  80. </el-table-column>
  81. </el-table>
  82. </div>
  83. <!-- 分页 -->
  84. <el-pagination
  85. style="margin-top: 20px; text-align: right"
  86. @size-change="handleSizeChange"
  87. @current-change="handleCurrentChange"
  88. :current-page="pagination.current"
  89. :page-sizes="[10, 20, 50, 100]"
  90. :page-size="pagination.size"
  91. :total="pagination.total"
  92. layout="total, sizes, prev, pager, next, jumper"
  93. ></el-pagination>
  94. </el-card>
  95. <!-- 调整积分对话框 -->
  96. <el-dialog title="调整积分" :visible.sync="dialogVisible" width="400px">
  97. <el-form :model="pointsForm" :rules="pointsRules" ref="pointsForm" label-width="100px">
  98. <el-form-item label="当前积分">
  99. <span>{{ currentChild ? currentChild.totalPoints : 0 }}</span>
  100. </el-form-item>
  101. <el-form-item label="调整积分" prop="amount">
  102. <el-input-number
  103. v-model="pointsForm.amount"
  104. :min="-1000"
  105. :max="1000"
  106. placeholder="正数为加分,负数为扣分"
  107. ></el-input-number>
  108. </el-form-item>
  109. <el-form-item label="原因" prop="reason">
  110. <el-input
  111. type="textarea"
  112. v-model="pointsForm.reason"
  113. placeholder="请输入调整原因"
  114. :rows="3"
  115. ></el-input>
  116. </el-form-item>
  117. </el-form>
  118. <div slot="footer">
  119. <el-button @click="dialogVisible = false">取消</el-button>
  120. <el-button type="primary" @click="submitPoints" :loading="submitting">确定</el-button>
  121. </div>
  122. </el-dialog>
  123. <!-- 编辑孩子对话框 -->
  124. <el-dialog title="编辑孩子" :visible.sync="editDialogVisible" width="500px">
  125. <el-form :model="editForm" :rules="editRules" ref="editForm" label-width="100px">
  126. <el-form-item label="昵称" prop="nickname">
  127. <el-input v-model="editForm.nickname" placeholder="请输入昵称"></el-input>
  128. </el-form-item>
  129. <el-form-item label="年龄" prop="age">
  130. <el-input-number v-model="editForm.age" :min="0" :max="18"></el-input-number>
  131. </el-form-item>
  132. <el-form-item label="扣分开关">
  133. <el-switch v-model="editForm.penaltyEnabled" :active-value="1" :inactive-value="0"></el-switch>
  134. </el-form-item>
  135. </el-form>
  136. <div slot="footer">
  137. <el-button @click="editDialogVisible = false">取消</el-button>
  138. <el-button type="primary" @click="submitEdit" :loading="submitting">确定</el-button>
  139. </div>
  140. </el-dialog>
  141. </div>
  142. </template>
  143. <script>
  144. import { getChildrenList, adjustChildPoints, updateChild } from '@/api/admin'
  145. export default {
  146. name: 'Children',
  147. computed: {
  148. tableHeight() {
  149. return window.innerHeight - 340
  150. }
  151. },
  152. data() {
  153. return {
  154. loading: false,
  155. submitting: false,
  156. tableData: [],
  157. searchForm: {
  158. nickname: '',
  159. familyId: ''
  160. },
  161. pagination: {
  162. current: 1,
  163. size: 10,
  164. total: 0
  165. },
  166. dialogVisible: false,
  167. editDialogVisible: false,
  168. currentChild: null,
  169. pointsForm: {
  170. amount: 0,
  171. reason: ''
  172. },
  173. pointsRules: {
  174. amount: [
  175. { required: true, message: '请输入调整积分', trigger: 'blur' }
  176. ],
  177. reason: [
  178. { required: true, message: '请输入调整原因', trigger: 'blur' }
  179. ]
  180. },
  181. editForm: {
  182. id: null,
  183. nickname: '',
  184. age: 0,
  185. penaltyEnabled: 1
  186. },
  187. editRules: {
  188. nickname: [
  189. { required: true, message: '请输入昵称', trigger: 'blur' }
  190. ],
  191. age: [
  192. { required: true, message: '请输入年龄', trigger: 'blur' }
  193. ]
  194. }
  195. }
  196. },
  197. mounted() {
  198. this.loadChildren()
  199. },
  200. methods: {
  201. handleActionCmd(row, cmd) {
  202. switch (cmd) {
  203. case 'adjustPoints': this.handleAdjustPoints(row); break;
  204. case 'edit': this.handleEdit(row); break;
  205. }
  206. },
  207. async loadChildren() {
  208. this.loading = true
  209. try {
  210. const params = {
  211. page: this.pagination.current,
  212. size: this.pagination.size,
  213. ...this.searchForm
  214. }
  215. const res = await getChildrenList(params)
  216. this.tableData = res.data.records || res.data || []
  217. this.pagination.total = res.data.total || this.tableData.length
  218. } catch (error) {
  219. this.$message.error(error.message || '加载孩子列表失败')
  220. } finally {
  221. this.loading = false
  222. }
  223. },
  224. handleSearch() {
  225. this.pagination.current = 1
  226. this.loadChildren()
  227. },
  228. handleReset() {
  229. this.searchForm = {
  230. nickname: '',
  231. familyId: ''
  232. }
  233. this.handleSearch()
  234. },
  235. handleRefresh() {
  236. this.loadChildren()
  237. },
  238. handleSizeChange(val) {
  239. this.pagination.size = val
  240. this.pagination.current = 1
  241. this.loadChildren()
  242. },
  243. handleCurrentChange(val) {
  244. this.pagination.current = val
  245. this.loadChildren()
  246. },
  247. handleAdjustPoints(row) {
  248. this.currentChild = row
  249. this.pointsForm = {
  250. amount: 0,
  251. reason: ''
  252. }
  253. this.dialogVisible = true
  254. },
  255. async submitPoints() {
  256. this.$refs.pointsForm.validate(async (valid) => {
  257. if (valid) {
  258. this.submitting = true
  259. try {
  260. await adjustChildPoints(this.currentChild.id, this.pointsForm)
  261. this.$message.success('积分调整成功')
  262. this.dialogVisible = false
  263. this.loadChildren()
  264. } catch (error) {
  265. this.$message.error(error.message || '积分调整失败')
  266. } finally {
  267. this.submitting = false
  268. }
  269. }
  270. })
  271. },
  272. handleEdit(row) {
  273. this.editForm = {
  274. id: row.id,
  275. nickname: row.nickname,
  276. age: row.age,
  277. penaltyEnabled: row.penaltyEnabled
  278. }
  279. this.editDialogVisible = true
  280. },
  281. async submitEdit() {
  282. this.$refs.editForm.validate(async (valid) => {
  283. if (valid) {
  284. this.submitting = true
  285. try {
  286. await updateChild(this.editForm.id, this.editForm)
  287. this.$message.success('更新成功')
  288. this.editDialogVisible = false
  289. this.loadChildren()
  290. } catch (error) {
  291. this.$message.error(error.message || '更新失败')
  292. } finally {
  293. this.submitting = false
  294. }
  295. }
  296. })
  297. },
  298. formatDate(dateStr) {
  299. if (!dateStr) return '-'
  300. const date = new Date(dateStr)
  301. return date.toLocaleString('zh-CN')
  302. }
  303. }
  304. }
  305. </script>
  306. <style scoped>
  307. .table-scroll-wrap { overflow-x: auto; }
  308. </style>