【新增】轮播图、通知公告、业务首页前端新增,其次增加多个首页卡片

pull/227/head
俞宝山 2024-07-19 02:12:00 +08:00
parent 94eeb52cd3
commit da0d57b09c
29 changed files with 1752 additions and 151 deletions

View File

@ -0,0 +1,24 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/biz/index/` + url, ...arg)
/**
* 业务主页Api接口管理器
*
* @author yubaoshan
* @date 2024/07/11 14:46
**/
export default {
// 获取轮播图列表
bizIndexSlideshowList(data) {
return request('slideshow/list', data, 'get')
},
// 获取通知公告列表
bizIndexNoticeList(data) {
return request('notice/list', data, 'get')
},
// 获取通知公告详情
bizIndexNoticeDetail(data) {
return request('notice/detail', data, 'get')
}
}

View File

@ -0,0 +1,36 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/biz/notice/` + url, ...arg)
/**
* 通知公告Api接口管理器
*
* @author yubaoshan
* @date 2024/07/11 14:46
**/
export default {
// 获取通知公告分页
bizNoticePage(data) {
return request('page', data, 'get')
},
// 提交通知公告表单 edit为true时为编辑默认为新增
bizNoticeSubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除通知公告
bizNoticeDelete(data) {
return request('delete', data)
},
// 获取通知公告详情
bizNoticeDetail(data) {
return request('detail', data, 'get')
},
// 禁用通知公告
bizNoticeDisableStatus(data) {
return request('disableStatus', data)
},
// 启用通知公告
bizNoticeEnableStatus(data) {
return request('enableStatus', data)
}
}

View File

@ -0,0 +1,32 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/dev/slideshow/` + url, ...arg)
/**
* 轮播图Api接口管理器
*
* @author yubaoshan
* @date 2024/07/13 00:31
**/
export default {
// 获取轮播图分页
devSlideshowPage(data) {
return request('page', data, 'get')
},
// 提交轮播图表单 edit为true时为编辑默认为新增
devSlideshowSubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除轮播图
devSlideshowDelete(data) {
return request('delete', data)
},
// 禁用轮播图
devSlideshowDisableStatus(data) {
return request('disableStatus', data)
},
// 启用轮播图
devSlideshowEnableStatus(data) {
return request('enableStatus', data)
}
}

View File

@ -49,5 +49,17 @@ export default {
// 获取当前用户操作日志列表
indexOpLogList(data) {
return request('opLog/list', data, 'get')
},
// 获取基础系统业务数据
indexBizDataCount(data) {
return request('bizDataCount', data, 'get')
},
// 获取运维一览数据
indexOpDataCount(data) {
return request('opDataCount', data, 'get')
},
// 获取基础工具数据
indexToolDataCount(data) {
return request('toolDataCount', data, 'get')
}
}

View File

@ -1,5 +1,5 @@
<template>
<a-card title="站内信" :bordered="false" :bodyStyle="miniMessageBodyStyle">
<a-card title="站内信" :bordered="false">
<template #extra><a @click="leaveFor('/usercenter')"></a></template>
<div class="index-message-list">
<a-list :data-source="messageList" size="small" :loading="miniMessageLoading">
@ -125,4 +125,7 @@
.index-message-list {
overflow: auto;
}
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<xn-form-container
title="详情"
:width="1000"
v-model:open="open"
:destroy-on-close="true"
@close="onClose"
>
<a-descriptions bordered>
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
<a-descriptions-item label="类型">
<a-tag :bordered="false" color="success" v-if="formData.type === 'NOTICE'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
<a-tag :bordered="false" color="processing" v-else-if="formData.type === 'ANNOUNCEMENT'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
<a-tag :bordered="false" color="warning" v-else-if="formData.type === 'WARNING'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="封面图">
<div v-if="formData.image">
<a-image :src="formData.image" style="width: 100px; height: 50px;margin-bottom: -10px;margin-top: -10px;" />
</div>
<span v-else></span>
</a-descriptions-item>
<a-descriptions-item label="内容"><div v-html="formData.content"></div></a-descriptions-item>
</a-descriptions>
<a-descriptions bordered :column="2" class="mt-2">
<a-descriptions-item label="摘要">{{ formData.digest }}</a-descriptions-item>
<a-descriptions-item label="备注"><span>{{ formData.remark }}</span></a-descriptions-item>
<a-descriptions-item label="排序"><span>{{ formData.sortCode }}</span></a-descriptions-item>
<a-descriptions-item label="发布位置">
<div v-if="formData.place">
<a-tag v-for="textValue in JSON.parse(formData.place)" :key="textValue" color="processing">
{{ $TOOL.dictTypeData('BIZ_NOTICE_PLACE', textValue) }}
</a-tag>
</div>
</a-descriptions-item>
<a-descriptions-item label="创建人"><span>{{ formData.createUserName }}</span></a-descriptions-item>
<a-descriptions-item label="创建时间"><span>{{ formData.createTime }}</span></a-descriptions-item>
<a-descriptions-item label="修改人"><span>{{ formData.updateUserName }}</span></a-descriptions-item>
<a-descriptions-item label="修改时间"><span>{{ formData.updateTime }}</span></a-descriptions-item>
</a-descriptions>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose"></a-button>
<a-button type="primary" @click="onClose" :loading="submitLoading">确定</a-button>
</template>
</xn-form-container>
</template>
<script setup name="bizNoticeDetail">
import bizIndexApi from '@/api/biz/bizIndexApi'
import { message } from 'ant-design-vue'
//
const open = ref(false)
const emit = defineEmits({ successful: null })
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (id) => {
open.value = true
if (id) {
const param = {
id: id
}
bizIndexApi.bizIndexNoticeDetail(param).then((data) => {
formData.value = Object.assign({}, data)
})
} else {
message.warning('未查到该信息')
}
}
//
const onClose = () => {
formData.value = {}
open.value = false
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,79 @@
<template>
<a-card :bordered="false" :title="title">
<template #extra><a @click="leaveFor('/biz/notice')"></a></template>
<a-table
:columns="columns"
:data-source="dataSource"
size="small"
:pagination="false"
:showHeader="false"
:scroll="{ y: 260 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'title'">
<a-popover>
<template #content>
<a-flex>
<img v-if="record.image" :src="record.image" style="width: 100px; height: 70px" />
<div style="padding-left: 10px; width: 300px; max-height: 100px; text-overflow: ellipsis">
{{ record.digest }}
<div style="float: right; padding-right: 10px">
<a-button type="primary" size="small" @click="detailRef.onOpen(record.id)"></a-button>
</div>
</div>
</a-flex>
</template>
<div v-if="record.type === 'NOTICE'">
<a-badge color="green" />
{{ record.title }}
</div>
<div v-if="record.type === 'ANNOUNCEMENT'">
<a-badge color="blue" />
{{ record.title }}
</div>
<div v-if="record.type === 'WARNING'">
<a-badge color="orange" />
{{ record.title }}
</div>
</a-popover>
</template>
</template>
</a-table>
<detail ref="detailRef" />
</a-card>
</template>
<script setup name="bizIndexNoticeCard">
import bizIndexApi from '@/api/biz/bizIndexApi'
import router from '@/router'
import Detail from './detail.vue'
const detailRef = ref()
const columns = [
{
title: '名称',
dataIndex: 'title',
align: 'left'
},
{
title: '时间',
dataIndex: 'createTime',
align: 'right',
width: '150px'
}
]
const title = ref('通知公告')
const dataSource = ref([])
bizIndexApi.bizIndexNoticeList().then((data) => {
dataSource.value = data
})
const leaveFor = (url = '/') => {
router.replace({
path: url
})
}
</script>
<style scoped>
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div>
<a-card :title="title" :bordered="false" class="mt-2">
<a-calendar v-model:value="calendarValue" :fullscreen="false" @select="onPanelSelect" />
<a-card :bordered="false">
<a-timeline>
@ -29,7 +29,7 @@
<a-button type="primary" :loading="submitLoading" @click="onSubmit"></a-button>
</template>
</xn-form-container>
</div>
</a-card>
</template>
<script setup name="schedule">
@ -42,6 +42,7 @@
import { required } from '@/utils/formRules'
import { onMounted } from 'vue'
import indexApi from '@/api/sys/indexApi'
const title = ref('我的日程')
const scheduleList = ref([])
const calendarValue = ref(dayjs())
@ -120,6 +121,12 @@
<style scoped>
.add-schedule {
cursor: pointer;
margin-top: -10px;
/*margin-top: -10px;*/
}
:deep(.ant-card-body) {
padding-top: 0 !important;
}
:deep(.ant-timeline-item-content) {
min-height: 10px !important;
}
</style>

View File

@ -1,15 +1,16 @@
<template>
<a-card :bordered="false">
<template #title> 快捷方式 </template>
<a-row :gutter="10">
<a-col :span="6" :key="shortcut.id" v-for="shortcut in shortcutList" :xs="12" :sm="8" :md="6" :lg="8" :xl="6">
<shortcutCard
:icon="shortcut.icon ? shortcut.icon : 'menu-outlined'"
:label="shortcut.title"
@click="leaveFor(shortcut.path)"
/>
</a-col>
</a-row>
<a-card :title="title" :bordered="false">
<div class="card-div">
<a-row :gutter="10">
<a-col :span="6" :key="shortcut.id" v-for="shortcut in shortcutList" :xs="12" :sm="8" :md="6" :lg="8" :xl="6">
<shortcutCard
:icon="shortcut.icon ? shortcut.icon : 'menu-outlined'"
:label="shortcut.title"
@click="leaveFor(shortcut.path)"
/>
</a-col>
</a-row>
</div>
</a-card>
</template>
@ -19,12 +20,11 @@
import ShortcutCard from '@/components/ShortcutCard/index.vue'
import { onMounted } from 'vue'
const shortcutList = ref([])
const title = ref('快捷方式')
onMounted(() => {
//
getUserLoginWorkbench()
})
const getUserLoginWorkbench = () => {
userCenterApi.userLoginWorkbench().then((data) => {
if (data) {
@ -32,7 +32,6 @@
}
})
}
const leaveFor = (url = '/') => {
router.replace({
path: url
@ -44,4 +43,11 @@
.ant-list-item {
padding: 8px 0px !important;
}
.card-div {
overflow: scroll;
max-height: 260px;
}
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<a-card :title="title" :bordered="false">
<a-carousel class="snowy-right-card-one" autoplay arrows>
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<LeftOutlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px"><RightOutlined /></div>
</template>
<img
v-for="item in slideshowList"
:key="item.id"
:src="item.image"
class="carousel-images"
@click="leaveForOpen(item.pathDetails)"
/>
</a-carousel>
</a-card>
</template>
<script setup name="carousel">
import bizIndexApi from '@/api/biz/bizIndexApi'
import { isEmpty, cloneDeep } from 'lodash-es'
import router from '@/router'
const slideshowList = ref([])
const title = ref('')
//
const props = defineProps({
config: {
type: Object,
default: () => {}
}
})
//
onMounted(() => {
//
const param = {
//
place: props.config.options.place ? props.config.options.place : 'BACK_SYS_INDEX'
}
bizIndexApi.bizIndexSlideshowList(param).then((data) => {
slideshowList.value = data
})
})
// URL
const leaveForOpen = (value) => {
if (isEmpty(value)) {
return
}
const detail = cloneDeep(value)
let detailObj = {}
if (typeof detail !== 'object') {
detailObj = JSON.parse(detail)
}
// json
if (detailObj.whetherToClick && detailObj.whetherToClick === 'ENABLE') {
if (detailObj.skipMode && detailObj.skipMode === 'URL') {
window.open(detailObj.url)
}
if (detailObj.skipMode && detailObj.skipMode === 'ROUTER') {
router.replace({
path: detailObj.url
})
}
}
}
</script>
<style scoped>
.carousel-images {
height: 180px;
width: 100%;
cursor: pointer;
}
.snowy-right-card-one {
height: 180px;
}
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 180px;
line-height: 150px;
background: #364d79;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
z-index: 1;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
:deep(.ant-card-body) {
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<a-card :title="title" :bordered="false">
<a-row>
<a-col :span="6">
<a-statistic :value="dataSource.userCount">
<template #title>
<user-outlined style="color: #1890ff" />
用户数量
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic :value="dataSource.orgCount">
<template #title>
<cluster-outlined style="color: rgba(229, 159, 18, 0.35)" />
组织数量
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic :value="dataSource.positionCount">
<template #title>
<apartment-outlined style="color: rgba(245, 6, 6, 0.2)" />
职位数量
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic :value="dataSource.roleCount">
<template #title>
<deployment-unit-outlined style="color: #09c755" />
角色数量
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</template>
<script setup name="sysBizDataCard">
import indexApi from '@/api/sys/indexApi'
const title = ref('业务数据')
const dataSource = ref({
userCount: 0,
roleCount: 0,
orgCount: 0,
positionCount: 0
})
onMounted(() => {
indexApi.indexBizDataCount().then((data) => {
dataSource.value = data
})
})
</script>
<style scoped>
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<a-card :title="title" :bordered="false">
<a-row>
<a-col :span="4">
<a-statistic :value="dataSource.jobCount">
<template #title>
<field-time-outlined style="color: #ec2c09" />
任务调度
</template>
</a-statistic>
</a-col>
<a-col :span="4">
<a-statistic :value="dataSource.sysDictCount">
<template #title>
<read-outlined style="color: #4b4b4b" />
系统字典
</template>
</a-statistic>
</a-col>
<a-col :span="4">
<a-statistic :value="dataSource.bizDictCount">
<template #title>
<read-outlined style="color: #353779" />
业务字典
</template>
</a-statistic>
</a-col>
<a-col :span="4">
<a-statistic :value="dataSource.backUserSessionCount">
<template #title>
<usergroup-delete-outlined style="color: #3ceecd" />
后台在线用户
</template>
</a-statistic>
</a-col>
<a-col :span="4">
<a-statistic :value="dataSource.clientUserSessionCount">
<template #title>
<UserSwitchOutlined style="color: rgba(229, 159, 18, 0.35)" />
前台在线用户
</template>
</a-statistic>
</a-col>
<a-col :span="4">
<a-statistic :value="dataSource.thirdUserCount">
<template #title>
<team-outlined style="color: #1890ff" />
三方用户
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</template>
<script setup name="sysBizDataCard">
import indexApi from '@/api/sys/indexApi'
const title = ref('运维一览')
const dataSource = ref({
jobCount: 0,
sysDictCount: 0,
bizDictCount: 0,
backUserSessionCount: 0,
clientUserSessionCount: 0,
thirdUserCount: 0
})
onMounted(() => {
indexApi.indexOpDataCount().then((data) => {
dataSource.value = data
})
})
</script>
<style scoped>
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -20,14 +20,14 @@
const opLogList = ref([])
onMounted(() => {
//
seleOpLogList()
getOpLogList()
})
//
const displayMore = () => {
return userInfo.roleCodeList && userInfo.roleCodeList.toString().indexOf('superAdmin') !== -1
}
const seleOpLogList = () => {
const getOpLogList = () => {
indexApi.indexOpLogList().then((data) => {
opLogList.value = data
})

View File

@ -0,0 +1,60 @@
<template>
<a-card :title="title" :bordered="false">
<a-row>
<a-col :span="12">
<a-statistic :value="dataSource.fileCount">
<template #title>
<FileFilled style="color: #1890ff" />
文件
</template>
</a-statistic>
</a-col>
<a-col :span="12">
<a-statistic :value="dataSource.smsCount">
<template #title>
<MessageFilled style="color: #07913e" />
短信
</template>
</a-statistic>
</a-col>
<a-col :span="12">
<a-statistic :value="dataSource.emailCount">
<template #title>
<MailFilled style="color: #fada00" />
邮件
</template>
</a-statistic>
</a-col>
<a-col :span="12">
<a-statistic :value="dataSource.messageCount">
<template #title>
<NotificationFilled style="color: #fada00" />
站内信
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</template>
<script setup name="sysToolDataCard">
import indexApi from '@/api/sys/indexApi'
const title = ref('基础工具')
const dataSource = ref({
fileCount: 0,
smsCount: 0,
emailCount: 0,
messageCount: 0
})
onMounted(() => {
indexApi.indexToolDataCount().then((data) => {
dataSource.value = data
})
})
</script>
<style scoped>
:deep(.ant-card-body) {
padding-top: 0 !important;
}
</style>

View File

@ -1,12 +1,8 @@
<template>
<a-card :bordered="false">
<div class="xn-acrd-line">
<div class="xn-card-line">
<div class="xn-flex">
<a-avatar
class="xn-wh60"
:src="userInfo.avatar"
:size="{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }"
/>
<a-avatar class="xn-wh60" :src="userInfo.avatar" :size="{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }" />
<div class="snowy-index-card-left-one-username">
<span>{{ userInfo.name }}</span>
<span>{{ userInfo.orgName }} | {{ userInfo.positionName }}</span>
@ -42,13 +38,13 @@
</script>
<style scoped>
.xn-acrd-line {
.xn-card-line {
display: flex;
justify-content: space-between
justify-content: space-between;
}
.xn-wh60 {
width: 60px;
height: 60px
height: 60px;
}
.snowy-index-card-left-one-username {
margin-left: 8px;
@ -59,11 +55,11 @@
.snowy-index-card-left-one-username > span:nth-child(1) {
font-weight: 600;
margin: 2px;
font-size: 18px
font-size: 18px;
}
.snowy-index-card-left-one-username > span:nth-child(2) {
color: #6d737b;
margin: 2px
margin: 2px;
}
.snowy-index-userinfo-time {
margin: 2px;

View File

@ -0,0 +1,55 @@
<template>
<a-card :bordered="false" title="周访问量">
<div id="visLogChartLine" class="xn-ht200"></div>
</a-card>
</template>
<script setup name="visLogChart">
import logApi from '@/api/dev/logApi'
import { Line } from '@antv/g2plot'
import { onMounted } from 'vue'
const seriesKey = 'series'
const valueKey = 'value'
const processData = (data, yFields, meta) => {
const result = []
data.forEach((d) => {
yFields.forEach((yField) => {
const name = meta?.[yField]?.alias || yField
result.push({ ...d, [seriesKey]: name, [valueKey]: d[yField] })
})
})
return result
}
onMounted(() => {
const lineMeta = {
date: {
alias: '登录登出周统计'
},
loginCount: {
alias: '登录'
},
logoutCount: {
alias: '登出'
}
}
logApi.logVisLineChartData().then((data) => {
const line = new Line('visLogChartLine', {
data: processData(data, ['loginCount', 'logoutCount'], lineMeta),
padding: 'auto',
xField: 'date',
yField: valueKey,
seriesField: seriesKey,
color: ['#1677FF', 'rgb(188, 189, 190)'],
appendPadding: [0, 8, 0, 0]
})
line.render()
})
})
</script>
<style scoped>
.xn-ht200 {
height: 200px;
}
</style>

View File

@ -21,14 +21,14 @@
const visLogList = ref([])
onMounted(() => {
//
seleVisLogList()
getVisLogList()
})
//
const displayMore = () => {
return userInfo.roleCodeList && userInfo.roleCodeList.toString().indexOf('superAdmin') !== -1
}
//
const seleVisLogList = () => {
const getVisLogList = () => {
indexApi.indexVisLogList().then((data) => {
visLogList.value = data
})

View File

@ -72,6 +72,7 @@
display: flex;
align-items: center;
margin-left: 10px;
justify-content: center;
}
.container-tag-icon {
font-size: 25px;

View File

@ -365,6 +365,8 @@ body,
.ant-tabs-dropdown-menu,
.xn-table,
.selector-table,
.card-div,
.ant-table-body,
.admin-ui-main{
&::-webkit-scrollbar {

View File

@ -0,0 +1,41 @@
<template>
<a-row :gutter="10">
<!-- 左侧 -->
<a-col :span="16">
<!-- 人员信息 -->
<sys-user-info-card class="mb-2" />
<a-row :gutter="10" class="mt-2">
<a-col :span="24">
<!-- 快捷方式 -->
<biz-shortcut-card />
</a-col>
</a-row>
<a-row :gutter="10" class="mt-2">
<a-col :span="12">
<!-- 通知公告 -->
<biz-notice-card />
</a-col>
<a-col :span="12">
<!-- 站内信息 -->
<biz-mini-message-card />
</a-col>
</a-row>
</a-col>
<!-- 右侧 -->
<a-col :span="8">
<!-- 轮播图 -->
<biz-slideshow-card :config="slideshowConfig" />
<!-- 我的日程 -->
<biz-schedule-card />
</a-col>
</a-row>
</template>
<script setup name="bizIndex">
//
const slideshowConfig = {
options: {
place: 'BACK_INDEX'
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<xn-form-container
title="详情"
:width="1000"
v-model:open="open"
:destroy-on-close="true"
@close="onClose"
>
<a-descriptions bordered>
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
<a-descriptions-item label="类型">
<a-tag :bordered="false" color="success" v-if="formData.type === 'NOTICE'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
<a-tag :bordered="false" color="processing" v-else-if="formData.type === 'ANNOUNCEMENT'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
<a-tag :bordered="false" color="warning" v-else-if="formData.type === 'WARNING'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', formData.type) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="封面图">
<div v-if="formData.image">
<a-image :src="formData.image" style="width: 100px; height: 50px;margin-bottom: -10px;margin-top: -10px;" />
</div>
<span v-else></span>
</a-descriptions-item>
<a-descriptions-item label="内容"><div v-html="formData.content"></div></a-descriptions-item>
</a-descriptions>
<a-descriptions bordered :column="2" class="mt-2">
<a-descriptions-item label="摘要">{{ formData.digest }}</a-descriptions-item>
<a-descriptions-item label="备注"><span>{{ formData.remark }}</span></a-descriptions-item>
<a-descriptions-item label="排序"><span>{{ formData.sortCode }}</span></a-descriptions-item>
<a-descriptions-item label="发布位置">
<div v-if="formData.place">
<a-tag v-for="textValue in JSON.parse(formData.place)" :key="textValue" color="processing">
{{ $TOOL.dictTypeData('BIZ_NOTICE_PLACE', textValue) }}
</a-tag>
</div>
</a-descriptions-item>
<a-descriptions-item label="创建人"><span>{{ formData.createUserName }}</span></a-descriptions-item>
<a-descriptions-item label="创建时间"><span>{{ formData.createTime }}</span></a-descriptions-item>
<a-descriptions-item label="修改人"><span>{{ formData.updateUserName }}</span></a-descriptions-item>
<a-descriptions-item label="修改时间"><span>{{ formData.updateTime }}</span></a-descriptions-item>
</a-descriptions>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose"></a-button>
<a-button type="primary" @click="onClose" :loading="submitLoading">确定</a-button>
</template>
</xn-form-container>
</template>
<script setup name="bizNoticeDetail">
import bizNoticeApi from '@/api/biz/bizNoticeApi'
import { message } from 'ant-design-vue'
//
const open = ref(false)
const emit = defineEmits({ successful: null })
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (id) => {
open.value = true
if (id) {
const param = {
id: id
}
bizNoticeApi.bizNoticeDetail(param).then((data) => {
formData.value = Object.assign({}, data)
})
} else {
message.warning('未查到该信息')
}
}
//
const onClose = () => {
formData.value = {}
open.value = false
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,148 @@
<template>
<xn-form-container
:title="formData.id ? '编辑通知公告' : '增加通知公告'"
:width="1000"
v-model:open="open"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="19">
<a-form-item label="标题:" name="title">
<a-input v-model:value="formData.title" placeholder="请输入标题" allow-clear />
</a-form-item>
<a-form-item name="type">
<template #label>
<a-tooltip>
<template #title>
这里只是标签的类型
</template>
<question-circle-outlined />
类型
</a-tooltip>
</template>
<a-radio-group v-model:value="formData.type" placeholder="请选择类型" :options="typeOptions" />
</a-form-item>
</a-col>
<a-col :span="5">
<a-form-item label="封面图:" name="image">
<xn-upload v-model:value="formData.image" uploadMode="image" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="内容:" name="content">
<xn-editor v-model:value="formData.content" placeholder="请输入内容" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="摘要:" name="digest">
<a-textarea
v-model:value="formData.digest"
placeholder="请输入摘要"
:auto-size="{ minRows: 3, maxRows: 5 }"
:showCount="true"
:maxlength="60"
/>
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="发布位置:" name="place">
<a-checkbox-group v-model:value="formData.place" placeholder="请选择发布位置" :options="placeOptions" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="排序:" name="sortCode">
<a-input-number
v-model:value="formData.sortCode"
placeholder="请输入排序"
:max="1000"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :auto-size="{ minRows: 3, maxRows: 5 }" />
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose"></a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="bizNoticeForm">
import tool from '@/utils/tool'
import { cloneDeep } from 'lodash-es'
import { required } from '@/utils/formRules'
import bizNoticeApi from '@/api/biz/bizNoticeApi'
//
const open = ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
const typeOptions = ref([])
const placeOptions = ref([])
const statusOptions = ref([])
//
const onOpen = (record) => {
open.value = true
if (record) {
let recordData = cloneDeep(record)
recordData.place = JSON.parse(recordData.place)
formData.value = Object.assign({}, recordData)
} else {
formData.value = {
type: 'NOTICE',
place: ['BACK_MOBILE', 'BACK_INDEX'],
sortCode: 99
}
}
typeOptions.value = tool.dictList('BIZ_NOTICE_TYPE')
placeOptions.value = tool.dictList('BIZ_NOTICE_PLACE')
statusOptions.value = tool.dictList('BIZ_NOTICE_STATUS')
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
open.value = false
}
//
const formRules = {
title: [required('请输入标题')],
content: [required('请输入内容')],
digest: [required('请输入摘要')],
type: [required('请选择类型')],
place: [required('请选择发布位置')],
sortCode: [required('请输入排序')]
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
formDataParam.place = JSON.stringify(formDataParam.place)
bizNoticeApi
.bizNoticeSubmitForm(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,250 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="标题" name="title">
<a-input v-model:value="searchFormState.title" placeholder="请输入标题" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="类型" name="type">
<a-select v-model:value="searchFormState.type" placeholder="请选择类型" :options="typeOptions" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="发布位置" name="place">
<a-select v-model:value="searchFormState.place" placeholder="请选择发布位置" :options="placeOptions" />
</a-form-item>
</a-col>
<a-col :span="6" v-show="advanced">
<a-form-item label="状态" name="status">
<a-select v-model:value="searchFormState.status" placeholder="请选择状态" :options="statusOptions" />
</a-form-item>
</a-col>
<a-col :span="6" v-show="advanced">
<a-form-item label="创建时间" name="createTime">
<a-range-picker v-model:value="searchFormState.createTime" show-time />
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="tableRef.refresh(true)"></a-button>
<a-button style="margin: 0 8px" @click="reset"></a-button>
<a @click="toggleAdvanced" style="margin-left: 8px">
{{ advanced ? '收起' : '展开' }}
<component :is="advanced ? 'up-outlined' : 'down-outlined'"/>
</a>
</a-col>
</a-row>
</a-form>
<s-table
ref="tableRef"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('bizNoticeAdd')">
<template #icon><plus-outlined /></template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('bizNoticeBatchDelete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchBizNotice"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'image'">
<div v-if="record.image">
<a-image :src="record.image" style="width: 30px; height: 30px;" />
</div>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'type'">
<a-tag :bordered="false" color="success" v-if="record.type === 'NOTICE'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', record.type) }}
</a-tag>
<a-tag :bordered="false" color="processing" v-else-if="record.type === 'ANNOUNCEMENT'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', record.type) }}
</a-tag>
<a-tag :bordered="false" color="warning" v-else-if="record.type === 'WARNING'">
{{ $TOOL.dictTypeData('BIZ_NOTICE_TYPE', record.type) }}
</a-tag>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'place'">
<a-tag v-for="textValue in JSON.parse(record.place)" :key="textValue" color="processing">
{{ $TOOL.dictTypeData('BIZ_NOTICE_PLACE', textValue) }}
</a-tag>
</template>
<template v-if="column.dataIndex === 'status'">
<a-switch
:loading="loading"
:checked="record.status === 'ENABLE'"
@change="editStatus(record)"
v-if="hasPerm('bizNoticerUpdateStatus')"
/>
<span v-else>{{ $TOOL.dictTypeData('BIZ_NOTICE_STATUS', record.status) }}</span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="detailRef.onOpen(record.id)" v-if="hasPerm('bizNoticeDetail')"></a>
<a-divider type="vertical" v-if="hasPerm(['bizNoticeEdit', 'bizNoticeDetail'], 'and')" />
<a @click="formRef.onOpen(record)" v-if="hasPerm('bizNoticeEdit')"></a>
<a-divider type="vertical" v-if="hasPerm(['bizNoticeEdit', 'bizNoticeDelete'], 'and')" />
<a-popconfirm title="确定要删除吗?" @confirm="deleteBizNotice(record)">
<a-button type="link" danger size="small" v-if="hasPerm('bizNoticeDelete')"></a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="tableRef.refresh()" />
<detail ref="detailRef" />
</template>
<script setup name="notice">
import tool from '@/utils/tool'
import { cloneDeep } from 'lodash-es'
import Form from './form.vue'
import Detail from './detail.vue'
import bizNoticeApi from '@/api/biz/bizNoticeApi'
const searchFormState = ref({})
const searchFormRef = ref()
const tableRef = ref()
const formRef = ref()
const detailRef = ref()
const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
const loading = ref(false)
//
const advanced = ref(false)
const toggleAdvanced = () => {
advanced.value = !advanced.value
}
const columns = [
{
title: '标题',
dataIndex: 'title'
},
{
title: '封面图',
dataIndex: 'image',
width: '100px'
},
{
title: '类型',
dataIndex: 'type'
},
{
title: '发布位置',
dataIndex: 'place'
},
{
title: '排序',
dataIndex: 'sortCode',
sorter: true
},
{
title: '状态',
dataIndex: 'status'
},
{
title: '创建时间',
dataIndex: 'createTime',
width: '150px'
},
]
//
if (hasPerm(['bizNoticeEdit', 'bizNoticeDelete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '200px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: false,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = cloneDeep(searchFormState.value)
// createTime
if (searchFormParam.createTime) {
searchFormParam.startCreateTime = searchFormParam.createTime[0]
searchFormParam.endCreateTime = searchFormParam.createTime[1]
delete searchFormParam.createTime
}
return bizNoticeApi.bizNoticePage(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
tableRef.value.refresh(true)
}
//
const deleteBizNotice = (record) => {
let params = [
{
id: record.id
}
]
bizNoticeApi.bizNoticeDelete(params).then(() => {
tableRef.value.refresh(true)
})
}
//
const deleteBatchBizNotice = (params) => {
bizNoticeApi.bizNoticeDelete(params).then(() => {
tableRef.value.clearRefreshSelected()
})
}
//
const editStatus = (record) => {
loading.value = true
if (record.status === 'ENABLE') {
bizNoticeApi
.bizNoticeDisableStatus(record)
.then(() => {
tableRef.value.refresh()
})
.finally(() => {
loading.value = false
})
} else {
bizNoticeApi
.bizNoticeEnableStatus(record)
.then(() => {
tableRef.value.refresh()
})
.finally(() => {
loading.value = false
})
}
}
const typeOptions = tool.dictList('BIZ_NOTICE_TYPE')
const placeOptions = tool.dictList('BIZ_NOTICE_PLACE')
const statusOptions = tool.dictList('BIZ_NOTICE_STATUS')
</script>

View File

@ -0,0 +1,111 @@
<template>
<xn-form-container
:title="formData.id ? '编辑轮播图' : '增加轮播图'"
:width="700"
v-model:open="open"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="18">
<a-form-item label="标题:" name="title">
<a-input v-model:value="formData.title" placeholder="请输入标题" allow-clear />
</a-form-item>
<a-form-item label="排序:" name="sortCode">
<a-input v-model:value="formData.sortCode" placeholder="请输入排序" allow-clear />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="图片:" name="image">
<xn-upload v-model:value="formData.image" uploadMode="image" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="展示位置:" name="place">
<a-checkbox-group v-model:value="formData.place" placeholder="请选择展示位置" :options="placeOptions" />
</a-form-item>
</a-col>
<a-col :span="24">
<sub-form ref="sumFormRef" :data-array="formData.pathDetails" :place="formData.place" />
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose"></a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="devSlideshowForm">
import tool from '@/utils/tool'
import { cloneDeep } from 'lodash-es'
import { required } from '@/utils/formRules'
import slideshowApi from '@/api/dev/slideshowApi'
import SubForm from './subForm.vue'
//
const open = ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
const sumFormRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
const placeOptions = ref([])
//
const onOpen = (record) => {
open.value = true
if (record) {
let recordData = cloneDeep(record)
recordData.place = JSON.parse(recordData.place)
recordData.pathDetails = JSON.parse(recordData.pathDetails)
formData.value = Object.assign({}, recordData)
} else {
formData.value = {
place: ['BACK_SYS_INDEX']
}
}
placeOptions.value = tool.dictList('DEV_SLIDESHOW_PLACE')
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
open.value = false
}
//
const formRules = {
title: [required('请输入标题')],
place: [required('请输入展示位置')],
sortCode: [required('请输入排序')]
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
const formDataParam = cloneDeep(formData.value)
formDataParam.place = JSON.stringify(formDataParam.place)
const details = sumFormRef.value.getData()
if (details === false) {
return
}
formDataParam.pathDetails = JSON.stringify(details)
submitLoading.value = true
slideshowApi
.devSlideshowSubmitForm(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,233 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="标题" name="title">
<a-input v-model:value="searchFormState.title" placeholder="请输入标题" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="展示位置" name="place">
<a-select v-model:value="searchFormState.place" placeholder="请选择展示位置" :options="placeOptions" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="状态" name="status">
<a-select v-model:value="searchFormState.status" placeholder="请选择状态" :options="statusOptions" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="tableRef.refresh(true)"></a-button>
<a-button style="margin: 0 8px" @click="reset"></a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="tableRef"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #expandColumnTitle>
<span>更多</span>
</template>
<template #expandedRowRender="{ record }">
<a-table
size="middle"
:dataSource="JSON.parse(record.pathDetails)"
:columns="detailsColumns"
:pagination="false"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'whetherToClick'">
<a-tag color="blue" v-if="record.whetherToClick === 'ENABLE'">
{{ $TOOL.dictTypeData('WHETHER_TO_CLICK', record.whetherToClick) }}
</a-tag>
<a-tag color="red" v-if="record.whetherToClick === 'DISABLE'">
{{ $TOOL.dictTypeData('WHETHER_TO_CLICK', record.whetherToClick) }}
</a-tag>
</template>
<template v-if="column.dataIndex === 'skipMode'">
{{ $TOOL.dictTypeData('SKIP_MODE', record.skipMode) }}
</template>
</template>
</a-table>
</template>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()">
<template #icon><plus-outlined /></template>
新增
</a-button>
<xn-batch-delete
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchDevSlideshow"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'place'">
<a-tag v-for="textValue in JSON.parse(record.place)" :key="textValue" color="green">{{ $TOOL.dictTypeData('DEV_SLIDESHOW_PLACE', textValue) }}</a-tag>
</template>
<template v-if="column.dataIndex === 'image'">
<a-image :src="record.image" style="width: 50px; height: 30px;" />
</template>
<template v-if="column.dataIndex === 'status'">
<a-switch
:loading="loading"
:checked="record.status === 'ENABLE'"
@change="editStatus(record)"
v-if="hasPerm('bizNoticerUpdateStatus')"
/>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)"></a>
<a-divider type="vertical" />
<a-popconfirm title="确定要删除吗?" @confirm="deleteDevSlideshow(record)">
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="tableRef.refresh()" />
</template>
<script setup name="slideshow">
import tool from '@/utils/tool'
import { cloneDeep } from 'lodash-es'
import Form from './form.vue'
import slideshowApi from '@/api/dev/slideshowApi'
const searchFormState = ref({})
const searchFormRef = ref()
const tableRef = ref()
const formRef = ref()
const loading = ref(false)
const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
const columns = [
{
title: '标题',
dataIndex: 'title'
},
{
title: '图片',
dataIndex: 'image'
},
{
title: '展示位置',
dataIndex: 'place'
},
{
title: '状态',
dataIndex: 'status'
},
{
title: '排序',
dataIndex: 'sortCode',
sorter: true
},
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
}
]
const detailsColumns = [
{
title: '位置',
dataIndex: 'label',
width: '200px'
},
{
title: '点击事件',
dataIndex: 'whetherToClick'
},
{
title: '跳转方式',
dataIndex: 'skipMode'
},
{
title: 'URL',
dataIndex: 'url'
}
]
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: false,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = cloneDeep(searchFormState.value)
return slideshowApi.devSlideshowPage(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
tableRef.value.refresh(true)
}
//
const deleteDevSlideshow = (record) => {
let params = [
{
id: record.id
}
]
slideshowApi.devSlideshowDelete(params).then(() => {
tableRef.value.refresh()
})
}
//
const deleteBatchDevSlideshow = (params) => {
slideshowApi.devSlideshowDelete(params).then(() => {
tableRef.value.clearRefreshSelected()
})
}
//
const editStatus = (record) => {
loading.value = true
if (record.status === 'ENABLE') {
slideshowApi
.devSlideshowDisableStatus(record)
.then(() => {
tableRef.value.refresh()
})
.finally(() => {
loading.value = false
})
} else {
slideshowApi
.devSlideshowEnableStatus(record)
.then(() => {
tableRef.value.refresh()
})
.finally(() => {
loading.value = false
})
}
}
const placeOptions = tool.dictList('DEV_SLIDESHOW_PLACE')
const statusOptions = tool.dictList('DEV_SLIDESHOW_STATUS')
</script>

View File

@ -0,0 +1,162 @@
<template>
<a-form
:model="formData"
ref="formRef"
name="basic"
autocomplete="off"
>
<a-table :columns="columns" :dataSource="formData" size="middle">
<template #bodyCell="{text, record, index, column}">
<template v-if="column.dataIndex === 'whetherToClick'">
<a-form-item :validate-status="validateStatus(record, 'whetherToClick')">
<a-radio-group v-model:value="record.whetherToClick" placeholder="请选择跳转方式" :options="whetherToClickOptions" />
</a-form-item>
</template>
<template v-if="column.dataIndex === 'skipMode'">
<a-form-item :validate-status="validateStatus(record, 'skipMode')">
<a-select v-model:value="record.skipMode" placeholder="请选择跳转方式" :disabled="record.whetherToClick === 'DISABLE'" :options="skipModeOptions" />
</a-form-item>
</template>
<template v-if="column.dataIndex === 'url'">
<a-form-item :validate-status="validateStatus(record, 'url')">
<a-input v-model:value="formData[index].url" :disabled="record.whetherToClick === 'DISABLE'" placeholder="请输入URL或路由地址"/>
</a-form-item>
</template>
</template>
</a-table>
</a-form>
</template>
<script name="subForm" setup>
import tool from "@/utils/tool"
import { remove, isEmpty, cloneDeep } from 'lodash-es'
const formRef = ref()
const formData = ref([])
const skipModeOptions = ref(tool.dictList('SKIP_MODE'))
const whetherToClickOptions = ref(tool.dictList('WHETHER_TO_CLICK'))
const props = defineProps({
dataArray: {
type: Object,
default: []
},
place: {
type: Object,
default: []
}
})
const columns = ref([
{
title: '位置',
dataIndex: 'label',
width: '20%',
},
{
title: '点击事件',
dataIndex: 'whetherToClick',
width: '25%',
},
{
title: '跳转方式',
dataIndex: 'skipMode',
width: '20%',
},
{
title: 'URL',
dataIndex: 'url'
}
])
//
const validateStatus = (record, name) => {
if (record[name]) {
return 'success'
} else {
return 'error'
}
}
//
const dataFiltrate = (newVal, oldVal) => {
let result = ''
oldVal.forEach((data) => {
if (!newVal.some(item => item === data)) {
result = data
}
})
return result
}
watch(
() => props.place,
(newVal, oldVal) => {
if (!isEmpty(props.dataArray) && isEmpty(formData.value)) {
formData.value = cloneDeep(props.dataArray)
} else {
if (typeof newVal === "object") {
if (!isEmpty(formData.value)) {
formData.value.forEach(() => {
//
if (!newVal.some(item => item === item.key)) {
//
if (formData.value.length > newVal.length) {
const deleteData = dataFiltrate(newVal, oldVal)
remove(formData.value, (item) => item.key === deleteData)
}
//
if (formData.value.length < newVal.length) {
const deleteData = dataFiltrate(oldVal, newVal)
//
if (!formData.value.some(item => item === deleteData)) {
const obj = {
key: deleteData,
label: tool.dictTypeData('DEV_SLIDESHOW_PLACE' , deleteData),
whetherToClick: 'DISABLE',
skipMode: 'URL',
url: ''
}
formData.value.push(obj)
}
}
}
})
} else {
newVal.forEach((item) => {
const obj = {
key: item,
label: tool.dictTypeData('DEV_SLIDESHOW_PLACE' , item),
whetherToClick: 'DISABLE',
skipMode: 'URL',
url: ''
}
formData.value.push(obj)
})
}
}
}
},
{ immediate: true, deep: true }
)
// false
const getData = () => {
if (isEmpty(formData.value)) {
return false
} else {
let result = true
formData.value.forEach((item) => {
if (item.whetherToClick === 'ENABLE' && isEmpty(item.url)) {
result = false
}
})
if (result === false) {
return false
} else {
return formData.value
}
}
}
defineExpose({
getData
})
</script>
<style lang="less" scoped>
.ant-form-item {
margin-bottom: 0 !important;
}
</style>

View File

@ -1,90 +0,0 @@
<template>
<a-carousel class="snowy-right-card-one" autoplay arrows>
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
</template>
<img
src="/src/assets/images/index_001.png"
class="carousel-images"
@click="leaveForOpen('https://www.xiaonuo.vip')"
/>
<img
src="/src/assets/images/index_002.png"
class="carousel-images"
@click="leaveForOpen('https://www.xiaonuo.vip')"
/>
<!--
<img v-for="(item, index) in carouselList"
:src="item.images"
class="carousel-images"
@click="leaveForOpen(item.url)"
/>
-->
</a-carousel>
</template>
<script setup name="carousel">
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue'
const carouselList = ref([
{
images: '/src/assets/images/index_001.png',
url: 'https://www.xiaonuo.vip'
},
{
images: '/src/assets/images/index_002.png',
url: 'https://www.xiaonuo.vip'
}
])
//
const leaveForOpen = (url) => {
if (url) {
window.open(url)
}
}
</script>
<style scoped>
.carousel-images {
height: 160px;
width: 100%;
cursor: pointer;
}
.snowy-right-card-one {
height: 160px;
}
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 160px;
line-height: 150px;
background: #364d79;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
z-index: 1;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>

View File

@ -1,31 +1,39 @@
<template>
<a-row :gutter="10">
<a-col :span="16" :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
<userInfo class="mb-2" />
<shortcut class="mb-2" />
<!-- 人员信息 -->
<sys-user-info-card class="mb-2" />
<!-- 业务数据 -->
<sys-biz-data-card class="mb-2" />
<!-- 运维一览 -->
<sys-op-data-card class="mb-2" />
<a-row :gutter="10">
<!-- 访问记录 -->
<a-col :span="12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<visLog class="mb-2" />
<sys-vis-log-card class="mb-2" />
</a-col>
<!-- 操作记录 -->
<a-col :span="12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<opLog class="mb-2" />
<sys-op-log-card class="mb-2" />
</a-col>
</a-row>
</a-col>
<a-col :span="8" :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<carousel class="mb-2" />
<schedule class="mb-2" />
<miniMessage class="mb-2" />
<!-- 轮播图 -->
<biz-slideshow-card :config="slideshowConfig" class="mb-2" />
<!-- 基础工具 -->
<sys-tool-data-card class="mb-2" />
<!-- 周访问量 -->
<sys-vis-chart-data-card />
</a-col>
</a-row>
</template>
<script setup name="indexHome">
import UserInfo from './components/userInfo.vue'
import Shortcut from './components/shortcut.vue'
import Schedule from './components/schedule.vue'
import MiniMessage from './components/miniMessage.vue'
import Carousel from './components/carousel.vue'
import VisLog from './components/visLog.vue'
import OpLog from './components/opLog.vue'
//
const slideshowConfig = {
options: {
place: 'BACK_SYS_INDEX'
}
}
</script>

View File

@ -20,11 +20,11 @@ import Less2CssVariablePlugin from 'antd-less-to-css-variable'
import viteCompression from 'vite-plugin-compression'
// ant-design-vue 的 less 变量,通过兼容包将 v4 变量转译成 v3 版本,并通过 less-loader 注入
import { theme } from 'ant-design-vue/lib';
import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken';
const { defaultAlgorithm, defaultSeed } = theme;
const mapToken = defaultAlgorithm(defaultSeed);
const v3Token = convertLegacyToken.default(mapToken);
import { theme } from 'ant-design-vue/lib'
import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken'
const { defaultAlgorithm, defaultSeed } = theme
const mapToken = defaultAlgorithm(defaultSeed)
const v3Token = convertLegacyToken.default(mapToken)
export const r = (...args) => resolve(__dirname, '.', ...args)
@ -86,21 +86,30 @@ export default defineConfig(({ command, mode }) => {
dts: r('src/auto-imports.d.ts')
}),
// 组件按需引入
Components({
dirs: [r('src/components')],
dts: false,
resolvers: []
}),
Components(
{
dirs: [r('src/components')],
dts: false,
resolvers: []
},
{
dirs: [r('src/components/HomeCard')],
dts: false,
resolvers: []
}
),
visualizer()
],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
plugins: [new Less2CssVariablePlugin({
// TODO有必要用的情况下是否需要传入 variables可能会造成重复引用
variables: { ...v3Token }
})],
plugins: [
new Less2CssVariablePlugin({
// TODO有必要用的情况下是否需要传入 variables可能会造成重复引用
variables: { ...v3Token }
})
],
modifyVars: v3Token
}
}