最新修改,初版
This commit is contained in:
409
src/views/UserView.vue
Normal file
409
src/views/UserView.vue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<div class="user-view-container">
|
||||
<div class="user-header-card">
|
||||
<div class="header-main">
|
||||
<div class="welcome-container">
|
||||
<div class="welcome-section">
|
||||
<h2 class="greeting">{{ welcomeMessage }},{{ currentUser.username }}</h2>
|
||||
<p class="sub-text">欢迎回到你的垃圾堆,开启牛马的一天。</p>
|
||||
</div>
|
||||
|
||||
<div class="identity-badges">
|
||||
<el-space :size="8">
|
||||
<el-tag v-if="realStaffInfo" type="warning" effect="plain" round size="small">
|
||||
<el-icon><User /></el-icon> 牛马身份: {{ realStaffInfo.name }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="danger" round size="small">未录入员工表,无权接单</el-tag>
|
||||
</el-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-action">
|
||||
<el-button type="danger" plain @click="handleLogout">
|
||||
<el-icon><SwitchButton /></el-icon> 退出登录
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-tabs-wrapper">
|
||||
<el-radio-group v-model="currentStatus" @change="handleStatusChange" class="custom-radio-group">
|
||||
<el-radio-button label="未分配">
|
||||
<el-icon><Notification /></el-icon> 待分配任务
|
||||
</el-radio-button>
|
||||
<el-radio-button label="进行中">
|
||||
<el-icon><Loading v-if="loading" /><Timer v-else /></el-icon> 我的进行中
|
||||
</el-radio-button>
|
||||
<el-radio-button label="已完成">
|
||||
<el-icon><CircleCheck /></el-icon> 我的已完成
|
||||
</el-radio-button>
|
||||
<el-radio-button label="已完结">
|
||||
<el-icon><Checked /></el-icon> 我的已完结
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<el-table :data="paginatedTickets" v-loading="loading" border style="width: 100%; margin-top: 20px" class="custom-table">
|
||||
<template #empty>
|
||||
<el-empty :image-size="120" :description="emptyText">
|
||||
<el-button type="primary" plain @click="fetchTickets">刷新列表</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="website" label="网站名称" min-width="150" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="数据量" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<div class="cell-content-card">
|
||||
<div class="data-display-area">
|
||||
<span v-if="scope.row.datanum && scope.row.datanum !== '0'" class="display-val">{{ scope.row.datanum }}</span>
|
||||
<span v-else class="no-data-placeholder">-</span>
|
||||
</div>
|
||||
<el-popover v-if="currentStatus !== '未分配'" placement="top" :width="200" trigger="click" @show="prepareEdit(scope.row, 'datanum')">
|
||||
<template #reference>
|
||||
<el-button type="primary" size="small" link class="edit-link-btn">
|
||||
{{ (scope.row.datanum && scope.row.datanum !== '0') ? '[ 修改数据 ]' : '[ 输入数据 ]' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="popover-content">
|
||||
<div class="pop-header">录入站点数据量</div>
|
||||
<el-input v-model="editValue" placeholder="请输入数值" size="small" clearable />
|
||||
<div class="pop-footer">
|
||||
<el-button type="primary" size="small" @click="submitEdit(scope.row, 'datanum')">确认提交</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="任务进度" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<div class="cell-content-card">
|
||||
<div class="data-display-area" style="width: 100%; padding: 0 10px;">
|
||||
<el-progress
|
||||
:percentage="Number(scope.row.progress || 0)"
|
||||
:status="Number(scope.row.progress) === 100 ? 'success' : ''"
|
||||
:stroke-width="12"
|
||||
striped
|
||||
striped-flow
|
||||
/>
|
||||
</div>
|
||||
<el-popover v-if="currentStatus === '进行中'" placement="top" :width="240" trigger="click" @show="prepareEdit(scope.row, 'progress')">
|
||||
<template #reference>
|
||||
<el-button type="primary" size="small" link class="edit-link-btn">
|
||||
[ 更新进度 ]
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="popover-content">
|
||||
<div class="pop-header">调整当前进度 (%)</div>
|
||||
<el-radio-group v-model="editValue" size="small" class="progress-radio-group">
|
||||
<el-radio-button :label="0">0</el-radio-button>
|
||||
<el-radio-button :label="30">30</el-radio-button>
|
||||
<el-radio-button :label="50">50</el-radio-button>
|
||||
<el-radio-button :label="70">70</el-radio-button>
|
||||
<el-radio-button :label="100">100</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="pop-footer">
|
||||
<el-button type="primary" size="small" @click="submitEdit(scope.row, 'progress')">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="operator" label="运营" width="120" align="center" />
|
||||
|
||||
<el-table-column label="备注" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<div class="cell-content-card">
|
||||
<div class="data-display-area">
|
||||
<div v-if="scope.row.remark" class="display-remark" :title="scope.row.remark">{{ scope.row.remark }}</div>
|
||||
<span v-else class="no-data-placeholder">-</span>
|
||||
</div>
|
||||
<el-popover v-if="currentStatus !== '未分配'" placement="top" :width="250" trigger="click" @show="prepareEdit(scope.row, 'remark')">
|
||||
<template #reference>
|
||||
<el-button type="warning" size="small" link class="edit-link-btn">
|
||||
{{ scope.row.remark ? '[ 修改备注 ]' : '[ 输入备注 ]' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="popover-content">
|
||||
<div class="pop-header">录入工单备注</div>
|
||||
<el-input v-model="editValue" type="textarea" :rows="3" placeholder="请输入备注" size="small" />
|
||||
<div class="pop-footer">
|
||||
<el-button type="primary" size="small" @click="submitEdit(scope.row, 'remark')">提交保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="['已完成', '已完结'].includes(currentStatus) ? '锁定日期' : '创建日期'" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<span class="pure-date">
|
||||
{{ formatDateOnly(['已完成', '已完结'].includes(currentStatus) ? scope.row.lock_time : scope.row.create_time) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.is_complete === '未分配'" type="primary" size="small" :disabled="!realStaffInfo" @click="handleAccept(scope.row)">立即接单</el-button>
|
||||
<el-button v-if="scope.row.is_complete === '进行中' && String(scope.row.staff_id) === String(realStaffInfo?.id)" type="success" size="small" @click="handleFinish(scope.row)">完成任务</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="filteredTickets.length"
|
||||
:page-sizes="[15, 30, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { User, SwitchButton, Timer, Notification, CircleCheck, Checked, Loading } from '@element-plus/icons-vue'
|
||||
import { ticketApi } from '@/api/ticket'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const allTickets = ref([])
|
||||
const currentStatus = ref('未分配')
|
||||
const realStaffInfo = ref(null)
|
||||
const editValue = ref('')
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(15)
|
||||
|
||||
const formatDateOnly = (timestamp) => {
|
||||
if (!timestamp || timestamp === '0' || timestamp === '') return '-';
|
||||
let date = !isNaN(timestamp) ? new Date(String(timestamp).length === 10 ? timestamp * 1000 : parseInt(timestamp)) : new Date(timestamp);
|
||||
if (isNaN(date.getTime())) return '-';
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// js计算时间
|
||||
const welcomeMessage = computed(() => {
|
||||
const hour = new Date().getHours()
|
||||
if (hour < 6) return '凌晨好'; if (hour < 9) return '早上好'; if (hour < 12) return '上午好';
|
||||
if (hour < 14) return '中午好'; if (hour < 18) return '下午好'; return '晚上好'
|
||||
})
|
||||
|
||||
// 获取当前用户信息
|
||||
const currentUser = computed(() => {
|
||||
const info = localStorage.getItem('user_info') || sessionStorage.getItem('user_info')
|
||||
return info ? JSON.parse(info) : { id: null, username: '未知' }
|
||||
})
|
||||
|
||||
// 获取工单信息
|
||||
const fetchTickets = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await ticketApi.getTickets({ page: 1, limit: 1000 })
|
||||
// console.log(res)
|
||||
if (res.code === 200 || res.code === true) allTickets.value = res.data.data
|
||||
} catch (e) { ElMessage.error('获取列表失败') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
// 过滤工单
|
||||
const filteredTickets = computed(() => {
|
||||
return allTickets.value.filter(ticket => {
|
||||
if (ticket.is_complete !== currentStatus.value) return false
|
||||
if (currentStatus.value !== '未分配') return String(ticket.staff_id) === String(realStaffInfo.value?.id)
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const paginatedTickets = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
return filteredTickets.value.slice(start, end)
|
||||
})
|
||||
|
||||
const handleSizeChange = (val) => { pageSize.value = val; currentPage.value = 1; }
|
||||
const handleCurrentChange = (val) => { currentPage.value = val; }
|
||||
const handleStatusChange = () => { currentPage.value = 1; fetchTickets(); }
|
||||
|
||||
const prepareEdit = (row, field) => { editValue.value = row[field] || '' }
|
||||
|
||||
const submitEdit = async (row, field) => {
|
||||
try {
|
||||
const payload = {
|
||||
datanum: field === 'datanum' ? String(editValue.value) : String(row.datanum || ''),
|
||||
remark: field === 'remark' ? editValue.value : (row.remark || ''),
|
||||
progress: field === 'progress' ? Number(editValue.value) : Number(row.progress || 0)
|
||||
}
|
||||
const res = await ticketApi.updateTicket(row.id, payload)
|
||||
if (res.code === 200 || res.code === true) {
|
||||
row[field] = editValue.value;
|
||||
ElMessage.success('更新成功');
|
||||
document.body.click()
|
||||
}
|
||||
} catch (e) { ElMessage.error('操作失败') }
|
||||
}
|
||||
|
||||
// 点击接受工单
|
||||
const handleAccept = (row) => {
|
||||
ElMessageBox.confirm(`确认接手工单 #${row.id}?`).then(async () => {
|
||||
try {
|
||||
const res = await ticketApi.updateTicket(row.id, { staff_id: realStaffInfo.value.id, is_complete: '进行中', progress: 0 })
|
||||
if (res.code === 200 || res.code === true) { ElMessage.success('接单成功'); fetchTickets() }
|
||||
} catch (e) { ElMessage.error('操作失败') }
|
||||
})
|
||||
}
|
||||
|
||||
// 点击完成
|
||||
const handleFinish = (row) => {
|
||||
ElMessageBox.confirm('确认已完成任务?').then(async () => {
|
||||
try {
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000);
|
||||
// 关键修改:在提交时增加 progress: 100
|
||||
const res = await ticketApi.updateTicket(row.id, {
|
||||
is_complete: '已完成',
|
||||
lock_time: String(currentTimestamp),
|
||||
progress: 100 // 默认改为100
|
||||
})
|
||||
|
||||
if (res.code === 200 || res.code === true) {
|
||||
ElMessage.success('任务已完成,进度已更新为100%');
|
||||
fetchTickets()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
ElMessageBox.confirm('确定退出当前账号?', '提示', { type: 'warning' }).then(() => {
|
||||
localStorage.clear(); sessionStorage.clear(); router.push('/login')
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await ticketApi.getStaffs()
|
||||
if (res.code === 200 || res.code === true) {
|
||||
const match = (res.data || []).find(s => s.name === currentUser.value.username)
|
||||
if (match) realStaffInfo.value = match
|
||||
}
|
||||
} catch (e) {}
|
||||
fetchTickets()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 容器布局 */
|
||||
.header-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.welcome-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px; /* 这里统一控制欢迎语、副标题、标签之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 清除标题默认的垂直 margin,防止撑开间距 */
|
||||
.greeting {
|
||||
margin: 0;
|
||||
color: #1a1a1a;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 副标题紧贴标题 */
|
||||
.sub-text {
|
||||
margin: 4px 0 0 0; /* 仅顶部保留微小间距 */
|
||||
color: #8c8c8c;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 标签组位置微调 */
|
||||
.identity-badges {
|
||||
margin-top: 4px; /* 让标签组靠近上方的文字 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 针对 el-tag 内部图标和文字的微调 */
|
||||
:deep(.el-tag) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-style: dashed; /* 既然是牛马,边框可以搞成虚线更有自嘲感 */
|
||||
}
|
||||
|
||||
|
||||
.user-view-container { padding: 24px; background-color: #f0f2f5; min-height: 100vh; }
|
||||
.user-header-card { background: white; padding: 24px; border-radius: 12px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
|
||||
.greeting { margin: 0 0 8px 0; color: #1a1a1a; font-size: 24px; }
|
||||
.status-tabs-wrapper { background: white; padding: 8px; border-radius: 8px; display: inline-block; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
|
||||
.custom-radio-group :deep(.el-radio-button__inner) { border: none !important; border-radius: 6px !important; margin: 0 4px; padding: 10px 20px; display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
.custom-table { border-radius: 12px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.05); }
|
||||
.cell-content-card { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; min-height: 60px; }
|
||||
.data-display-area { min-height: 24px; display: flex; align-items: center; justify-content: center; width: 100%; }
|
||||
.display-val { font-weight: 700; color: #409eff; font-size: 15px; }
|
||||
.display-remark { font-size: 12px; color: #595959; max-width: 180px; line-height: 1.4; background: #f5f7fa; padding: 4px 10px; border-radius: 4px; text-align: center; }
|
||||
.no-data-placeholder { color: #dcdfe6; font-size: 12px; }
|
||||
.pure-date { color: #606266; font-weight: 500; font-family: monospace; font-size: 14px; }
|
||||
.edit-link-btn { font-size: 12px; height: auto; padding: 0; opacity: 0.7; }
|
||||
.edit-link-btn:hover { opacity: 1; text-decoration: underline; }
|
||||
|
||||
.pagination-container { margin-top: 24px; display: flex; justify-content: flex-end; padding: 10px 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
|
||||
|
||||
.progress-radio-group { display: flex; justify-content: center; margin: 10px 0; }
|
||||
.progress-radio-group :deep(.el-radio-button__inner) { padding: 8px 10px !important; }
|
||||
|
||||
:deep(.el-table__row) { height: 100px; }
|
||||
:deep(.el-table .cell) { overflow: visible !important; }
|
||||
.pop-header { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #434343; text-align: center; }
|
||||
.pop-footer { margin-top: 12px; text-align: right; }
|
||||
|
||||
|
||||
/* 1. 强行修正进度条内部容器的对齐方式 */
|
||||
:deep(.el-progress-bar__innerText) {
|
||||
display: flex !important;
|
||||
align-items: center !important; /* 垂直居中 */
|
||||
justify-content: center !important; /* 水平居中 */
|
||||
height: 100% !important;
|
||||
line-height: 1 !important;
|
||||
/* 移除默认的基线对齐 */
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
/* 2. 针对 100% 时出现的那个 svg 图标进行位移修正 */
|
||||
:deep(.el-progress-bar__innerText svg),
|
||||
:deep(.el-progress-bar__innerText .el-icon) {
|
||||
margin: 0 !important;
|
||||
/* 关键:利用相对定位向上平移 1px,抵消视觉上的下沉 */
|
||||
position: relative !important;
|
||||
top: -1px !important;
|
||||
/* 稍微调整图标大小,让它在 10px 的进度条里更协调 */
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
}
|
||||
|
||||
/* 3. 如果还是觉得整体偏,可以给进度条容器加一个微调 */
|
||||
:deep(.el-progress) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user