| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- <template>
- <div class="page-container">
- <el-card>
- <div slot="header">
- <span>家庭任务管理</span>
- <el-button style="float: right" size="small" @click="loadData" :loading="loading">刷新</el-button>
- </div>
- <!-- 搜索 -->
- <el-form :inline="true" :model="searchForm" style="margin-bottom: 20px">
- <el-form-item label="选择家庭">
- <el-select v-model="searchForm.familyId" placeholder="请选择家庭" clearable @change="handleFamilyChange">
- <el-option v-for="family in families" :key="family.familyId" :label="family.familyName" :value="family.familyId">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="选择孩子">
- <el-select v-model="searchForm.memberId" placeholder="请选择孩子" clearable @change="loadChildTasks">
- <el-option v-for="child in children" :key="child.memberId" :label="child.childName" :value="child.memberId">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="searchForm.status" placeholder="全部" clearable @change="loadChildTasks">
- <el-option label="待完成" value="pending"></el-option>
- <el-option label="已完成" value="completed"></el-option>
- <el-option label="已超时" value="overdue"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadChildTasks">搜索</el-button>
- </el-form-item>
- </el-form>
- <!-- 家庭概览 -->
- <div v-if="familyOverview" class="overview-cards" v-loading="overviewLoading">
- <div v-if="!overviewLoading && familyOverview.totalChildren === 0" class="empty-state">
- <i class="el-icon-user-solid" style="font-size: 48px; color: #ccc; margin-bottom: 16px;"></i>
- <p>暂无孩子数据</p>
- </div>
- <template v-else>
- <el-row :gutter="20">
- <el-col :span="6">
- <div class="overview-card">
- <div class="overview-value">{{ familyOverview.totalChildren }}</div>
- <div class="overview-label">孩子数量</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="overview-card">
- <div class="overview-value warning">{{ familyOverview.totalPendingReview }}</div>
- <div class="overview-label">待审核任务</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="overview-card">
- <div class="overview-value success">{{ familyOverview.totalCompleted }}</div>
- <div class="overview-label">已完成任务</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="overview-card">
- <div class="overview-value danger">{{ familyOverview.totalOverdue }}</div>
- <div class="overview-label">超时任务</div>
- </div>
- </el-col>
- </el-row>
- </template>
- </div>
- <!-- 孩子列表 -->
- <el-table :max-height="tableHeight" v-if="familyOverview && !searchForm.memberId" :data="familyOverview.children" border>
- <el-table-column prop="childName" label="孩子姓名" width="120"></el-table-column>
- <el-table-column prop="totalPoints" label="总积分" width="100"></el-table-column>
- <el-table-column prop="streakDays" label="连续打卡" width="100">
- <template slot-scope="{ row }">{{ row.streakDays }}天</template>
- </el-table-column>
- <el-table-column prop="todayTaskCount" label="今日任务" width="100"></el-table-column>
- <el-table-column prop="todayCompletedCount" label="今日完成" width="100"></el-table-column>
- <el-table-column prop="pendingReviewCount" label="待审核" width="100">
- <template slot-scope="{ row }">
- <el-badge :value="row.pendingReviewCount" :max="99" class="badge-item" />
- </template>
- </el-table-column>
- <el-table-column label="操作" width="150">
- <template slot-scope="{ row }">
- <el-button size="mini" @click="selectChild(row)">查看任务</el-button>
- </template>
- </el-table-column>
- </el-table>
- <!-- 任务列表 -->
- <el-table v-if="searchForm.memberId" :data="tasks" border v-loading="taskLoading" :max-height="tableHeight">
- <template slot="empty">
- <div class="empty-table-state">
- <i class="el-icon-tickets" style="font-size: 48px; color: #ccc; margin-bottom: 16px;"></i>
- <p>暂无任务数据</p>
- </div>
- </template>
- <el-table-column prop="id" label="ID" width="60"></el-table-column>
- <el-table-column prop="title" label="任务标题" min-width="150"></el-table-column>
- <el-table-column prop="points" label="积分" width="80"></el-table-column>
- <el-table-column prop="deadline" label="截止时间" width="160">
- <template slot-scope="{ row }">{{ formatDate(row.deadline) }}</template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template slot-scope="{ row }">
- <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="needReview" label="需审核" width="80">
- <template slot-scope="{ row }">
- <el-tag :type="row.needReview === 1 ? 'warning' : 'info'" size="small">{{ row.needReview === 1 ? '是' : '否' }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="completedAt" label="完成时间" width="160">
- <template slot-scope="{ row }">{{ formatDate(row.completedAt) }}</template>
- </el-table-column>
- <el-table-column label="操作" width="220" fixed="right">
- <template slot-scope="{ row }">
- <el-button v-if="row.status !== 'cancelled'" size="mini" type="primary" @click="handleEditTask(row)">编辑</el-button>
- <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
- <el-button size="mini">
- 更多<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item v-if="row.status !== 'cancelled'" command="delete" icon="el-icon-delete">删除</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'completed' && row.needReview === 1" command="reviewPass" icon="el-icon-check">通过</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'completed' && row.needReview === 1" command="reviewReject" icon="el-icon-close">拒绝</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table>
- <!-- 分页 -->
- <el-pagination
- v-if="searchForm.memberId && total > 0"
- style="margin-top: 20px; text-align: right"
- :current-page="pagination.page"
- :page-size="pagination.size"
- :total="total"
- layout="total, prev, pager, next"
- @current-change="handlePageChange"
- />
- </el-card>
- <!-- 审核弹窗 -->
- <el-dialog title="审核任务" :visible.sync="reviewDialogVisible" width="400px">
- <el-form>
- <el-form-item label="任务标题">{{ currentTask.title }}</el-form-item>
- <el-form-item label="执行者">孩子</el-form-item>
- <el-form-item label="完成时间">{{ formatDate(currentTask.completedAt) }}</el-form-item>
- <el-form-item label="审核意见">
- <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="请输入审核意见(选填)"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer">
- <el-button @click="reviewDialogVisible = false">取消</el-button>
- <el-button type="danger" @click="confirmReview(false)">拒绝</el-button>
- <el-button type="primary" @click="confirmReview(true)">通过</el-button>
- </div>
- </el-dialog>
- <!-- 编辑任务弹窗 -->
- <el-dialog title="编辑任务" :visible.sync="editDialogVisible" width="500px">
- <el-form :model="editForm" label-width="80px">
- <el-form-item label="任务标题">
- <el-input v-model="editForm.title"></el-input>
- </el-form-item>
- <el-form-item label="任务描述">
- <el-input type="textarea" v-model="editForm.description" rows="3"></el-input>
- </el-form-item>
- <el-form-item label="积分">
- <el-input-number v-model="editForm.points" :min="1" :max="100"></el-input-number>
- </el-form-item>
- <el-form-item label="截止时间">
- <el-date-picker v-model="editForm.deadline" type="datetime" placeholder="选择日期时间" value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="editForm.status">
- <el-option label="待完成" value="pending"></el-option>
- <el-option label="已完成" value="completed"></el-option>
- <el-option label="已超时" value="overdue"></el-option>
- <el-option label="已取消" value="cancelled"></el-option>
- </el-select>
- </el-form-item>
- </el-form>
- <div slot="footer">
- <el-button @click="editDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitEditTask" :loading="editLoading">保存</el-button>
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import { fetchBoundFamilies, fetchFamilyOverview, fetchChildTasks, fetchPendingReviewTasks, reviewTask, updateTask, deleteTask } from '@/api/guideTask'
- export default {
- name: 'GuideFamilyTask',
- computed: {
- tableHeight() {
- return window.innerHeight - 340
- }
- },
- data() {
- return {
- loading: false,
- taskLoading: false,
- overviewLoading: false,
- families: [],
- children: [],
- tasks: [],
- familyOverview: null,
- total: 0,
- searchForm: {
- familyId: null,
- memberId: null,
- status: ''
- },
- pagination: {
- page: 1,
- size: 10
- },
- reviewDialogVisible: false,
- currentTask: null,
- reviewComment: '',
- reviewAction: true,
- editDialogVisible: false,
- editLoading: false,
- editForm: {
- id: null,
- title: '',
- description: '',
- points: 2,
- deadline: '',
- status: 'pending'
- }
- }
- },
- mounted() {
- this.loadFamilies()
- },
- methods: {
- handleActionCmd(row, cmd) {
- switch (cmd) {
- case 'edit': this.handleEditTask(row); break;
- case 'delete': this.handleDeleteTask(row); break;
- case 'reviewPass': this.handleReview(row, true); break;
- case 'reviewReject': this.handleReview(row, false); break;
- }
- },
- async loadFamilies() {
- this.loading = true
- try {
- const res = await fetchBoundFamilies()
- this.families = res.data || []
- } catch (e) {
- this.$message.error('加载失败')
- } finally {
- this.loading = false
- }
- },
- async handleFamilyChange() {
- if (!this.searchForm.familyId) {
- this.familyOverview = null
- this.children = []
- return
- }
-
- this.overviewLoading = true
- try {
- const res = await fetchFamilyOverview(this.searchForm.familyId)
- this.familyOverview = res.data
- this.children = res.data.children || []
- } catch (e) {
- this.$message.error('加载失败')
- } finally {
- this.overviewLoading = false
- }
- },
- selectChild(child) {
- this.searchForm.memberId = child.memberId
- this.loadChildTasks()
- },
- async loadChildTasks() {
- if (!this.searchForm.memberId) {
- this.tasks = []
- return
- }
-
- this.taskLoading = true
- try {
- const res = await fetchChildTasks(
- this.searchForm.familyId,
- this.searchForm.memberId,
- this.pagination.page,
- this.pagination.size,
- this.searchForm.status
- )
- this.tasks = res.data.records || []
- this.total = res.data.total || 0
- } catch (e) {
- this.$message.error('加载失败')
- } finally {
- this.taskLoading = false
- }
- },
- handlePageChange(page) {
- this.pagination.page = page
- this.loadChildTasks()
- },
- getStatusType(status) {
- const map = { pending: 'info', completed: 'success', overdue: 'danger', cancelled: 'info' }
- return map[status] || 'info'
- },
- getStatusText(status) {
- const map = { pending: '待完成', completed: '已完成', overdue: '已超时', cancelled: '已取消' }
- return map[status] || status
- },
- formatDate(date) {
- if (!date) return '-'
- return new Date(date).toLocaleString()
- },
- handleReview(task, approved) {
- this.currentTask = task
- this.reviewAction = approved
- this.reviewComment = ''
- this.reviewDialogVisible = true
- },
- async confirmReview() {
- try {
- await reviewTask(
- this.searchForm.familyId,
- this.currentTask.id,
- {
- approved: this.reviewAction,
- comment: this.reviewComment
- }
- )
- this.$message.success(this.reviewAction ? '已通过' : '已拒绝')
- this.reviewDialogVisible = false
- this.loadChildTasks()
- this.handleFamilyChange()
- } catch (e) {
- this.$message.error('操作失败')
- }
- },
- handleEditTask(task) {
- this.currentTask = task
- this.editForm = {
- id: task.id,
- title: task.title,
- description: task.description || '',
- points: task.points || 2,
- deadline: task.deadline ? new Date(task.deadline).toISOString().slice(0, 16) : '',
- status: task.status
- }
- this.editDialogVisible = true
- },
- async submitEditTask() {
- if (!this.editForm.title) {
- this.$message.warning('请输入任务标题')
- return
- }
- this.editLoading = true
- try {
- const data = {
- title: this.editForm.title,
- description: this.editForm.description,
- points: this.editForm.points,
- status: this.editForm.status,
- deadline: this.editForm.deadline ? new Date(this.editForm.deadline).getTime() : null
- }
- await updateTask(this.searchForm.familyId, this.editForm.id, data)
- this.$message.success('保存成功')
- this.editDialogVisible = false
- this.loadChildTasks()
- } catch (e) {
- this.$message.error('保存失败')
- } finally {
- this.editLoading = false
- }
- },
- async handleDeleteTask(task) {
- try {
- await this.$confirm('确定要删除该任务吗?', '提示', {
- type: 'warning'
- })
- await deleteTask(this.searchForm.familyId, task.id)
- this.$message.success('删除成功')
- this.loadChildTasks()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error('删除失败')
- }
- }
- }
- }
- }
- </script>
- <style scoped>
- .overview-cards { margin-bottom: 20px; }
- .overview-card {
- background: #fff;
- border-radius: 8px;
- padding: 20px;
- text-align: center;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .overview-value {
- font-size: 32px;
- font-weight: bold;
- color: #333;
- }
- .overview-value.warning { color: #e6a23c; }
- .overview-value.success { color: #67c23a; }
- .overview-value.danger { color: #f56c6c; }
- .overview-label {
- font-size: 14px;
- color: #999;
- margin-top: 8px;
- }
- .badge-item { margin-top: 8px; }
- .empty-state,
- .empty-table-state {
- text-align: center;
- padding: 40px;
- color: #999;
- background: #fafafa;
- border-radius: 8px;
- border: 1px dashed #ddd;
- }
- .empty-table-state {
- margin: 20px 0;
- }
- </style>
|