!9 czh-20260520-增加初版首页
Merge pull request !9 from 陈志浩/feature/processPortal
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<div class="my-work">
|
||||
<div class="my-work__header">
|
||||
<h2 class="my-work__title">我的工作</h2>
|
||||
<div class="my-work__actions">
|
||||
<div class="my-work__tabs">
|
||||
<div class="my-work__tab" :class="{ 'my-work__tab--active': activeTab === 'todo' }" @click="activeTab = 'todo'">
|
||||
待办
|
||||
<span class="my-work__badge my-work__badge--danger">{{ todoTotal }}</span>
|
||||
</div>
|
||||
<div class="my-work__tab" :class="{ 'my-work__tab--active': activeTab === 'cc' }" @click="activeTab = 'cc'">我的抄送</div>
|
||||
</div>
|
||||
<a class="my-work__more" @click="goMore">
|
||||
更多
|
||||
<RightOutlined class="my-work__more-icon" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-work__body">
|
||||
<template v-if="activeTab === 'todo'">
|
||||
<BasicTable @register="registerTodoTable">
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTodoTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
<template v-if="activeTab === 'cc'">
|
||||
<BasicTable @register="registerCcTable">
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getCcTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<task-handle-modal @register="registerHandleModal" @success="reloadTodo" />
|
||||
<select-entruster-modal @register="registerEntrusterModal" @selected="selectedEntruster" />
|
||||
<select-entruster-modal @register="registerComplaintModal" @selected="selectedComplaint" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
import TaskHandleModal from '/@/views/super/bpm/process/personalOffice/myHandleTask/modal/TaskHandleModal.vue';
|
||||
import SelectEntrusterModal from '/@/views/super/bpm/process/personalOffice/myHandleTask/modal/SelectEntrusterModal.vue';
|
||||
import { getTaskInfoForHistory } from '/@/views/super/bpm/process/personalOffice/myHandleTask/useTaskList';
|
||||
import { todoColumns } from '../data/todo.data';
|
||||
import { ccColumns } from '../data/cc.data';
|
||||
import {
|
||||
list as todoList,
|
||||
taskNodeInfo,
|
||||
taskEntrust,
|
||||
taskClaim,
|
||||
taskComplaint,
|
||||
} from '/@/views/super/bpm/process/personalOffice/myHandleTask/task.handle.api';
|
||||
import { list as ccList } from '/@/views/super/bpm/process/personalOffice/myCcTask/task.cc.api';
|
||||
|
||||
const router = useRouter();
|
||||
const activeTab = ref<'todo' | 'cc'>('todo');
|
||||
const todoTotal = ref(0);
|
||||
|
||||
const { tableContext: todoContext } = useListPage({
|
||||
designScope: 'process-portal-todo',
|
||||
pagination: true,
|
||||
tableProps: {
|
||||
api: (params) => todoList('run', params),
|
||||
columns: todoColumns,
|
||||
showIndexColumn: true,
|
||||
showTableSetting: false,
|
||||
canResize: false,
|
||||
scroll: { x: 900 },
|
||||
actionColumn: { dataIndex: 'action', fixed: 'right', width: 180 },
|
||||
useSearchForm: false,
|
||||
},
|
||||
});
|
||||
const [registerTodoTable, { reload: reloadTodo }] = todoContext;
|
||||
|
||||
const { tableContext: ccContext } = useListPage({
|
||||
designScope: 'process-portal-cc',
|
||||
pagination: true,
|
||||
tableProps: {
|
||||
api: ccList,
|
||||
columns: ccColumns,
|
||||
showIndexColumn: true,
|
||||
showTableSetting: false,
|
||||
canResize: false,
|
||||
scroll: { x: 900 },
|
||||
actionColumn: { dataIndex: 'action', fixed: 'right', width: 120 },
|
||||
useSearchForm: false,
|
||||
},
|
||||
});
|
||||
const [registerCcTable] = ccContext;
|
||||
|
||||
onMounted(() => {
|
||||
fetchTodoTotal();
|
||||
});
|
||||
|
||||
async function fetchTodoTotal() {
|
||||
try {
|
||||
const res = await todoList('run', { pageNo: 1, pageSize: 1 });
|
||||
todoTotal.value = res?.total ?? 0;
|
||||
} catch (e) {
|
||||
todoTotal.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function goMore() {
|
||||
if (activeTab.value === 'todo') {
|
||||
router.push('/task/myHandleTaskInfo');
|
||||
} else {
|
||||
router.push('/task/myCcTaskList');
|
||||
}
|
||||
}
|
||||
|
||||
const [registerHandleModal, { openModal: openHandleModal }] = useModal();
|
||||
const [registerEntrusterModal, { openModal: openEntrusterModal }] = useModal();
|
||||
const [registerComplaintModal, { openModal: openComplaintModal }] = useModal();
|
||||
|
||||
async function handleProcess(record) {
|
||||
let { formData, formUrl } = await getTaskNodeInfo(record);
|
||||
formData['PROCESS_TAB_TYPE'] = 'run';
|
||||
openHandleModal(true, { formData, formUrl, title: '流程办理' });
|
||||
}
|
||||
|
||||
async function getTaskNodeInfo(record) {
|
||||
let params = { taskId: record.id };
|
||||
const result = await taskNodeInfo('run', params);
|
||||
let procInsId = record.processInstanceId || (result.records ? result.records.BPM_INST_ID : '');
|
||||
let realDataId = (result.records && result.records.business_id) || result.dataId;
|
||||
let realTableName = (result.records && result.records.business_tablename) || result.tableName;
|
||||
let formData: any = {
|
||||
taskId: record.id,
|
||||
taskDefKey: result.taskDefKey,
|
||||
procInsId: procInsId,
|
||||
dataId: realDataId,
|
||||
tableName: realTableName,
|
||||
processDataId: result.dataId,
|
||||
processTableName: result.tableName,
|
||||
permissionList: result.permissionList,
|
||||
subPermissionList: result.subPermissionList,
|
||||
vars: result.records,
|
||||
};
|
||||
let tempFormUrl = result.formUrl;
|
||||
if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !/^http[s]?:\/\/.*/.test(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) {
|
||||
tempFormUrl = result.formUrl.split('?')[0];
|
||||
}
|
||||
return { formData, formUrl: tempFormUrl };
|
||||
}
|
||||
|
||||
async function handleClaim(record) {
|
||||
await taskClaim({ taskId: record.id });
|
||||
await reloadTodo();
|
||||
await fetchTodoTotal();
|
||||
}
|
||||
|
||||
function handleSelectEntruster(record) {
|
||||
openEntrusterModal(true, { taskId: record.id });
|
||||
}
|
||||
|
||||
function handleComplaintEntruster(record) {
|
||||
openComplaintModal(true, { taskId: record.id });
|
||||
}
|
||||
|
||||
async function selectedEntruster(params) {
|
||||
await taskEntrust(params);
|
||||
await reloadTodo();
|
||||
}
|
||||
|
||||
async function selectedComplaint(params) {
|
||||
await taskComplaint(params);
|
||||
await reloadTodo();
|
||||
}
|
||||
|
||||
async function showCcHistory(record) {
|
||||
let { formData, formUrl } = await getTaskInfoForHistory(record);
|
||||
formData['PROCESS_TAB_TYPE'] = 'history';
|
||||
openHandleModal(true, { formData, formUrl, title: '流程历史' });
|
||||
}
|
||||
|
||||
function getTodoTableAction(record) {
|
||||
let arr = [];
|
||||
if (record.taskAssigneeId && record.taskAssigneeId != '') {
|
||||
arr.push({ label: '办理', onClick: handleProcess.bind(null, record) });
|
||||
} else {
|
||||
arr.push({
|
||||
label: '签收',
|
||||
popConfirm: {
|
||||
title: '确定签收吗?',
|
||||
placement: 'left',
|
||||
confirm: handleClaim.bind(null, record),
|
||||
},
|
||||
});
|
||||
}
|
||||
arr.push({ label: '转办', onClick: handleComplaintEntruster.bind(null, record) });
|
||||
arr.push({ label: '委托', onClick: handleSelectEntruster.bind(null, record) });
|
||||
return arr;
|
||||
}
|
||||
|
||||
function getCcTableAction(record) {
|
||||
return [{ label: '查看审批', onClick: showCcHistory.bind(null, record) }];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.my-work {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__tab {
|
||||
padding: 5px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
&__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
border-radius: 9px;
|
||||
line-height: 1;
|
||||
|
||||
&--danger {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&__more {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__more-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 12px 20px;
|
||||
|
||||
:deep(.ant-table) {
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-table-thead > tr > th {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
padding: 10px 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<a-row :gutter="16" class="stat-cards">
|
||||
<a-col :xs="24" :sm="12" :lg="6" v-for="item in statList" :key="item.title">
|
||||
<div class="stat-card" hoverable>
|
||||
<div class="stat-card__header">
|
||||
<span class="stat-card__label">{{ item.title }}</span>
|
||||
<div class="stat-card__icon-wrap" :style="{ background: item.bgColor }">
|
||||
<component :is="item.icon" :style="{ color: item.iconColor, fontSize: '18px' }" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card__footer">
|
||||
<div class="stat-card__value-group">
|
||||
<span class="stat-card__value">{{ item.value }}</span>
|
||||
<span class="stat-card__change" :class="'stat-card__change--' + item.trend">
|
||||
<CaretUpOutlined v-if="item.trend === 'up'" />
|
||||
<CaretDownOutlined v-if="item.trend === 'down'" />
|
||||
{{ item.change }}
|
||||
</span>
|
||||
</div>
|
||||
<svg class="stat-card__sparkline" width="80" height="32" :viewBox="'0 0 80 32'">
|
||||
<defs>
|
||||
<linearGradient :id="'grad-' + item.key" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" :stop-color="item.sparkColor" stop-opacity="0.3" />
|
||||
<stop offset="100%" :stop-color="item.sparkColor" stop-opacity="0.05" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<polygon :points="item.fillPoints" :fill="'url(#grad-' + item.key + ')'" />
|
||||
<path :d="item.pathD" fill="none" :stroke="item.sparkColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
FileTextOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
WarningOutlined,
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
function buildSparkline(data: number[]) {
|
||||
const max = Math.max(...data);
|
||||
const min = Math.min(...data);
|
||||
const range = max - min || 1;
|
||||
const width = 80;
|
||||
const height = 32;
|
||||
const pad = 2;
|
||||
const points = data.map((v, i) => {
|
||||
const x = pad + (i / (data.length - 1)) * (width - pad * 2);
|
||||
const y = height - pad - ((v - min) / range) * (height - pad * 2);
|
||||
return `${x},${y}`;
|
||||
});
|
||||
const pathD = `M ${points.join(' L ')}`;
|
||||
const fillPoints = [`${pad},${height - pad}`, ...points, `${width - pad},${height - pad}`].join(' ');
|
||||
return { pathD, fillPoints };
|
||||
}
|
||||
|
||||
const statList = [
|
||||
{
|
||||
key: 'todo',
|
||||
title: '待办事项',
|
||||
value: '28',
|
||||
change: '+4',
|
||||
trend: 'up',
|
||||
icon: FileTextOutlined,
|
||||
bgColor: 'rgba(24, 144, 255, 0.1)',
|
||||
iconColor: '#1890ff',
|
||||
sparkColor: '#3b82f6',
|
||||
...buildSparkline([20, 22, 18, 24, 21, 26, 28]),
|
||||
},
|
||||
{
|
||||
key: 'done',
|
||||
title: '本月已办',
|
||||
value: '342',
|
||||
change: '+18.2%',
|
||||
trend: 'up',
|
||||
icon: CheckCircleOutlined,
|
||||
bgColor: 'rgba(82, 196, 26, 0.1)',
|
||||
iconColor: '#52c41a',
|
||||
sparkColor: '#22c55e',
|
||||
...buildSparkline([280, 295, 310, 305, 320, 335, 342]),
|
||||
},
|
||||
{
|
||||
key: 'avg',
|
||||
title: '平均时长',
|
||||
value: '2.4h',
|
||||
change: '-0.6h',
|
||||
trend: 'down',
|
||||
icon: ClockCircleOutlined,
|
||||
bgColor: 'rgba(168, 85, 247, 0.1)',
|
||||
iconColor: '#a855f7',
|
||||
sparkColor: '#a855f7',
|
||||
...buildSparkline([3.2, 3.0, 2.8, 2.9, 2.6, 2.5, 2.4]),
|
||||
},
|
||||
{
|
||||
key: 'warn',
|
||||
title: '超时预警',
|
||||
value: '6',
|
||||
change: '+2',
|
||||
trend: 'up',
|
||||
icon: WarningOutlined,
|
||||
bgColor: 'rgba(255, 77, 79, 0.1)',
|
||||
iconColor: '#ff4d4f',
|
||||
sparkColor: '#ef4444',
|
||||
...buildSparkline([3, 4, 2, 5, 4, 5, 6]),
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.stat-cards {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
transition: box-shadow 0.2s;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
&__change {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
&--up {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&--down {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
&__sparkline {
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="welcome-banner">
|
||||
<div class="welcome-banner__deco welcome-banner__deco--top"></div>
|
||||
<div class="welcome-banner__deco welcome-banner__deco--bottom"></div>
|
||||
<div class="welcome-banner__content">
|
||||
<div class="welcome-banner__left">
|
||||
<h2 class="welcome-banner__title">{{ greeting }},{{ userinfo.realname }}<span class="welcome-banner__wave">👋</span></h2>
|
||||
<p class="welcome-banner__desc">
|
||||
今天有 <span class="welcome-banner__highlight">8 项待办</span>、
|
||||
<span class="welcome-banner__warn">2 项即将超时</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="welcome-banner__right">
|
||||
<div class="welcome-banner__date-btn">
|
||||
<CalendarOutlined />
|
||||
<span>{{ formattedDate }}</span>
|
||||
</div>
|
||||
<div class="welcome-banner__time-btn">
|
||||
<ClockCircleOutlined />
|
||||
<span>{{ formattedTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { CalendarOutlined, ClockCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const userinfo = computed(() => userStore.getUserInfo);
|
||||
|
||||
const now = ref(new Date());
|
||||
let timer: ReturnType<typeof setInterval>;
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
now.value = new Date();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer);
|
||||
});
|
||||
|
||||
const greeting = computed(() => {
|
||||
const hour = now.value.getHours();
|
||||
if (hour < 6) return '凌晨好';
|
||||
if (hour < 9) return '早上好';
|
||||
if (hour < 12) return '上午好';
|
||||
if (hour < 14) return '中午好';
|
||||
if (hour < 18) return '下午好';
|
||||
return '晚上好';
|
||||
});
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
const d = now.value;
|
||||
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} 周${weekDays[d.getDay()]}`;
|
||||
});
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
const d = now.value;
|
||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.welcome-banner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 28px 32px;
|
||||
color: #fff;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
|
||||
|
||||
&__deco {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
|
||||
&--top {
|
||||
top: -60px;
|
||||
right: -40px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
filter: blur(40px);
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
bottom: -40px;
|
||||
left: -30px;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
filter: blur(30px);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 10px 0;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__wave {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__highlight {
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__warn {
|
||||
font-weight: 600;
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__date-btn,
|
||||
&__time-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(4px);
|
||||
cursor: default;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
|
||||
export const ccColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '业务标题',
|
||||
align: 'center',
|
||||
dataIndex: 'bpmBizTitle',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
align: 'center',
|
||||
width: 260,
|
||||
dataIndex: 'taskName',
|
||||
},
|
||||
{
|
||||
title: '发起人',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
dataIndex: 'processApplyUserName',
|
||||
},
|
||||
{
|
||||
title: '办理人',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
dataIndex: 'taskAssigneeName',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,43 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
export const todoColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '业务标题',
|
||||
align: 'center',
|
||||
dataIndex: 'bpmBizTitle',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '当前环节',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
dataIndex: 'taskName',
|
||||
},
|
||||
{
|
||||
title: '转入时间',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
dataIndex: 'taskBeginTime',
|
||||
},
|
||||
{
|
||||
title: '事项截止时间',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
dataIndex: 'deadline',
|
||||
customRender: ({ text, record }) => {
|
||||
const value = !text ? '' : text.length > 10 ? text.substr(0, 10) : text;
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
if (record.deadlineOverdue) {
|
||||
return h(Tag, { color: 'error' }, () => (record.deadlineWarnText ? `${value} ${record.deadlineWarnText}` : value));
|
||||
}
|
||||
if (record.deadlineSoon) {
|
||||
return h(Tag, { color: 'warning' }, () => (record.deadlineWarnText ? `${value} ${record.deadlineWarnText}` : value));
|
||||
}
|
||||
return value;
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<template>
|
||||
<div class="process-portal">
|
||||
<WelcomeBanner />
|
||||
<StatCards />
|
||||
<MyWork />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import WelcomeBanner from './components/WelcomeBanner.vue';
|
||||
import StatCards from './components/StatCards.vue';
|
||||
import MyWork from './components/MyWork.vue';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.process-portal {
|
||||
padding: 12px 16px;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user