This commit is contained in:
PQZ 2025-11-05 10:18:26 +08:00
parent 92142ef2d0
commit 146195e3fb
11 changed files with 306 additions and 35 deletions

View File

@ -53,6 +53,18 @@ public class BaseCalendarEventController extends BaseController
return success(list); return success(list);
} }
/**
* 同步中国节假日
* @author PQZ
* @date 17:18 2025/11/3
* @return com.ruoyi.common.core.domain.AjaxResult
**/
@GetMapping("/syncEvent")
public AjaxResult syncEvent() {
baseCalendarEventService.syncEvent();
return success();
}
/** /**
* 导出节日列表 * 导出节日列表
*/ */

View File

@ -15,4 +15,6 @@ import com.ruoyi.base.domain.BaseCalendarEvent;
public interface IBaseCalendarEventService extends IService<BaseCalendarEvent> public interface IBaseCalendarEventService extends IService<BaseCalendarEvent>
{ {
IPage<BaseCalendarEvent> queryListPage(BaseCalendarEvent pageReqVO, Page<BaseCalendarEvent> page); IPage<BaseCalendarEvent> queryListPage(BaseCalendarEvent pageReqVO, Page<BaseCalendarEvent> page);
void syncEvent();
} }

View File

@ -1,29 +1,91 @@
package com.ruoyi.base.service.impl; package com.ruoyi.base.service.impl;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Year;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.base.mapper.BaseCalendarEventMapper; import com.ruoyi.base.mapper.BaseCalendarEventMapper;
import com.ruoyi.base.domain.BaseCalendarEvent; import com.ruoyi.base.domain.BaseCalendarEvent;
import com.ruoyi.base.service.IBaseCalendarEventService; import com.ruoyi.base.service.IBaseCalendarEventService;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/** /**
* 节日Service业务层处理 * 节日Service业务层处理
* *
* @author pqz * @author pqz
* @date 2025-10-31 * @date 2025-10-31
*/ */
@Service @Service
public class BaseCalendarEventServiceImpl extends ServiceImpl<BaseCalendarEventMapper,BaseCalendarEvent> implements IBaseCalendarEventService public class BaseCalendarEventServiceImpl extends ServiceImpl<BaseCalendarEventMapper, BaseCalendarEvent> implements IBaseCalendarEventService {
{
@Autowired @Autowired
private BaseCalendarEventMapper baseCalendarEventMapper; private BaseCalendarEventMapper baseCalendarEventMapper;
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public IPage<BaseCalendarEvent> queryListPage(BaseCalendarEvent pageReqVO, Page<BaseCalendarEvent> page) { public IPage<BaseCalendarEvent> queryListPage(BaseCalendarEvent pageReqVO, Page<BaseCalendarEvent> page) {
return baseCalendarEventMapper.queryListPage(pageReqVO, page); return baseCalendarEventMapper.queryListPage(pageReqVO, page);
} }
@Override
public void syncEvent() {
String apiKey = "55bc9d05a05e5e1c0183f7ab380707d8";
String baseUrl = "https://apis.tianapi.com/jiejiari/index";
String date = "2025";
String type = "1";
try {
// 构建请求URL
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.queryParam("key", apiKey)
.queryParam("date", date)
.queryParam("type", type);
// 发送GET请求
String response = restTemplate.getForObject(builder.toUriString(), String.class);
// 解析响应JSON
if (response != null) {
Map<String, Object> resultMap = objectMapper.readValue(response, Map.class);
Integer code = (Integer) resultMap.get("code");
// 检查请求是否成功
if (code != null && code == 200) {
Map<String, Object> newsMap = (Map<String, Object>) resultMap.get("result");
List<Map<String, Object>> dataList = (List<Map<String, Object>>) newsMap.get("list");
// 输出数据到控制台
System.out.println("获取到 " + dataList.size() + " 条节假日数据:");
for (Map<String, Object> data : dataList) {
System.out.println("日期: " + data.get("date") +
", 名称: " + data.get("name") +
", 类型: " + data.get("type"));
}
} else {
System.err.println("获取节假日数据失败,错误码: " + code +
", 错误信息: " + resultMap.get("msg"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} }

View File

@ -44,7 +44,6 @@ public class BaseCalendarServiceImpl extends ServiceImpl<BaseCalendarMapper, Bas
**/ **/
@Override @Override
public void generateCalendar() { public void generateCalendar() {
// 获取当前年份 // 获取当前年份
int currentYear = LocalDate.now().getYear(); int currentYear = LocalDate.now().getYear();
// 从当前年份的1月1日开始计算 // 从当前年份的1月1日开始计算
@ -62,13 +61,14 @@ public class BaseCalendarServiceImpl extends ServiceImpl<BaseCalendarMapper, Bas
calendar.setDay((long) date.getDayOfMonth()); calendar.setDay((long) date.getDayOfMonth());
calendar.setWeek((long) date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); calendar.setWeek((long) date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR));
calendar.setWeekday((long) date.getDayOfWeek().getValue()); calendar.setWeekday((long) date.getDayOfWeek().getValue());
// 使用Hutool计算农历日期 // 使用Hutool计算农历日期
calendar.setLunarDate(calculateLunarDate(date)); calendar.setLunarDate(calculateLunarDate(date));
// 设置工作状态周六(6)和周日(7)设置为""
// 默认工作状态为空正常工作日 if (date.getDayOfWeek().getValue() == 6 || date.getDayOfWeek().getValue() == 7) {
calendar.setWorkStatus(""); calendar.setWorkStatus("");
} else {
calendar.setWorkStatus("");
}
calendarList.add(calendar); calendarList.add(calendar);
} }
//批量保存到数据库 //批量保存到数据库

View File

@ -163,6 +163,11 @@ dify:
# key: app-ahgDwrLi8KQ6V0aEKKwT6Io7 # key: app-ahgDwrLi8KQ6V0aEKKwT6Io7
# url: http://10.19.128.77/v1 # url: http://10.19.128.77/v1
# 登录相关配置
login:
# 是否限制单用户登录
single: true
# Python环境配置 # Python环境配置
python: python:

View File

@ -6,6 +6,7 @@ import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -58,6 +59,9 @@ public class SysLoginService
@Autowired @Autowired
private ISysConfigService configService; private ISysConfigService configService;
@Value("${login.single}")
private boolean isSingle;
/** /**
* 登录验证 * 登录验证
* *
@ -102,8 +106,8 @@ public class SysLoginService
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal(); LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId()); recordLoginInfo(loginUser.getUserId());
// 生成token String token = tokenService.createToken(loginUser);
return tokenService.createToken(loginUser); return token;
} }
/** /**

View File

@ -8,6 +8,13 @@ export function listEvent(query) {
params: query params: query
}) })
} }
// 生成日历
export function syncEvent() {
return request({
url: '/base/event/syncEvent',
method: 'get',
})
}
// 查询节日详细 // 查询节日详细
export function getEvent(id) { export function getEvent(id) {

View File

@ -6,6 +6,7 @@ import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { isPathMatch } from '@/utils/validate' import { isPathMatch } from '@/utils/validate'
import { isRelogin } from '@/utils/request' import { isRelogin } from '@/utils/request'
import watermark from '@/utils/watermark' // 引入水印工具
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
@ -31,6 +32,21 @@ router.beforeEach((to, from, next) => {
// 判断当前用户是否已拉取完user_info信息 // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => { store.dispatch('GetInfo').then(() => {
isRelogin.show = false isRelogin.show = false
// 清理可能存在的旧水印
watermark.remove()
// 设置新水印
const currentUser = store.getters.name || '未知用户'
const currentDate = new Date().toLocaleDateString()
watermark.set({
text: `${currentUser}\n${currentDate}`,
color: 'rgba(180, 180, 180, 0.3)',
fontSize: 16,
rotate: -15,
density: 0.6,
zIndex: 9999
})
store.dispatch('GenerateRoutes').then(accessRoutes => { store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表 // 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表 router.addRoutes(accessRoutes) // 动态添加可访问路由表
@ -43,6 +59,20 @@ router.beforeEach((to, from, next) => {
}) })
}) })
} else { } else {
// 清理可能存在的旧水印
watermark.remove()
// 设置新水印
const currentUser = store.getters.name || '未知用户'
const currentDate = new Date().toLocaleDateString()
watermark.set({
text: `${currentUser}\n${currentDate}`,
color: 'rgba(180, 180, 180, 0.3)',
fontSize: 16,
rotate: -15,
density: 0.6,
zIndex: 9999
})
next() next()
} }
} }

View File

@ -0,0 +1,74 @@
// src/utils/watermark.js
let watermarkDom = null // 水印DOM缓存
/**
* 创建水印
* @param {Object} options 水印配置
* @param {String} options.text 水印文字支持\n换行
* @param {String} options.color 文字颜色默认rgba(180,180,180,0.3)
* @param {Number} options.fontSize 字体大小默认18px
* @param {Number} options.rotate 旋转角度默认-15
* @param {Number} options.density 密度0-1默认0.3
* @param {Number} options.zIndex 层级默认9999
*/
function createWatermark(options = {}) {
// 合并默认配置
const {
text = 'RuoYi 水印',
color = 'rgba(180, 180, 180, 0.5)',
fontSize = 10,
rotate = -15,
density = 0.6,
zIndex = 9999
} = options
// 移除已存在的水印
if (watermarkDom) {
document.body.removeChild(watermarkDom)
}
// 创建水印容器
watermarkDom = document.createElement('div')
watermarkDom.style.position = 'fixed'
watermarkDom.style.top = '0'
watermarkDom.style.left = '0'
watermarkDom.style.width = '100%'
watermarkDom.style.height = '100%'
watermarkDom.style.pointerEvents = 'none' // 不影响页面交互
watermarkDom.style.zIndex = zIndex
document.body.appendChild(watermarkDom)
// 生成Canvas水印图
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 计算单条水印大小(根据文字长度动态调整)
const textArr = text.split('\n')
const maxTextLength = Math.max(...textArr.map(t => t.length))
canvas.width = maxTextLength * fontSize * 1.2 // 宽度适配文字,增加间距
canvas.height = textArr.length * fontSize * 3.2 // 高度适配行数,增加间距
// 绘制文字
ctx.fillStyle = color
ctx.font = `${fontSize}px SimHei, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.rotate((Math.PI / 180) * rotate) // 旋转
// 多行文字绘制
textArr.forEach((line, index) => {
ctx.fillText(line, canvas.width / 2, canvas.height / 2 + (index - (textArr.length - 1) / 2) * fontSize * 1.5)
})
// 设置为背景图,重复平铺
watermarkDom.style.backgroundImage = `url(${canvas.toDataURL('image/png')})`
watermarkDom.style.backgroundSize = `${canvas.width / density}px ${canvas.height / density}px` // 调整密度控制逻辑
}
export default {
// 设置水印(创建或更新)
set: (options) => createWatermark(options),
// 移除水印
remove: () => {
if (watermarkDom && document.body.contains(watermarkDom)) {
document.body.removeChild(watermarkDom)
watermarkDom = null
}
}
}

View File

@ -1,10 +1,23 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="generate"
v-hasPermi="['base:calendar:add']"
>日历同步</el-button>
</el-col>
</el-row>
<el-calendar v-model="currentDate" ref="calendar"> <el-calendar v-model="currentDate" ref="calendar">
<template slot="dateCell" slot-scope="{ date, data }"> <template slot="dateCell" slot-scope="{ date, data }">
<div class="calendar-cell"> <div class="calendar-cell">
<div class="calendar-date">{{ data.day.split('-')[2] }}</div> <div class="calendar-date">{{ data.day.split('-')[2] }}</div>
<div class="lunar-date">{{ getLunarDate(data.day) }}</div> <div class="lunar-date">{{ getLunarDate(data.day) }}</div>
<div v-if="getWorkStatus(data.day)" class="work-status"></div>
</div> </div>
</template> </template>
</el-calendar> </el-calendar>
@ -12,23 +25,33 @@
</template> </template>
<script> <script>
import { listCalendar } from '@/api/base/calendar' import { listCalendar,generateCalendar } from '@/api/base/calendar'
export default { export default {
name: "CalendarView", name: 'CalendarView',
data() { data() {
return { return {
currentDate: new Date(), currentDate: new Date(),
calendarData: [] calendarData: []
}; }
}, },
created() { watch: {
this.fetchCalendarData(); // 1.使
currentDate(val, oldVal) {
const year = val.getFullYear()
const month = val.getMonth() + 1
this.fetchCalendarData(year, month)
}
}, },
mounted() {
const year = this.currentDate.getFullYear()
const month = this.currentDate.getMonth() + 1
this.fetchCalendarData(year, month)
},
methods: { methods: {
fetchCalendarData() { fetchCalendarData(year, month) {
const year = this.currentDate.getFullYear();
const month = this.currentDate.getMonth() + 1;
// //
listCalendar({ listCalendar({
pageNum: 1, pageNum: 1,
@ -36,32 +59,61 @@ export default {
year: year, year: year,
month: month month: month
}).then(response => { }).then(response => {
this.calendarData = response.data.records; this.calendarData = response.data.records
console.log(this.calendarData) })
}); },
generate(){
generateCalendar().then(res => {
this.$message.success("同步成功")
})
}, },
getLunarDate(dateStr) { /**
// * 根据日期获取日历记录
const date = new Date(dateStr); * @param {string} dateStr - 日期字符串
const year = date.getFullYear(); * @returns {Object|null} 对应的日期记录
const month = date.getMonth() + 1; */
const day = date.getDate(); getCalendarRecord(dateStr) {
const date = new Date(dateStr)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
// //
if (!this.calendarData || this.calendarData.length === 0) { if (!this.calendarData || this.calendarData.length === 0) {
return null; return null
} }
const record = this.calendarData.find(item =>
return this.calendarData.find(item =>
item.year === year && item.year === year &&
item.month === month && item.month === month &&
item.day === day item.day === day
) ) || null
return record ? record.lunarDate : null; },
/**
* 获取农历日期
* @param {string} dateStr - 日期字符串
* @returns {string|null} 农历日期
*/
getLunarDate(dateStr) {
const record = this.getCalendarRecord(dateStr)
return record ? record.lunarDate : null
},
/**
* 获取工作状态
* @param {string} dateStr - 日期字符串
* @returns {boolean} 是否为休息日
*/
getWorkStatus(dateStr) {
const record = this.getCalendarRecord(dateStr)
return record ? record.workStatus : null
} }
} }
}; }
</script> </script>
<style scoped> <style scoped>
@ -82,6 +134,13 @@ export default {
color: #999; color: #999;
} }
.work-status {
font-size: 12px;
color: #ef9e11;
font-weight: bold;
margin-top: 2px;
}
.el-calendar-table .el-calendar-day:hover { .el-calendar-table .el-calendar-day:hover {
background-color: #f2f8ff; background-color: #f2f8ff;
} }

View File

@ -32,6 +32,15 @@
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="eventSync"
>同步</el-button>
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="primary" type="primary"
@ -102,7 +111,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="total>0" v-show="total>0"
:total="total" :total="total"
@ -133,7 +142,7 @@
</template> </template>
<script> <script>
import { listEvent, getEvent, delEvent, addEvent, updateEvent } from "@/api/base/event"; import { listEvent, getEvent,syncEvent, delEvent, addEvent, updateEvent } from "@/api/base/event";
export default { export default {
name: "Event", name: "Event",
@ -176,6 +185,13 @@ export default {
this.getList(); this.getList();
}, },
methods: { methods: {
eventSync(){
syncEvent().then(res =>{
this.$modal.msgSuccess("同步成功");
})
},
/** 查询节日列表 */ /** 查询节日列表 */
getList() { getList() {
this.loading = true; this.loading = true;