init
This commit is contained in:
50
src/views/home/i18n/en.ts
Normal file
50
src/views/home/i18n/en.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export default {
|
||||
home: {
|
||||
addFavoriteRoutesTip: 'no data , right click on the tab to favorite',
|
||||
addFlowsRoutesTip: 'no data, please select in Process management to save',
|
||||
quickNavigationToolsTip: 'quick navigation bar',
|
||||
quickNavigationFlowsTip: 'quick flow bar',
|
||||
systemLogsTip: 'system logs',
|
||||
auditLogsTip: 'audit logs',
|
||||
moreTip: 'more',
|
||||
newsletterTip: 'news letter',
|
||||
pendingTask: 'pending task✍️',
|
||||
copyTask: 'copy task🔖',
|
||||
schedule: 'schedule',
|
||||
reminders: 'Reminders',
|
||||
},
|
||||
msg: {
|
||||
readed: 'readed',
|
||||
unread: 'unread',
|
||||
},
|
||||
schedule: {
|
||||
index: '#',
|
||||
importsysScheduleTip: 'import SysSchedule',
|
||||
id: 'id',
|
||||
title: 'title',
|
||||
type: 'type',
|
||||
state: 'state',
|
||||
content: 'content',
|
||||
time: 'time',
|
||||
date: 'date',
|
||||
createBy: 'createBy',
|
||||
createTime: 'createTime',
|
||||
updateBy: 'updateBy',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputTitleTip: 'input title',
|
||||
inputTypeTip: 'input type',
|
||||
inputStateTip: 'input state',
|
||||
inputContentTip: 'input content',
|
||||
inputTimeTip: 'input time',
|
||||
inputDateTip: 'input date',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
50
src/views/home/i18n/zh-cn.ts
Normal file
50
src/views/home/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export default {
|
||||
home: {
|
||||
addFavoriteRoutesTip: '当前无数据,请在标签页上右键单击以收藏',
|
||||
addFlowsRoutesTip: '当前无数据,请在流程管理选择以收藏',
|
||||
quickNavigationToolsTip: '常用功能',
|
||||
quickNavigationFlowsTip: '常用流程',
|
||||
systemLogsTip: '系统日志',
|
||||
auditLogsTip: '审计日志',
|
||||
moreTip: '更多',
|
||||
newsletterTip: '公告',
|
||||
pendingTask: '待办任务✍️',
|
||||
copyTask: '抄送任务🔖',
|
||||
schedule: '日程',
|
||||
reminders: '提醒',
|
||||
},
|
||||
msg: {
|
||||
readed: '已读',
|
||||
unread: '未读',
|
||||
},
|
||||
schedule: {
|
||||
index: '#',
|
||||
importsysScheduleTip: '导入日程',
|
||||
id: 'id',
|
||||
title: '标题',
|
||||
type: '类型',
|
||||
state: '状态',
|
||||
content: '内容',
|
||||
time: '时间',
|
||||
date: '日期',
|
||||
createBy: '创建人',
|
||||
createTime: '创建时间',
|
||||
updateBy: '修改人',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '删除标记',
|
||||
tenantId: '租户ID',
|
||||
inputIdTip: '请输入id',
|
||||
inputTitleTip: '请输入标题',
|
||||
inputTypeTip: '请输入日程类型',
|
||||
inputStateTip: '请输入状态',
|
||||
inputContentTip: '请输入内容',
|
||||
inputTimeTip: '请输入时间',
|
||||
inputDateTip: '请输入日期',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入删除标记',
|
||||
inputTenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
||||
24
src/views/home/index.vue
Normal file
24
src/views/home/index.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="pageLoading">
|
||||
<el-main>
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :rows="1"></el-skeleton>
|
||||
</el-card>
|
||||
<el-card shadow="never" style="margin-top: 15px;">
|
||||
<el-skeleton></el-skeleton>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</div>
|
||||
<widgets/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="dashboard">
|
||||
const Widgets = defineAsyncComponent(() => import('./widgets/index.vue'));
|
||||
const pageLoading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
pageLoading.value = false;
|
||||
});
|
||||
</script>
|
||||
43
src/views/home/news/content.vue
Normal file
43
src/views/home/news/content.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<!-- 消息内容 -->
|
||||
<el-drawer v-model="visible" size="40%">
|
||||
<div class="flex items-center justify-center -mt-8">
|
||||
<div class="max-w-md w-full p-6 bg-white rounded-lg shadow-lg">
|
||||
<h1 class="text-2xl font-semibold text-center text-gray-500 mb-6">{{ currentNew.title }}</h1>
|
||||
<div class="text-center">
|
||||
<p class="text-xs text-gray-600 text-center mt-8">© {{ currentNew.createBy }} {{ currentNew.createTime }}</p>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 text-justify mt-8 mb-6" v-html="currentNew.content"></p>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="newsLetter">
|
||||
import {readUserMessage} from "/@/api/admin/message";
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const currentNew = ref()
|
||||
const visible = ref(false)
|
||||
|
||||
// 打开信息内容
|
||||
const openDialog = (item: any) => {
|
||||
visible.value = true
|
||||
currentNew.value = item;
|
||||
readMessage(item)
|
||||
}
|
||||
|
||||
// 阅读事件
|
||||
const readMessage = async (item: any) => {
|
||||
if (item.readFlag === '1') {
|
||||
return
|
||||
}
|
||||
await readUserMessage({id: item.id})
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
53
src/views/home/news/list.vue
Normal file
53
src/views/home/news/list.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="40%">
|
||||
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" @sort-change="sortChangeHandle"
|
||||
@cell-click="cellClick">
|
||||
<el-table-column type="index" label="序号" width="60"/>
|
||||
<el-table-column prop="title" label="标题" show-overflow-tooltip/>
|
||||
<el-table-column prop="readFlag" label="状态" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag>{{ scope.row.readFlag === '1' ? $t('msg.readed') : $t('msg.unread') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="时间"/>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"/>
|
||||
|
||||
<!-- 消息内容 -->
|
||||
<news-content ref="contentRef" @refresh="getDataList"/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="newsList">
|
||||
import {BasicTableProps, useTable} from '/@/hooks/table';
|
||||
import {fetchUserMessageList} from '/@/api/admin/message';
|
||||
|
||||
const NewsContent = defineAsyncComponent(() => import('./content.vue'));
|
||||
|
||||
// 搜索变量
|
||||
const contentRef = ref()
|
||||
const visible = ref(false);
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {category: '0'},
|
||||
pageList: fetchUserMessageList,
|
||||
});
|
||||
const {getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle} = useTable(state);
|
||||
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (type: string) => {
|
||||
state.queryForm.category = type
|
||||
getDataList();
|
||||
visible.value = true
|
||||
};
|
||||
|
||||
const cellClick = (row: any) => {
|
||||
contentRef.value.openDialog(row)
|
||||
}
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
</script>
|
||||
155
src/views/home/schedule/form.vue
Normal file
155
src/views/home/schedule/form.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible">
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.title')" prop="title">
|
||||
<el-input v-model="form.title" :placeholder="t('schedule.inputTitleTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.type')" prop="scheduleType">
|
||||
<el-select v-model="form.scheduleType" :placeholder="t('schedule.inputTypeTip')" clearable default-first-option>
|
||||
<el-option v-for="item in schedule_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.state')" prop="scheduleState">
|
||||
<el-select v-model="form.scheduleState" :placeholder="t('schedule.inputStateTip')" clearable default-first-option>
|
||||
<el-option v-for="item in schedule_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('schedule.date')" prop="scheduleDate">
|
||||
<el-date-picker type="date" :placeholder="t('schedule.inputDateTip')" v-model="form.scheduleDate" :value-format="dateStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('schedule.time')" prop="scheduleTime">
|
||||
<el-time-picker v-model="form.scheduleTime" arrow-control :placeholder="t('schedule.inputTimeTip')" :value-format="timeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.content')" prop="content">
|
||||
<editor v-model:get-html="form.content" :placeholder="t('schedule.inputContentTip')" :disable="form.id !== ''" v-if="visible" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SysScheduleDialog">
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import {addObj, getObj, putObj} from '/@/api/admin/schedule';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useDict} from '/@/hooks/dict';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 获取字典内容
|
||||
const { schedule_type, schedule_status } = useDict('schedule_type', 'schedule_status');
|
||||
|
||||
// 使用国际化实例
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
title: '',
|
||||
scheduleType: 'record',
|
||||
scheduleState: '0',
|
||||
content: '',
|
||||
scheduleTime: '',
|
||||
scheduleDate: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
|
||||
scheduleType: [{ required: true, message: '日程类型不能为空', trigger: 'blur' }],
|
||||
scheduleState: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
|
||||
scheduleTime: [{ required: true, message: '时间不能为空', trigger: 'blur' }],
|
||||
scheduleDate: [{ required: true, message: '日期不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开日程表单弹窗
|
||||
* @function
|
||||
* @param {string} id - 日程ID
|
||||
* @param {Object} row - 行数据
|
||||
*/
|
||||
const openDialog = (id: string, row: any) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => dataFormRef.value?.resetFields());
|
||||
|
||||
if (row?.date) {
|
||||
form.date = row.date;
|
||||
}
|
||||
|
||||
// 获取sysSchedule信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getsysScheduleData(id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单数据
|
||||
* @function
|
||||
* @async
|
||||
*/
|
||||
const onSubmit = async () => {
|
||||
// 验证表单是否符合规则
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
await (form.id ? putObj(form) : addObj(form));
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取sysSchedule数据
|
||||
* @function
|
||||
* @param {string} id - 日程ID
|
||||
*/
|
||||
const getsysScheduleData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true;
|
||||
getObj(id)
|
||||
.then((res: any) => Object.assign(form, res.data))
|
||||
.finally(() => (loading.value = false));
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({ openDialog });
|
||||
</script>
|
||||
170
src/views/home/schedule/index.vue
Normal file
170
src/views/home/schedule/index.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="日程管理" size="80%" @close="handleClose">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item :label="t('schedule.date')" prop="scheduleDate">
|
||||
<el-date-picker
|
||||
type="date"
|
||||
:placeholder="t('schedule.inputDateTip')"
|
||||
v-model="state.queryForm.scheduleDate"
|
||||
:value-format="dateStr"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('schedule.title')" prop="title">
|
||||
<el-input :placeholder="t('schedule.inputTitleTip')" v-model="state.queryForm.title" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" formDialogRef @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button formDialogRef icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog(null, state.queryForm)">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button formDialogRef :disabled="multiple" icon="Delete" type="primary" class="ml10" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="true"
|
||||
@exportExcel="exportExcel"
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" :label="t('schedule.index')" width="80" />
|
||||
<el-table-column prop="title" :label="t('schedule.title')" show-overflow-tooltip />
|
||||
<el-table-column prop="scheduleType" :label="t('schedule.type')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="schedule_type" :value="scope.row.scheduleType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scheduleState" :label="t('schedule.state')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="schedule_status" :value="scope.row.scheduleState"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scheduleDate" :label="t('schedule.date')" show-overflow-tooltip />
|
||||
<el-table-column prop="scheduleTime" :label="t('schedule.time')" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" :label="t('schedule.createBy')" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" :label="t('schedule.createTime')" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog(scope.row.id)">{{ $t('common.editBtn') }}</el-button>
|
||||
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">{{ $t('common.delBtn') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysSchedule">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/admin/schedule';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import DictTag from '/@/components/DictTag/index.vue';
|
||||
const { schedule_type, schedule_status } = useDict('schedule_type', 'schedule_status');
|
||||
const emit = defineEmits(['refresh']);
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const visible = ref(false);
|
||||
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
createdIsNeed: false,
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
state.queryForm.date = '';
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/job/schedule/export', state.queryForm, 'schedule.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
//关闭日程刷新首页日程数据
|
||||
const handleClose = () => {
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const open = (row: any) => {
|
||||
state.queryForm.date = row.date;
|
||||
getDataList();
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
86
src/views/home/shortcut/index.vue
Normal file
86
src/views/home/shortcut/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<el-card class="h-[191px] box-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ props.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="10" v-if="showRoutes.length > 0">
|
||||
<el-col class="shortcutCard" :span="6" :key="shortcut.id" v-for="shortcut in showRoutes">
|
||||
<SvgIcon name="ele-Close" :size="12" class="shortcutCardClose" @click="handleCloseFavorite(shortcut)" />
|
||||
<shortcutCard :icon="shortcut.meta?.icon" :label="shortcut.name" @click="handleRoute(shortcut.path)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty :image-size="48" :description="props.emptyDescription" v-else />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Shortcut">
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import shortcutCard from '/@/components/ShortcutCard/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: true,
|
||||
},
|
||||
emptyDescription:{
|
||||
type: String,
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取路由对象的实例。
|
||||
*/
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* 获取 tagsView 路由列表 store 对象的实例。
|
||||
*/
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { favoriteRoutes } = storeToRefs(storesTagsViewRoutes); // 将 tagView 路由列表转换为 Ref 对象
|
||||
|
||||
/**
|
||||
* 点击跳转链接触发事件的回调函数。
|
||||
* @param path - 需要跳转的路径。
|
||||
*/
|
||||
const handleRoute = (path: string) => {
|
||||
router.push(path); // 跳转到指定路由页面
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭收藏路由的事件回调函数。
|
||||
* @param item - 需要删除的路由信息。
|
||||
*/
|
||||
const handleCloseFavorite = (item: any) => {
|
||||
storesTagsViewRoutes.delFavoriteRoutes(item); // 从收藏路由列表中删除指定路由
|
||||
};
|
||||
|
||||
const showRoutes = computed(() => {
|
||||
if (props.type === 'flow') {
|
||||
return favoriteRoutes.value.filter((item) => item.path.includes('/flow/list/index?flowId'));
|
||||
} else {
|
||||
return favoriteRoutes.value.filter((item) => !item.path.includes('/flow/list/index?flowId'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shortcutCard {
|
||||
position: relative;
|
||||
|
||||
.shortcutCardClose {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 30%;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
color: #6d6b6b;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
src/views/home/widgets/components/audit-log.vue
Normal file
56
src/views/home/widgets/components/audit-log.vue
Normal file
@@ -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>
|
||||
186
src/views/home/widgets/components/calendar.vue
Normal file
186
src/views/home/widgets/components/calendar.vue
Normal file
@@ -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>
|
||||
77
src/views/home/widgets/components/current-user.vue
Normal file
77
src/views/home/widgets/components/current-user.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
title: '当前用户',
|
||||
icon: 'User',
|
||||
description: '获取当前用户信息展示',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card class="user-card h-[191px] hover:shadow-lg transition-shadow duration-300 ease-in-out">
|
||||
<div class="flex justify-center items-center p-4 h-full">
|
||||
<div class="relative">
|
||||
<img class="object-cover w-20 h-20 rounded-full shadow-md" :src="userData.avatar" alt="Avatar" />
|
||||
<div class="absolute inset-0 rounded-full shadow-inner"></div>
|
||||
</div>
|
||||
<div class="ml-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-gray-100">{{ userData.name }}</h2>
|
||||
<p class="mt-1 text-xs text-gray-500 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>
|
||||
.user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
99
src/views/home/widgets/components/demo-chart1.vue
Normal file
99
src/views/home/widgets/components/demo-chart1.vue
Normal file
@@ -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>
|
||||
55
src/views/home/widgets/components/demo-chart2.vue
Normal file
55
src/views/home/widgets/components/demo-chart2.vue
Normal file
@@ -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>
|
||||
14
src/views/home/widgets/components/favorite-flow.vue
Normal file
14
src/views/home/widgets/components/favorite-flow.vue
Normal file
@@ -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>
|
||||
14
src/views/home/widgets/components/favorite-menu.vue
Normal file
14
src/views/home/widgets/components/favorite-menu.vue
Normal file
@@ -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>
|
||||
95
src/views/home/widgets/components/flow-data.vue
Normal file
95
src/views/home/widgets/components/flow-data.vue
Normal file
@@ -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>
|
||||
12
src/views/home/widgets/components/index.ts
Normal file
12
src/views/home/widgets/components/index.ts
Normal file
@@ -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);
|
||||
73
src/views/home/widgets/components/news.vue
Normal file
73
src/views/home/widgets/components/news.vue
Normal file
@@ -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>
|
||||
15
src/views/home/widgets/components/sys-log-line.vue
Normal file
15
src/views/home/widgets/components/sys-log-line.vue
Normal file
@@ -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>
|
||||
60
src/views/home/widgets/components/sys-log.vue
Normal file
60
src/views/home/widgets/components/sys-log.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<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 && logState.dataList.length > 0">
|
||||
<el-timeline-item v-for="(item, index) in logState.dataList" :key="index">
|
||||
<div>{{ item.title }} - {{ item.remoteAddr }}</div>
|
||||
<div style="font-size: 0.8rem; color: #909399">{{ item.createTime }}</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<el-empty v-else :description="$t('common.noDataText')" />
|
||||
</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({
|
||||
dataList: [], // Explicitly initialize dataList
|
||||
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>
|
||||
518
src/views/home/widgets/index.vue
Normal file
518
src/views/home/widgets/index.vue
Normal file
@@ -0,0 +1,518 @@
|
||||
<!--
|
||||
基于 SCUI 重构适配 vite 加载和适配相关业务页面。https://gitee.com/lolicode/scui/tree/master/src/views/home
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div :class="['widgets-home', customizing ? 'customizing' : '']" ref="main">
|
||||
<div class="widgets-content">
|
||||
<div class="widgets-top">
|
||||
<div class="flex justify-end custom_btn">
|
||||
<el-button v-if="customizing" type="primary" round @click="save">完成</el-button>
|
||||
<el-button v-else type="primary" round @click="custom">自定义</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widgets" ref="widgets">
|
||||
<div class="widgets-wrapper">
|
||||
<div v-if="nowCompsList.length <= 0" class="p-5 text-center no-widgets">
|
||||
<el-empty description='您的仪表盘是空的!点击右上角的"自定义"按钮添加小组件吧。' :image-size="200"></el-empty>
|
||||
</div>
|
||||
<el-row :gutter="0">
|
||||
<el-col v-for="(item, index) in grid.layout" v-bind:key="index" :md="item" :xs="24">
|
||||
<draggable
|
||||
v-model="grid.copmsList[index]"
|
||||
animation="200"
|
||||
handle=".customize-overlay"
|
||||
group="people"
|
||||
item-key="com"
|
||||
dragClass="aaaaa"
|
||||
force-fallback
|
||||
fallbackOnBody
|
||||
class="draggable-box"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="widgets-item">
|
||||
<component :is="allComps[element as keyof typeof allComps]"></component>
|
||||
<div v-if="customizing" class="customize-overlay">
|
||||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||||
<label v-if="allComps[element as keyof typeof allComps]">
|
||||
<el-icon v-if="allComps[element as keyof typeof allComps].icon">
|
||||
<component :is="allComps[element as keyof typeof allComps].icon" />
|
||||
</el-icon>
|
||||
{{ allComps[element as keyof typeof allComps].title }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizing" class="widgets-aside">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="widgets-aside-title">添加部件</div>
|
||||
<div class="widgets-aside-close" @click="close()">
|
||||
<el-icon><Close /></el-icon>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-header style="height: auto">
|
||||
<div class="p-3 selectLayout">
|
||||
<div class="selectLayout-item item01" :class="{ active: grid.layout.join(',') === '12,6,6' }" @click="setLayout([12, 6, 6])">
|
||||
<el-row :gutter="2">
|
||||
<el-col :span="7"><span></span></el-col>
|
||||
<el-col :span="7"><span></span></el-col>
|
||||
<el-col :span="10"><span></span></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="selectLayout-item item02" :class="{ active: grid.layout.join(',') === '24,16,8' }" @click="setLayout([24, 16, 8])">
|
||||
<el-row :gutter="2">
|
||||
<el-col :span="24"><span></span></el-col>
|
||||
<el-col :span="16"><span></span></el-col>
|
||||
<el-col :span="8"><span></span></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="selectLayout-item item03" :class="{ active: grid.layout.join(',') === '24' }" @click="setLayout([24])">
|
||||
<el-row :gutter="2">
|
||||
<el-col :span="24"><span></span></el-col>
|
||||
<el-col :span="24"><span></span></el-col>
|
||||
<el-col :span="24"><span></span></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<div class="widgets-list">
|
||||
<div v-if="myCompsList.length <= 0" class="p-5 text-center widgets-list-nodata">
|
||||
<el-empty description="所有可用小组件都已添加。" :image-size="100"></el-empty>
|
||||
</div>
|
||||
<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item">
|
||||
<div class="item-logo">
|
||||
<el-icon>
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<h2>{{ item.title }}</h2>
|
||||
<p>{{ item.description }}</p>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<el-button type="primary" icon="el-icon-plus" size="small" @click="push(item)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
<el-footer style="height: 51px">
|
||||
<el-button size="small" @click="backDefaul()">恢复默认</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="widgets" setup>
|
||||
import draggable from 'vuedraggable';
|
||||
import allComps from './components/index';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface WidgetComponent {
|
||||
title: string;
|
||||
icon: Component | string; // Can be a component or an icon name string
|
||||
description: string;
|
||||
[key: string]: any; // Allow other props
|
||||
}
|
||||
|
||||
interface WidgetListItem {
|
||||
key: string;
|
||||
title: string;
|
||||
icon: Component | string;
|
||||
description: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// Default layout settings
|
||||
const defaultGrid = ref({
|
||||
layout: [7, 7, 10],
|
||||
copmsList: [
|
||||
['current-user', 'flow-data', 'audit-log', 'sys-log-line'],
|
||||
['news', 'sys-log', 'demo-chart1'],
|
||||
['calendar', 'favorite-menu', 'favorite-flow', 'demo-chart2'],
|
||||
],
|
||||
});
|
||||
const customizing = ref(false);
|
||||
const widgets = ref();
|
||||
const widgetsKey = ref('widgets');
|
||||
const grid = ref(JSON.parse(JSON.stringify(defaultGrid.value)));
|
||||
|
||||
const allCompsList = computed(() => {
|
||||
const list: WidgetListItem[] = [];
|
||||
for (const [key, compDetails] of Object.entries(allComps as Record<string, WidgetComponent>)) {
|
||||
list.push({ key, title: compDetails.title, icon: compDetails.icon, description: compDetails.description });
|
||||
}
|
||||
|
||||
const myCopmsList = grid.value.copmsList.flat();
|
||||
list.forEach((comp: WidgetListItem) => {
|
||||
const existingItem = myCopmsList.find((item: string) => item === comp.key);
|
||||
comp.disabled = !!existingItem;
|
||||
});
|
||||
|
||||
return list;
|
||||
});
|
||||
|
||||
const myCompsList = computed(() => {
|
||||
// Support list
|
||||
const myGrid = [
|
||||
'calendar',
|
||||
'current-user',
|
||||
'news',
|
||||
'audit-log',
|
||||
'sys-log',
|
||||
'flow-data',
|
||||
'favorite-menu',
|
||||
'favorite-flow',
|
||||
'sys-log-line',
|
||||
'demo-chart1',
|
||||
'demo-chart2',
|
||||
];
|
||||
return allCompsList.value.filter((item: WidgetListItem) => !item.disabled && myGrid.includes(item.key));
|
||||
});
|
||||
|
||||
const nowCompsList = computed(() => grid.value.copmsList.flat());
|
||||
|
||||
const custom = () => {
|
||||
customizing.value = true;
|
||||
nextTick(() => {
|
||||
const oldWidth = widgets.value.offsetWidth;
|
||||
const scale = widgets.value.offsetWidth / oldWidth;
|
||||
widgets.value.style.transform = `scale(${scale})`;
|
||||
});
|
||||
};
|
||||
|
||||
const setLayout = (layout: Array<number>) => {
|
||||
grid.value.layout = layout;
|
||||
if (layout.join(',') === '24') {
|
||||
if (grid.value.copmsList[1]) {
|
||||
grid.value.copmsList[0].push(...grid.value.copmsList[1]);
|
||||
}
|
||||
if (grid.value.copmsList[2]) {
|
||||
grid.value.copmsList[0].push(...grid.value.copmsList[2]);
|
||||
}
|
||||
grid.value.copmsList[1] = [];
|
||||
grid.value.copmsList[2] = [];
|
||||
}
|
||||
};
|
||||
|
||||
const push = (item: WidgetListItem) => {
|
||||
grid.value.copmsList[0].push(item.key);
|
||||
};
|
||||
|
||||
const remove = (itemKey: string) => {
|
||||
grid.value.copmsList = grid.value.copmsList.map((obj: string[]) => obj.filter((o: string) => o !== itemKey));
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
customizing.value = false;
|
||||
widgets.value.style.removeProperty('transform');
|
||||
Local.set(widgetsKey.value, JSON.stringify(grid.value));
|
||||
};
|
||||
|
||||
const backDefaul = () => {
|
||||
customizing.value = false;
|
||||
widgets.value.style.removeProperty('transform');
|
||||
grid.value = defaultGrid.value;
|
||||
Local.remove(widgetsKey.value);
|
||||
// 重新加载页面
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
customizing.value = false;
|
||||
widgets.value.style.removeProperty('transform');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化key
|
||||
const data = useUserInfo().userInfos;
|
||||
widgetsKey.value = `${window.location.host}-${data.user.userId}-widgets}`;
|
||||
let widgets = Local.get(widgetsKey.value);
|
||||
grid.value = widgets ? JSON.parse(widgets) : defaultGrid.value;
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.custom_btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.widgets-home {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.widgets-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.widgets-aside {
|
||||
width: 360px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 5px;
|
||||
min-height: 100px; /* Ensure a minimum height for better drag experience */
|
||||
position: relative; /* Needed for customize-overlay positioning */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.widgets-aside-title {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.widgets-aside-title i {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.widgets-aside-close {
|
||||
font-size: 18px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.widgets-aside-close:hover {
|
||||
background: rgba(180, 180, 180, 0.1);
|
||||
}
|
||||
|
||||
.widgets-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.widgets-top-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.widgets {
|
||||
transform-origin: top left;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.draggable-box {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.customizing .widgets-wrapper {
|
||||
margin-right: -360px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.customizing .widgets-wrapper .el-col {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.customizing .widgets-wrapper .draggable-box {
|
||||
border: 1px dashed var(--el-color-primary);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.customizing .widgets-wrapper .no-widgets {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widgets-item {
|
||||
background: var(--el-bg-color-overlay);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0px; /* Reduced for tighter vertical spacing */
|
||||
min-height: 100px; /* Ensure a minimum height for better drag experience */
|
||||
position: relative; /* Needed for customize-overlay positioning */
|
||||
}
|
||||
|
||||
.widgets-item > :deep(div) {
|
||||
border-radius: 8px; /* Ensure component content also has rounded corners if it fills the card */
|
||||
}
|
||||
|
||||
/* Ensure components inside widgets-item are not creating extra margins or borders if not needed */
|
||||
.widgets-item > :deep(.el-card) {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.customize-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.customize-overlay label {
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
height: 40px;
|
||||
padding: 0 30px;
|
||||
border-radius: 40px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.customize-overlay label i {
|
||||
margin-right: 15px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.customize-overlay .close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.widgets-list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.widgets-list-item .item-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: rgba(180, 180, 180, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
margin-right: 15px;
|
||||
color: #6a8bad;
|
||||
}
|
||||
|
||||
.widgets-list-item .item-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.widgets-list-item .item-info h2 {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.widgets-list-item .item-info p {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.widgets-list-item:hover {
|
||||
background: rgba(180, 180, 180, 0.1);
|
||||
}
|
||||
|
||||
.widgets-wrapper .sortable-ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.selectLayout {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selectLayout-item {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 2px solid var(--el-border-color-light);
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.selectLayout-item span {
|
||||
display: block;
|
||||
background: var(--el-border-color-light);
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.selectLayout-item.item02 span {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.selectLayout-item.item02 .el-col:nth-child(1) span {
|
||||
height: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.selectLayout-item.item03 span {
|
||||
height: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.selectLayout-item:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.selectLayout-item.active {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.selectLayout-item.active span {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.dark {
|
||||
.widgets-aside {
|
||||
background: #2b2b2b;
|
||||
}
|
||||
|
||||
.customize-overlay {
|
||||
background: rgba(43, 43, 43, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.customizing .widgets {
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
.customizing .widgets-aside {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.customizing .widgets-wrapper {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user