初始化版本,引入智慧食堂模块,第三方模块

This commit is contained in:
zjf committed 2024-12-06 18:48:18 +08:00
commit d22f88efd3
677 files changed
+69341

No files matched your search

@@ -0,0 +1,56 @@
<script lang="ts">
export default {
title: '审计日志',
icon: 'DocumentCopy',
description: '审计日志列表',
};
</script>
<template>
<el-card class="box-card h-96">
<template #header>
<div class="card-header">
<span>{{ $t('home.auditLogsTip') }}</span>
<el-button link class="button" text @click="handleRoutr">{{ $t('home.moreTip') }}</el-button>
</div>
</template>
<el-timeline v-if="auditState.dataList.length > 0">
<el-timeline-item v-for="(item, index) in auditState.dataList" :key="index" :timestamp="item.createTime">
{{ item.createBy }} : {{ item.auditField }} {{ item.afterVal }} => {{ item.beforeVal }}
</el-timeline-item>
</el-timeline>
<el-empty :image-size="120" v-else />
</el-card>
</template>
<script setup lang="ts" name="SysAuditLogDashboard">
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/admin/audit';
const router = useRouter();
// 创建基本表格参数对象
const auditState: BasicTableProps = reactive({
queryForm: {},
pageList: fetchList,
descs: ['create_time'],
pagination: {
size: 4, // 每页显示数据量
},
});
// 使用实例
useTable(auditState);
// 跳转路由
const handleRoutr = () => {
router.push('/admin/audit/index');
};
</script>
<style scoped lang="scss">
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
@@ -0,0 +1,186 @@
<script lang="ts">
export default {
title: '日程管理',
icon: 'Calendar',
description: '日历组件展示',
};
</script>
<template>
<el-card class="h-96 box-card">
<Calendar
view="weekly"
:locale="locale"
:attributes="reminders"
ref="calendar"
title-position="center"
@did-move="weeknumberClick"
@dayclick="dayClick"
:masks="masks"
v-bind="themeConfig?.isDark ? { 'is-dark': true } : {}"
transparent
borderless
expanded
/>
<div v-if="calendar" class="py-4 px-6 w-full h-[18rem] overflow-y-auto">
<template v-for="{ day, cells } in Object.values(dayCells)">
<ul v-if="cells.length > 0" class="py-2 space-y-2" :key="day">
<li v-for="cell in cells" :key="cell">
<div class="flex items-center space-x-4">
<!--Icon-->
<div class="flex-grow-0 flex-shrink-0">
<div :class="`flex justify-center items-center w-10 h-10 rounded-lg bg-blue-100 text-blue-500 dark:bg-blue-400 dark:text-white`">
<el-icon>
<CircleCheck />
</el-icon>
</div>
</div>
<div class="flex items-center justify-between flex-grow">
<div>
<p class="font-medium">
{{ cell.data.customData.title }}
</p>
<p class="text-xs font-medium text-gray-400 dark:text-gray-400 leading-2">
{{ cell.data.customData.scheduleDate }} {{ cell.data.customData.scheduleTime }}
</p>
</div>
</div>
<el-switch @change="changeSwitch(cell.data.customData.id)"></el-switch>
</div>
</li>
</ul>
</template>
<el-empty :image-size="120" v-if="reminders.length === 0" class="text-center" />
</div>
<!-- 新增日程的表单 -->
<schedule-form ref="scheduleFormRef" @refresh="initscheduleList()" />
<!-- 日程查询 -->
<schedule ref="scheduleRef" @refresh="initscheduleList()" />
</el-card>
</template>
<script setup lang="ts" name="scheduleCalendar">
import { Calendar } from 'v-calendar';
import 'v-calendar/style.css';
import { useThemeConfig } from '/@/stores/themeConfig';
import { parseDate } from '/@/utils/formatTime';
import { list, putObj } from '/@/api/admin/schedule';
const ScheduleForm = defineAsyncComponent(() => import('/@/views/home/schedule/form.vue'));
const Schedule = defineAsyncComponent(() => import('/@/views/home/schedule/index.vue'));
// 获取当前国际化方言
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const locale = computed(() => {
return themeConfig.value.globalI18n;
});
const masks = ref({
weekdays: 'WWW',
});
const reminders = ref([]);
const calendar = ref<any>(null);
const scheduleFormRef = ref();
const scheduleRef = ref();
// 添加类型定义
interface DayCell {
day: string;
cells: Array<{
data: {
customData: {
id: string;
title: string;
scheduleDate: string;
scheduleTime: string;
};
};
}>;
}
// 修改 dayCells 的类型定义
const dayCells = computed<Record<string, DayCell>>(() => {
if (!calendar.value) return {};
return calendar.value.dayCells || {};
});
// 日期范围的开始日期引用
const startDateRef = ref();
// 日期范围的结束日期引用
const endDateRef = ref();
// 点击周数的回调函数
const weeknumberClick = (page: any) => {
// 获取当前周的第一天和最后一天的日期
const startDate = page[0].viewDays[0].id;
const endDate = page[0].viewDays[6].id;
// 更新开始日期引用和结束日期引用
startDateRef.value = startDate;
endDateRef.value = endDate;
// 初始化日程列表
initscheduleList();
};
//处理日程安排事件,若当前日期下没有日程则打开表单对话框,否则打开日程详情页面
const dayClick = (day: any) => {
if (filterCellSelected(day.id)) {
scheduleRef.value.open({ date: parseDate(day.id, null) });
} else {
scheduleFormRef.value.openDialog(null, { date: parseDate(day.id, null) });
}
};
// 修改开关状态
const changeSwitch = async (id: string) => {
// 修改对象的状态为'3'
await putObj({ id: id, scheduleState: '3' });
// 初始化调度列表
initscheduleList();
};
const initscheduleList = () => {
// 初始化日程列表
list({
startDate: startDateRef.value,
endDate: endDateRef.value,
}).then((res) => {
// 获取返回结果的数据并转换为合适的格式
reminders.value = res.data.map((item: any) => {
return {
key: item.id,
highlight: {
color: 'primary',
fillMode: 'outline',
},
dates: item.scheduleDate,
customData: item,
};
});
});
};
// 过滤日历中选中的单元格是否有日程
const filterCellSelected = (day: string) => {
return (
reminders.value.filter((item: any) => {
return item.dates.includes(day);
}).length > 0
);
};
onMounted(() => {
initscheduleList();
});
</script>
<style scoped lang="scss">
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
@@ -0,0 +1,78 @@
<script lang="ts">
export default {
title: '当前用户',
icon: 'User',
description: '获取当前用户信息展示',
};
</script>
<template>
<el-card class="flex items-center justify-center h-[191px]">
<div class="flex items-center justify-center">
<div class="relative">
<img class="object-cover w-16 h-16 rounded-full" :src="userData.avatar" alt="Avatar" />
<div class="absolute inset-0 rounded-full shadow-inner"></div>
</div>
<div class="ml-8">
<h2 class="text-lg font-bold dark:text-gray-100">{{ userData.name }}</h2>
<p class="text-gray-600 dark:text-gray-400">{{ userData.deptName }} | {{ userData.postName }}</p>
</div>
</div>
</el-card>
</template>
<script setup lang="ts" name="currentUser">
import { useUserInfo } from '/@/stores/userInfo';
import { getObj } from '/@/api/admin/user';
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
const proxy = instance?.proxy;
if (!proxy) {
throw new Error('Failed to get component instance');
}
const userData = ref({
postName: '',
name: '',
username: '',
userId: '',
avatar: '',
deptName: '',
} as any);
const loading = ref(false);
onMounted(() => {
const data = useUserInfo().userInfos;
initUserInfo(data.user.userId);
});
/**
* 根据用户 ID 初始化用户信息。
* @param {any} userId - 要查询的用户 ID。
* @returns {Promise<void>} - 初始化用户信息的 Promise 实例。
*/
const initUserInfo = async (userId: any): Promise<void> => {
try {
loading.value = true; // 显示加载状态
const res = await getObj(userId); // 执行查询操作
userData.value = res.data; // 将查询到的数据保存到 userData 变量中
userData.value.postName = res.data?.postList?.map((item: any) => item.postName).join(',') || ''; // 将 postList 中的 postName 合并成字符串并保存到 userData 变量中
// 文件上传增加后端前缀
userData.value.avatar = proxy.baseURL + res.data.avatar;
} finally {
loading.value = false; // 结束加载状态
}
};
</script>
<style scoped>
.info {
margin-left: 8px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
@@ -0,0 +1,99 @@
<script lang="ts">
export default {
title: '示例图表1',
icon: 'DocumentCopy',
description: '示例图表无意义,可删除',
};
</script>
<template>
<el-card class="relative h-full">
<v-chart class="w-full h-80" :option="option"/>
</el-card>
</template>
<script setup lang="ts" name="demo-chart1">
import VChart from 'vue-echarts';
import {use} from 'echarts/core';
import {PieChart} from 'echarts/charts';
import {TooltipComponent, LegendComponent} from 'echarts/components';
import {CanvasRenderer} from 'echarts/renderers';
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
import {useMessage} from "/@/hooks/message";
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer]);
const option = reactive({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
},
legend: {
data: ['Java17', 'Java8', 'Other'],
},
series: [
{
name: 'Java',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14,
},
labelLine: {
show: false,
},
data: [
{value: 1548, name: 'Java17'},
{value: 775, name: 'Java8'},
{value: 679, name: 'Other', selected: true},
],
},
{
name: '使用率',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30,
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center',
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0,
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33,
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4,
},
},
},
data: [
{value: 1048, name: 'Java17'},
{value: 335, name: 'Java8'},
{value: 310, name: 'Other'},
],
},
],
});
</script>
@@ -0,0 +1,55 @@
<script lang="ts">
export default {
title: '示例图表2',
icon: 'DocumentCopy',
description: '示例图表无意义,可删除',
};
</script>
<template>
<el-card class="relative h-full">
<v-chart class="w-full h-80" :option="option" />
</el-card>
</template>
<script setup lang="ts" name="log-line-chart">
import VChart from 'vue-echarts';
import { use } from 'echarts/core';
import { ScatterChart } from 'echarts/charts';
import { TitleComponent, GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
use([TitleComponent, ScatterChart, CanvasRenderer, GridComponent]);
const option = reactive({
xAxis: {},
yAxis: {},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68],
],
type: 'scatter',
},
],
});
</script>
@@ -0,0 +1,14 @@
<script lang="ts">
export default {
title: '常用流程',
icon: 'Star',
description: '常用流程收藏',
};
</script>
<template>
<Shortcut :title="$t('home.quickNavigationFlowsTip')" :empty-description="$t('home.addFlowsRoutesTip')" type="flow"></Shortcut>
</template>
<script setup lang="ts" name="SysFavoriteDashboard">
const Shortcut = defineAsyncComponent(() => import('/@/views/home/shortcut/index.vue'));
</script>
@@ -0,0 +1,14 @@
<script lang="ts">
export default {
title: '常用功能',
icon: 'Star',
description: '常用功能收藏',
};
</script>
<template>
<Shortcut :title="$t('home.quickNavigationToolsTip')" :empty-description="$t('home.addFavoriteRoutesTip')" type="menu"></Shortcut>
</template>
<script setup lang="ts" name="SysFavoriteDashboard">
const Shortcut = defineAsyncComponent(() => import('/@/views/home/shortcut/index.vue'));
</script>
@@ -0,0 +1,95 @@
<script lang="ts">
export default {
title: '工作流信息',
icon: 'PieChart',
description: '获取工作流信息展示',
};
</script>
<template>
<el-card class="flex items-center justify-center h-[191px]">
<div class="relative flex items-center justify-center">
<!-- 待办任务 -->
<div class="flex-row items-center w-auto">
<div class="p-3 rounded-full bg-lightPrimary dark:bg-navy-700">
<span class="flex items-center text-brand-500 dark:text-white">
<svg
t="1697994355915"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="8441"
width="32"
height="32"
>
<path
d="M404.266667 85.333333v78.933334h236.8V85.333333h78.933333v78.933334h157.866667A39.466667 39.466667 0 0 1 917.333333 203.733333v631.466667A39.466667 39.466667 0 0 1 877.866667 874.666667H167.466667A39.466667 39.466667 0 0 1 128 835.2V203.733333A39.466667 39.466667 0 0 1 167.466667 164.266667h157.866666V85.333333h78.933334zM838.4 361.6H206.933333V795.733333h631.466667V361.6z m-195.904 84.309333l55.808 55.786667-195.370667 195.370667-139.562666-139.562667 55.893333-55.808 83.712 83.754667 139.541333-139.541334h-0.021333z"
fill="#8a8a8a"
p-id="8442"
></path>
</svg>
</span>
</div>
</div>
<router-link to="/flow/task/pending">
<div class="flex flex-col justify-center w-auto ml-4 h-50">
<p class="text-sm font-medium text-gray-600 font-dm">{{ $t('home.pendingTask') }}</p>
<h4 class="text-xl font-bold text-navy-700 dark:text-white">{{ state.pendingNum }}</h4>
</div>
</router-link>
<!-- 抄送任务 -->
<div class="flex-row items-center w-auto ml-8">
<div class="p-3 rounded-full bg-lightPrimary dark:bg-navy-700">
<span class="flex items-center text-brand-500 dark:text-white">
<svg
t="1697994410210"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9746"
width="32"
height="32"
>
<path
d="M382.293 692.497a37.956 37.956 0 0 1-32.29 7.373v0.205l-278.118-84.31a39.39 39.39 0 0 1-5.803-73.113l862.14-420.045a39.39 39.39 0 0 1 55.432 45.056L823.228 800.358a39.39 39.39 0 0 1-49.63 27.99l-295.595-89.566a15.428 15.428 0 0 1-12.083-14.814c0-4.437 1.98-8.397 5.12-11.196l356.762-401.135-445.44 380.792h-0.069z m98.031 93.867l54 17.886a15.428 15.428 0 0 1 7.986 23.142l-53.93 81.92a15.428 15.428 0 0 1-28.33-8.533v-99.806a15.428 15.428 0 0 1 20.274-14.61z"
p-id="9747"
fill="#8a8a8a"
></path>
</svg>
</span>
</div>
</div>
<router-link to="/flow/task/cc">
<div class="flex flex-col justify-center w-auto ml-4 h-50">
<p class="text-sm font-medium text-gray-600 font-dm">{{ $t('home.copyTask') }}</p>
<h4 class="text-xl font-bold text-navy-700 dark:text-white">{{ state.copyNum }}</h4>
</div>
</router-link>
</div>
</el-card>
</template>
<script setup lang="ts" name="flowData">
import { queryTaskData } from '/@/api/flow/task';
const state = reactive({
pendingNum: 0,
copyNum: 0,
});
onMounted(async () => {
try {
const { data } = await queryTaskData();
state.pendingNum = Number.parseInt(data?.pendingNum || 0);
state.copyNum = Number.parseInt(data?.copyNum || 0);
} catch (error) {
// 避免没有启动 flow模块 vue 组件渲染 warning
}
});
</script>
<style scoped>
.el-col {
text-align: center;
}
</style>
@@ -0,0 +1,12 @@
const resultComps = {};
const files = import.meta.glob('./*.vue', { import: 'default', eager: true });
Object.keys(files).forEach((fileName) => {
if (fileName) {
// @ts-ignore
resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = files[fileName];
}
});
export default markRaw(resultComps);
@@ -0,0 +1,73 @@
<script lang="ts">
export default {
title: '系统公告',
icon: 'MuteNotification',
description: '系统公告展示',
};
</script>
<template>
<el-card class="h-96">
<template #header>
<div class="card-header">
<span>{{ $t('home.newsletterTip') }}</span>
<el-button link class="button" text @click="openList">{{ $t('home.moreTip') }}</el-button>
</div>
</template>
<el-timeline v-if="newsList.length > 0">
<div class="relative">
<el-timeline-item v-for="(item, index) in newsList" :key="index" :timestamp="item.createTime" @click="contentRef.openDialog(item)">
<button class="absolute right-0 px-3 rotate-[10deg] -top-2 border bg-primary text-white font-bold">
{{ item.readFlag === '1' ? $t('msg.readed') : $t('msg.unread') }}
</button>
<div class="p-2 border border-gray-400 purple_border">
{{ item.title }}
</div>
</el-timeline-item>
</div>
</el-timeline>
<el-empty :image-size="120" v-else />
</el-card>
<!-- 消息列表 -->
<news-lists ref="listRef"/>
<!-- 消息内容 -->
<news-content ref="contentRef" @refresh="getUserMessage" />
</template>
<script setup lang="ts" name="newsLetter">
import { fetchUserMessageList } from '/@/api/admin/message';
const NewsContent = defineAsyncComponent(() => import('/@/views/home/news/content.vue'));
const NewsLists = defineAsyncComponent(() => import('/@/views/home/news/list.vue'));
const listRef = ref();
const contentRef = ref();
const visible = ref(false);
const showList = ref(false);
const newsList = ref([]);
// 获取用户的信息
const getUserMessage = () => {
// 取前三条数据
return fetchUserMessageList({ current: 1, size: 3, category: '0' }).then((res) => {
newsList.value = res.data.records;
});
};
const openList = () => {
showList.value = true;
listRef.value.openDialog('0');
};
onMounted(() => {
getUserMessage();
});
</script>
<style scoped lang="scss">
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
@@ -0,0 +1,15 @@
<script lang="ts">
export default {
title: '请求折线图',
icon: 'DocumentCopy',
description: '折线图示例',
};
</script>
<template>
<el-card class="relative h-full">
<log-line-chart />
</el-card>
</template>
<script setup lang="ts" name="line-chart">
const LogLineChart = defineAsyncComponent(() => import('/@/views/admin/log/line-chart.vue'));
</script>
@@ -0,0 +1,57 @@
<script lang="ts">
export default {
title: '系统日志',
icon: 'Monitor',
description: '系统日志列表',
};
</script>
<template>
<el-card class="h-96 box-card">
<template #header>
<div class="card-header">
<span>{{ $t('home.systemLogsTip') }}</span>
<el-button link class="button" text @click="handleRoutr">{{ $t('home.moreTip') }}</el-button>
</div>
</template>
<el-timeline v-if="logState.dataList.length > 0">
<el-timeline-item v-for="(item, index) in logState.dataList" :key="index" :timestamp="item.createTime">
{{ item.title }} - {{ item.remoteAddr }}
</el-timeline-item>
</el-timeline>
</el-card>
</template>
<script setup lang="ts" name="SysLogDashboard">
import { BasicTableProps, useTable } from '/@/hooks/table';
import { pageList } from '/@/api/admin/log';
const router = useRouter();
// 创建基本表格参数对象
const logState: BasicTableProps = reactive({
pageList, // 分页列表数据
descs: ['create_time'], // 排序方式
pagination: {
size: 4, // 每页显示数据量
},
});
// 使用实例
useTable(logState);
/**
* 处理路由跳转事件
* @function
*/
const handleRoutr = () => {
router.push('/admin/log/index'); // 跳转到日志管理页面
};
</script>
<style scoped lang="scss">
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>