开源版重新整理提交

This commit is contained in:
mayongjian
2025-01-20 17:51:04 +08:00
parent dce3cd54a5
commit fcfe7dbd9d
44 changed files with 719 additions and 623 deletions

118
README.md
View File

@@ -13,7 +13,7 @@
</p>
## 平台简介
* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev/) 版本。
* 本仓库为红薯管理端:前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev/) 版本。
* 配套后端代码仓库地址 [HongShu](https://gitee.com/Maverick_Ma/hongshu.git)
## 前端运行
@@ -25,120 +25,12 @@ git clone https://gitee.com/Maverick_Ma/hongshu-admin.git
cd hongshu-admin
# 安装依赖
yarn --registry=https://registry.npmmirror.com
npm install
# 启动服务
yarn dev
npm run dev
# 构建测试环境 yarn build:stage
# 构建生产环境 yarn build:prod
# 构建测试环境 npm build:stage
# 构建生产环境 npm build:prod
# 前端访问地址 http://localhost:81
```
## 管理端内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 系统接口根据业务代码自动生成相关的api接口文档。
14. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
15. 缓存监控:对系统的缓存信息查询,命令统计等。
16. 在线构建器拖动表单元素生成相应的HTML代码。
17. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 2.0版本实现功能
1. 添加移动端
2. 重构实现 SpringCloud 微服务架构
3. 加入商城购物功能
4. 加入推荐算法,优化内容推荐和用户推荐功能
5. 使用 MQ+Redis 优化点赞、收藏、浏览功能
6. 支持七牛云、阿里、腾讯、Minio等多种oss对象存储方式
7. 笔记内容支持视频和live图
#### ⚠如有【项目问题】或【部署需求】可联系微信coder_xiaomage
## 演示站2.0版)
- web端 ➡️ [点我体验](http://47.95.205.22)
- admin端 ➡️ [点我体验](http://47.95.205.22/admin/)
- app端 ➡️ [点我体验](http://47.95.205.22/app/)
- 文档及资料会暂时放到我的个人博客:[点我进入](https://mayongjian.cn)
* 由于服务器资源有限,首次加载可能缓慢一些。
* 同时为优化服务器也感谢小伙伴们打赏支持❤️。
<img src="src/assets/images/reward.png" style="width: 50px heihgt: 50px"/>
## 视频演示
➡️ [点击查看](https://www.bilibili.com/video/BV1QP8dekEGq/?spm_id_from=333.999.list.card_archive.click&vd_source=ec9224821314432ac6e12dc7d500d74b)
## 演示图
### - web端
<table>
<tr>
<td><img src="src/assets/images/web/web-login.png"/></td>
<td><img src="src/assets/images/web/web-dashboard.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/web/web-search.png"/></td>
<td><img src="src/assets/images/web/web-trends.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/web/web-message.png"/></td>
<td><img src="src/assets/images/web/web-follow.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/web/web-publish.png"/></td>
<td><img src="src/assets/images/web/web-user.png"/></td>
</tr>
</table>
### - admin端
<table>
<tr>
<td><img src="src/assets/images/admin/admin-login.png"/></td>
<td><img src="src/assets/images/admin/admin-data.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/admin/admin-category.png"/></td>
<td><img src="src/assets/images/admin/admin-member.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/admin/admin-note.png"/></td>
<td><img src="src/assets/images/admin/admin-album.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/admin/admin-comment.png"/></td>
<td><img src="src/assets/images/admin/admin-log.png"/></td>
</tr>
</table>
### - app端
<table>
<tr>
<td><img src="src/assets/images/app/app-login.png"/></td>
<td><img src="src/assets/images/app/app-index.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/app/app-trend.png"/></td>
<td><img src="src/assets/images/app/app-message.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/app/app-user.png"/></td>
<td><img src="src/assets/images/app/app-follow.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/app/app-hot.png"/></td>
<td><img src="src/assets/images/app/app-main.png"/></td>
</tr>
<tr>
<td><img src="src/assets/images/app/app-search.png"/></td>
<td><img src="src/assets/images/app/app-push.png"/></td>
</tr>
</table>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

View File

@@ -5,27 +5,37 @@
<script setup>
import { getBlogContributeCount } from "@/api/web/index";
import * as echarts from "echarts";
import { onMounted, ref } from "vue";
import { onMounted, onBeforeUnmount, ref, watch } from "vue";
const contributeDate = ref([]);
const blogContributeCount = ref([]);
let chart = null;
async function initDate() {
getBlogContributeCount().then((response) => {
if (response.code == 200) {
try {
const response = await getBlogContributeCount();
if (response.code === 200) {
contributeDate.value = response.data.contributeDate;
blogContributeCount.value = response.data.blogContributeCount;
renderChart();
}
} catch (error) {
console.error("获取数据失败:", error);
}
}
function renderChart() {
const container = document.getElementById("container");
if (!container) {
console.error("容器元素未找到");
return;
}
let chart = echarts.init(container);
console.log("ECharts实例化成功:", chart);
if (!chart) {
chart = echarts.init(container);
}
let option = {
const option = {
title: {
top: 30,
text: "笔记贡献度",
@@ -77,13 +87,11 @@ async function initDate() {
},
},
itemStyle: {
normal: {
color: "#ffffff",
borderWidth: 1,
borderColor: "#D3D3D3",
},
},
},
],
series: [
{
@@ -92,30 +100,35 @@ async function initDate() {
coordinateSystem: "calendar",
data: blogContributeCount.value,
symbolSize: function (val) {
if (val[1] == 0) {
return val[1];
} else {
let size = 8 + val[1] * 2;
if (size > 18) {
size = 18;
}
let size = val[1] === 0 ? 0 : Math.min(18, 8 + val[1] * 2);
return size;
}
},
itemStyle: {
normal: {
color: "#2ec7c9",
},
},
},
],
};
chart.setOption(option);
}
});
}
function resizeChart() {
chart && chart.resize();
}
onMounted(() => {
initDate();
window.addEventListener("resize", resizeChart);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", resizeChart);
if (chart) {
chart.dispose();
chart = null;
}
});
watch([contributeDate, blogContributeCount], renderChart);
</script>

View File

@@ -1,6 +1,6 @@
<template>
<a
href=""
href="https://gitee.com/Maverick_Ma/hongshu"
target="_blank"
class="github-corner"
aria-label="View source on Github"

View File

@@ -5,9 +5,9 @@
</template>
<script setup>
const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
const url = ref("https://gitee.com/Maverick_Ma/hongshu");
function goto() {
window.open(url.value)
window.open(url.value);
}
</script>

View File

@@ -5,9 +5,9 @@
</template>
<script setup>
const url = ref('https://gitee.com/y_project/RuoYi-Vue');
const url = ref("https://gitee.com/Maverick_Ma/hongshu");
function goto() {
window.open(url.value)
window.open(url.value);
}
</script>

View File

@@ -1,8 +1,21 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
<hamburger
id="hamburger-container"
:is-active="appStore.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb
id="breadcrumb-container"
class="breadcrumb-container"
v-if="!settingsStore.topNav"
/>
<top-nav
id="topmenu-container"
class="topmenu-container"
v-if="settingsStore.topNav"
/>
<div class="right-menu">
<template v-if="appStore.device !== 'mobile'">
@@ -23,7 +36,11 @@
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<el-dropdown
@command="handleCommand"
class="right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
@@ -33,7 +50,10 @@
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<el-dropdown-item
command="setLayout"
v-if="settingsStore.showSettings"
>
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
@@ -48,25 +68,25 @@
</template>
<script setup>
import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import { ElMessageBox } from "element-plus";
import Breadcrumb from "@/components/Breadcrumb";
import TopNav from "@/components/TopNav";
import Hamburger from "@/components/Hamburger";
import Screenfull from "@/components/Screenfull";
import SizeSelect from "@/components/SizeSelect";
import HeaderSearch from "@/components/HeaderSearch";
import RuoYiGit from "@/components/RuoYi/Git";
import RuoYiDoc from "@/components/RuoYi/Doc";
import useAppStore from "@/store/modules/app";
import useUserStore from "@/store/modules/user";
import useSettingsStore from "@/store/modules/settings";
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const appStore = useAppStore();
const userStore = useUserStore();
const settingsStore = useSettingsStore();
function toggleSideBar() {
appStore.toggleSideBar()
appStore.toggleSideBar();
}
function handleCommand(command) {
@@ -83,24 +103,26 @@ function handleCommand(command) {
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index';
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
}).catch(() => { });
.then(() => {
userStore.logOut().then(() => {
location.href = "/index";
});
})
.catch(() => {});
}
const emits = defineEmits(['setLayout'])
const emits = defineEmits(["setLayout"]);
function setLayout() {
emits('setLayout');
emits("setLayout");
}
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;

View File

@@ -145,19 +145,19 @@ export const dynamicRoutes = [
]
},
{
path: '/tool/gen-edit',
path: '/web/comment-data',
component: Layout,
hidden: true,
permissions: ['tool:gen:edit'],
permissions: ['web:comment:list'],
children: [
{
path: 'index/:tableId(\\d+)',
component: () => import('@/views/tool/gen/editTable'),
name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
path: 'index/:nid(\\d+)',
component: () => import('@/views/web/comment/data'),
name: 'CommentData',
meta: { title: '评论数据', activeMenu: '/web/comment' }
}
]
}
},
]
const router = createRouter({

View File

@@ -35,7 +35,8 @@ const useUserStore = defineStore(
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar;
// const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
this.roles = res.roles

View File

@@ -96,7 +96,7 @@
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<!-- <el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<PieChart
v-if="showPieChart"
@@ -105,7 +105,7 @@
:tagName="tagNameArray"
/>
</div>
</el-col>
</el-col> -->
</el-row>
</div>
</template>
@@ -213,7 +213,7 @@ async function fetchData() {
const btnClick = (id) => {
const routes = {
1: "/system/log/webLoginInfo",
1: "/loginlog/loginlog",
2: "/member/member",
3: "/message/comment",
4: "/note/note",

View File

@@ -27,15 +27,17 @@
type="primary"
icon="Cloudy"
plain
@click="goTarget('https://gitee.com/y_project/RuoYi-Vue')"
>访问码云</el-button
@click="goTarget('https://gitee.com/Maverick_Ma/hongshu')"
>
访问码云
</el-button>
<el-button
icon="HomeFilled"
plain
@click="goTarget('http://ruoyi.vip')"
>访问主页</el-button
@click="goTarget('https://gitee.com/Maverick_Ma/hongshu')"
>
访问主页
</el-button>
</p>
</el-col>
@@ -106,24 +108,6 @@
</h3>
</div>
<blockquote class="text-warning" style="font-size: 16px">
领取阿里云通用云产品1888优惠券
<br />
<el-link
href="https://www.aliyun.com/minisite/goods?source=5176.11533457&userCode=ojvsntx1"
type="primary"
target="_blank"
> 点我进入 </el-link
>
<br />
领取腾讯云通用云产品2860优惠券
<br />
<el-link
href="https://curl.qcloud.com/efTJbNyi"
type="primary"
target="_blank"
> 点我进入 </el-link
>
<br />
阿里云服务器折扣区
<br />
<el-link
@@ -151,7 +135,7 @@
</el-col>
<!-- 捐赠支持 -->
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-col :xs="24" :sm="24" :md="12" :lg="6">
<el-card class="update-log">
<template v-slot:header>
<div class="clearfix">
@@ -160,7 +144,7 @@
</template>
<div class="body">
<img
src="@/assets/images/reward.png"
src="@/assets/images/pay.png"
alt="donate"
style="width: 92%"
/>

View File

@@ -261,14 +261,11 @@ const { queryParams, form, rules } = toRefs(data);
function subjectList() {
getSubjectList(queryParams.value).then((response) => {
tableData.value = response.rows;
currentPage.value = 1;
pageSize.value = 20;
total.value = response.total;
});
}
function handleFind() {
currentPage.value = 1;
subjectList();
}
@@ -296,8 +293,8 @@ function checkAll() {
chooseTitle.value = "全选";
} else {
selectUids.value = [];
tableData.value.forEach((picture) => {
selectUids.value.push(picture.uid);
tableData.value.forEach((album) => {
selectUids.value.push(album.id);
});
isCheckedAll.value = true;
chooseTitle.value = "取消全选";

View File

@@ -1,8 +1,86 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
>
<el-form-item label="评论人" prop="dictLabel">
<el-input
v-model="queryParams.dictLabel"
placeholder="请输入评论人"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="评论内容" prop="dictLabel">
<el-input
v-model="queryParams.dictLabel"
placeholder="请输入评论内容"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="评论时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
>
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:dict:remove']"
>
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['system:dict:export']"
>
导出
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">
关闭
</el-button>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="commentList"
:data="dataList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
@@ -12,10 +90,16 @@
<el-avatar :size="60" :src="scope.row.avatar"> </el-avatar>
</template>
</el-table-column>
<el-table-column label="评论人" align="center" prop="uid" />
<el-table-column label="被评论人" align="center" prop="noteUid" />
<el-table-column label="评论人" align="center" prop="username" />
<el-table-column label="被评论人" align="center" prop="pushUsername" />
<el-table-column label="评论内容" align="center" prop="content" />
<el-table-column label="评论等级" align="center" prop="level" />
<el-table-column label="评论等级" align="center" prop="level">
<template #default="scope">
<el-tag :type="getTagType(scope.row.level)">
{{ getLevelText(scope.row.level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="评论时间"
align="center"
@@ -26,23 +110,31 @@
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
fixed="right"
min-width="200"
width="160"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-tooltip content="删除" placement="top">
<el-button
type="danger"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:edit']"
>
修改
</el-button>
<el-button
link
type="primary"
icon="Delete"
size="small"
@click="handleDelete(scope.row)"
v-hasPermi="['web:member:remove']"
/>
</el-tooltip>
v-hasPermi="['system:dict:remove']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
@@ -54,18 +146,136 @@
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="dataRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="form.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number
v-model="form.dictSort"
controls-position="right"
:min="0"
/>
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="form.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入内容"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Comment">
import { getCommentByNid, delComment } from "@/api/web/comment";
<script setup name="Data">
import useDictStore from "@/store/modules/dict";
import {
optionselect as getDictOptionselect,
getType,
} from "@/api/system/dict/type";
import {
listData,
getData,
delData,
addData,
updateData,
} from "@/api/web/comment/data";
const { proxy } = getCurrentInstance();
const dateRange = ref([]);
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const commentList = ref([]);
const dataList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const defaultDictType = ref("");
const typeOptions = ref([]);
const route = useRoute();
// 数据标签回显样式
const listClassOptions = ref([
{ value: "default", label: "默认" },
{ value: "primary", label: "主要" },
{ value: "success", label: "成功" },
{ value: "info", label: "信息" },
{ value: "warning", label: "警告" },
{ value: "danger", label: "危险" },
]);
const levelMap = {
1: "一级评论",
2: "二级评论",
3: "三级评论",
4: "四级评论",
5: "五级评论",
};
// 根据 level 值返回对应的颜色
const getTagType = (level) => {
switch (level) {
case 1:
return "success"; // 一级评论,绿色
case 2:
return "primary"; // 二级评论,蓝色
case 3:
return "warning"; // 三级评论,黄色
case 4:
return "danger"; // 四级评论,红色
case 5:
return "info"; // 五级评论,蓝色(或者可以用其他颜色)
default:
return "default"; // 默认颜色
}
};
// 根据 level 返回对应的文本
const getLevelText = (level) => {
return levelMap[level] || "其他评论";
};
const data = reactive({
form: {},
@@ -76,34 +286,153 @@ const data = reactive({
noteUid: undefined,
content: undefined,
},
rules: {},
rules: {
dictLabel: [
{ required: true, message: "数据标签不能为空", trigger: "blur" },
],
dictValue: [
{ required: true, message: "数据键值不能为空", trigger: "blur" },
],
dictSort: [
{ required: true, message: "数据顺序不能为空", trigger: "blur" },
],
},
});
const { queryParams, form, rules } = toRefs(data);
/** 查询评论列表 */
function getList() {
loading.value = true;
getCommentByNid(queryParams.value).then((response) => {
commentList.value = response.rows;
total.value = response.total;
loading.value = false;
/** 查询字典类型详细 */
function getTypes(nid) {
getType(nid).then((response) => {
queryParams.value.dictType = response.data.dictType;
defaultDictType.value = response.data.dictType;
getList();
});
}
/** 查询字典类型列表 */
function getTypeList() {
getDictOptionselect().then((response) => {
typeOptions.value = response.data;
});
}
/** 查询字典数据列表 */
function getList(nid) {
loading.value = true;
queryParams.value.nid = nid;
listData(proxy.addDateRange(queryParams.value, dateRange.value)).then(
(response) => {
dataList.value = response.rows;
total.value = response.total;
loading.value = false;
}
);
}
/** 取消按钮 */
function cancel() {
open.value = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = {
dictCode: undefined,
dictLabel: undefined,
dictValue: undefined,
cssClass: undefined,
listClass: "default",
dictSort: 0,
status: "0",
remark: undefined,
};
proxy.resetForm("dataRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 返回按钮操作 */
function handleClose() {
const obj = { path: "/message/comment" };
proxy.$tab.closeOpenPage(obj);
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
queryParams.value.dictType = defaultDictType.value;
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加字典数据";
form.value.dictType = queryParams.value.dictType;
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.dictCode);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const dictCode = row.dictCode || ids.value;
getData(dictCode).then((response) => {
form.value = response.data;
open.value = true;
title.value = "修改字典数据";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["dataRef"].validate((valid) => {
if (valid) {
if (form.value.dictCode != undefined) {
updateData(form.value).then((response) => {
useDictStore().removeDict(queryParams.value.dictType);
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addData(form.value).then((response) => {
useDictStore().removeDict(queryParams.value.dictType);
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const commentIds = row.id || ids.value;
const dictCodes = row.dictCode || ids.value;
proxy.$modal
.confirm('是否确认删除评论编号为"' + commentIds + '"的数据项?')
.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?')
.then(function () {
return delComment(commentIds);
return delData(dictCodes);
})
.then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
useDictStore().removeDict(queryParams.value.dictType);
})
.catch(() => {});
}
getList();
/** 导出按钮操作 */
function handleExport() {
proxy.download(
"system/dict/data/export",
{
...queryParams.value,
},
`dict_data_${new Date().getTime()}.xlsx`
);
}
getList(route.params && route.params.nid);
</script>

View File

@@ -54,11 +54,38 @@
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button type="primary" icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['web:comment:remove']"
>
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['web:comment:export']"
>
导出
</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList">
</right-toolbar>
</el-row>
</el-form>
<el-table
@@ -68,34 +95,53 @@
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" prop="id" />
<el-table-column label="评论人" align="center" prop="username" />
<el-table-column label="头像" align="center" prop="avatar">
<template #default="scope">
<el-avatar :size="60" :src="scope.row.avatar"> </el-avatar>
<el-avatar :size="50" :src="scope.row.avatar"> </el-avatar>
</template>
</el-table-column>
<el-table-column label="评论人" align="center" prop="username" />
<el-table-column label="被评论人" align="center" prop="pushUsername" />
<el-table-column label="被评论笔记" align="center" prop="title">
<el-table-column label="评论人" align="center" prop="replyUsername" />
<el-table-column label="头像" align="center" prop="replyAvatar">
<template #default="scope">
<el-link
type="primary"
:underline="false"
@click="handleDetail(scope.row)"
>
<el-tooltip
class="item"
effect="dark"
:content="scope.row.title"
placement="top"
>
<div class="table-cell">{{ scope.row.title }}</div>
</el-tooltip>
</el-link>
<el-avatar :size="50" :src="scope.row.replyAvatar"> </el-avatar>
</template>
</el-table-column>
<el-table-column
label="笔记"
width="120"
align="center"
:show-overflow-tooltip="true"
>
<template #default="scope">
<router-link
:to="'/web/comment-data/index/' + scope.row.nid"
class="link-type"
>
<span>{{ scope.row.title }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="笔记封面" align="center" prop="noteCover">
<template #default="scope">
<el-avatar
:size="60"
:src="scope.row.noteCover"
shape="square"
@mouseover="showPreview(scope.row.noteCover, $event)"
@mouseleave="hidePreview"
/>
</template>
</el-table-column>
<el-table-column label="笔记作者" align="center" prop="pushUsername" />
<el-table-column label="评论内容" align="center" prop="content" />
<el-table-column label="评论等级" align="center" prop="level" />
<el-table-column label="评论等级" align="center" prop="level">
<template #default="scope">
<el-tag :type="getTagType(scope.row.level)">
{{ getLevelText(scope.row.level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="评论时间"
align="center"
@@ -134,6 +180,11 @@
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 放大预览图片的容器 -->
<div v-if="previewVisible" class="image-preview" :style="previewStyle">
<img :src="previewSrc" alt="Preview" />
</div>
</div>
</template>
@@ -146,6 +197,9 @@ const router = useRouter();
const commentList = ref([]);
const open = ref(false);
const previewVisible = ref(false);
const previewSrc = ref("");
const previewStyle = reactive({ top: "0px", left: "0px" });
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
@@ -154,6 +208,36 @@ const multiple = ref(true);
const total = ref(0);
const dateRange = ref([]);
const levelMap = {
1: "一级评论",
2: "二级评论",
3: "三级评论",
4: "四级评论",
5: "五级评论",
};
// 根据 level 值返回对应的颜色
const getTagType = (level) => {
switch (level) {
case 1:
return "success"; // 一级评论,绿色
case 2:
return "primary"; // 二级评论,蓝色
case 3:
return "warning"; // 三级评论,黄色
case 4:
return "danger"; // 四级评论,红色
case 5:
return "info"; // 五级评论,蓝色(或者可以用其他颜色)
default:
return "default"; // 默认颜色
}
};
// 根据 level 返回对应的文本
const getLevelText = (level) => {
return levelMap[level] || "其他评论";
};
const data = reactive({
form: {},
queryParams: {
@@ -168,6 +252,20 @@ const data = reactive({
const { queryParams, form } = toRefs(data);
/** 显示放大预览图片 */
function showPreview(src, event) {
previewSrc.value = src;
previewVisible.value = true;
previewStyle.top = event.clientY + 10 + "px";
previewStyle.left = event.clientX + 10 + "px";
}
/** 隐藏放大预览图片 */
function hidePreview() {
previewVisible.value = false;
previewSrc.value = "";
}
/** 查询评论列表 */
function getList() {
loading.value = true;
@@ -214,5 +312,33 @@ function handleDelete(row) {
})
.catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download(
"system/post/export",
{
...queryParams.value,
},
`post_${new Date().getTime()}.xlsx`
);
}
getList();
</script>
<style scoped>
.image-preview {
position: fixed;
z-index: 1000;
border: 1px solid #ddd;
background-color: #fff;
padding: 5px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.image-preview img {
max-width: 200px;
max-height: 200px;
}
</style>

View File

@@ -111,10 +111,8 @@
>导出</el-button
>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList">
</right-toolbar>
</el-row>
<el-table

View File

@@ -86,9 +86,9 @@
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button type="primary" icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置 </el-button>
</el-form-item>
</el-form>
@@ -619,8 +619,8 @@ onMounted(() => {
}
.image-preview img {
max-width: 200px;
max-height: 200px;
max-width: 300px;
max-height: 300px;
}
.image-uploader .el-upload {

View File

@@ -28,9 +28,9 @@
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button type="primary" icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置 </el-button>
</el-form-item>
</el-form>

View File

@@ -1,266 +0,0 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="登录地址" prop="ipaddr">
<el-input
v-model="queryParams.ipaddr"
placeholder="请输入登录地址"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="用户名称" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="登录状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_common_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="登录时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 1, 1, 23, 59, 59),
]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['web:logininfor:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
@click="handleClean"
v-hasPermi="['monitor:logininfor:remove']"
>清空</el-button
>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table
ref="loginInforRef"
v-loading="loading"
:data="loginInfoList"
@selection-change="handleSelectionChange"
:default-sort="defaultSort"
@sort-change="handleSortChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="登录编号" align="center" prop="id" />
<el-table-column
label="用户名称"
align="center"
prop="username"
:show-overflow-tooltip="true"
sortable="custom"
:sort-orders="['descending', 'ascending']"
/>
<el-table-column
label="IP地址"
align="center"
prop="ipaddr"
:show-overflow-tooltip="true"
/>
<el-table-column
label="登录地点"
align="center"
prop="loginLocation"
:show-overflow-tooltip="true"
/>
<el-table-column
label="操作系统"
align="center"
prop="os"
:show-overflow-tooltip="true"
/>
<el-table-column
label="浏览器"
align="center"
prop="browser"
:show-overflow-tooltip="true"
/>
<el-table-column label="登录状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_common_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="描述"
align="center"
prop="msg"
:show-overflow-tooltip="true"
/>
<el-table-column
label="访问时间"
align="center"
prop="loginTime"
sortable="custom"
:sort-orders="['descending', 'ascending']"
width="180"
>
<template #default="scope">
<span>{{ parseTime(scope.row.loginTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script setup name="LoginInfo">
import { list, delLoginInfo, cleanLoginInfo } from "@/api/web/loginInfor";
const { proxy } = getCurrentInstance();
const { sys_common_status } = proxy.useDict("sys_common_status");
const loginInfoList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const selectName = ref("");
const total = ref(0);
const dateRange = ref([]);
const defaultSort = ref({ prop: "loginTime", order: "descending" });
// 查询参数
const queryParams = ref({
pageNum: 1,
pageSize: 10,
ipaddr: undefined,
username: undefined,
status: undefined,
orderByColumn: undefined,
isAsc: undefined,
});
/** 查询登录日志列表 */
function getList() {
loading.value = true;
list(proxy.addDateRange(queryParams.value, dateRange.value)).then(
(response) => {
loginInfoList.value = response.rows;
total.value = response.total;
loading.value = false;
}
);
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
queryParams.value.pageNum = 1;
proxy.$refs["loginInforRef"].sort(
defaultSort.value.prop,
defaultSort.value.order
);
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.id);
multiple.value = !selection.length;
single.value = selection.length != 1;
selectName.value = selection.map((item) => item.userName);
}
/** 排序触发事件 */
function handleSortChange(column, prop, order) {
queryParams.value.orderByColumn = column.prop;
queryParams.value.isAsc = column.order;
getList();
}
/** 删除按钮操作 */
function handleDelete(row) {
const infoIds = row.id || ids.value;
proxy.$modal
.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?')
.then(function () {
return delLoginInfo(infoIds);
})
.then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}
/** 清空按钮操作 */
function handleClean() {
proxy.$modal
.confirm("是否确认清空所有登录日志数据项?")
.then(function () {
return cleanLoginInfo();
})
.then(() => {
getList();
proxy.$modal.msgSuccess("清空成功");
})
.catch(() => {});
}
getList();
</script>