协议问题解决

This commit is contained in:
2026-01-12 11:45:37 +08:00
parent 6336f89f0d
commit 26df740dd0
16 changed files with 728 additions and 243 deletions

View File

@@ -122,18 +122,6 @@
</div>
</div>
</div>
<!-- 上传按钮 -->
<div v-if="selectedFiles.length > 0" class="upload-actions">
<el-button
type="primary"
@click="handleUpload"
:loading="uploading"
:disabled="selectedFiles.length === 0"
>
{{ uploading ? '上传中...' : '确定上传' }}
</el-button>
</div>
</div>
<el-dialog
@@ -362,10 +350,8 @@ function handleFileSelect(event: Event) {
// 清空 input允许重复选择同一文件
input.value = '';
// cover模式下选择文件后立即上传
if (props.listType === 'cover') {
handleUpload();
}
// 选择文件后立即上传
handleUpload();
}
}

View File

@@ -10,9 +10,10 @@
<style lang="scss" scoped>
.blank-layout {
width: 100vw;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
</style>

View File

@@ -24,6 +24,23 @@ export const routes: Array<RouteRecordRaw> = [
requiresAuth: false,
menuType: 3,
}),
// 协议预览页面
{
path: "/agreement/:type",
component: () => import("@/layouts/BlankLayout.vue"),
children: [
{
path: "",
name: "Agreement",
component: () => import("@/views/public/agreement/AgreementView.vue"),
meta: {
title: "协议",
requiresAuth: false,
menuType: 3,
},
}
],
},
// 首页(显示在导航栏)
// {
// path: "/home",

View File

@@ -47,6 +47,22 @@ export default {
faviconUrl: (state: SystemState) => {
const fileId = state.baseInfo?.favicon;
return (fileId && fileId.trim()) ? `${FILE_DOWNLOAD_URL}${fileId}` : '/favicon.ico';
},
// 协议文件URL
platformAgreementUrl: (state: SystemState) => {
const fileId = state.baseInfo?.platformAgreement;
return (fileId && fileId.trim()) ? `${FILE_DOWNLOAD_URL}${fileId}` : '';
},
userAgreementUrl: (state: SystemState) => {
const fileId = state.baseInfo?.userAgreement;
return (fileId && fileId.trim()) ? `${FILE_DOWNLOAD_URL}${fileId}` : '';
},
privacyAgreementUrl: (state: SystemState) => {
const fileId = state.baseInfo?.privacyAgreement;
return (fileId && fileId.trim()) ? `${FILE_DOWNLOAD_URL}${fileId}` : '';
}
},

View File

@@ -10,6 +10,11 @@ export interface SystemBaseInfo {
adminLogo: string; // 管理后台LogofileId
favicon: string; // 网站图标fileId
// 登录协议文件ID
platformAgreement: string; // 平台协议fileId
userAgreement: string; // 用户协议fileId
privacyAgreement: string; // 隐私协议fileId
// 登录开关(来自不同分组)
smsLoginEnabled: boolean; // 短信登录开关sms分组
emailLoginEnabled: boolean; // 邮箱登录开关email分组

View File

@@ -122,6 +122,44 @@
:tip="item.remark || '点击上传图片支持jpg、png格式'"
/>
</el-form-item>
<!-- 文件上传(协议文档等) -->
<el-form-item
v-else-if="getRenderType(item) === 'fileupload'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<div class="file-upload-wrapper">
<FileUpload
:as-dialog="false"
:multiple="false"
accept=".pdf,.txt"
:max-size="100"
module="system"
:tip="item.remark || '支持PDF、TXT格式不超过100MB'"
@success="(files) => handleFileUploadSuccess(files, group.groupKey, item.configKey)"
/>
<div v-if="configData[group.groupKey][item.configKey]" class="uploaded-file-info">
<el-tag type="success" size="small">已上传</el-tag>
<el-button
type="primary"
link
size="small"
@click="previewAgreementFile(configData[group.groupKey][item.configKey])"
>
预览文件
</el-button>
<el-button
type="danger"
link
size="small"
@click="configData[group.groupKey][item.configKey] = ''"
>
删除
</el-button>
</div>
</div>
</el-form-item>
</template>
<!-- 操作按钮 -->
@@ -149,7 +187,6 @@ import { ElMessage } from 'element-plus';
import { configApi } from '@/apis/system';
import type { ConfigItem } from '@/types/system/config';
import FileUpload from '@/components/file/FileUpload.vue';
defineOptions({
name: 'SystemConfigView'
});
@@ -358,6 +395,26 @@ function handleCoverUpdate(url: string, groupKey: string, configKey: string) {
configData[groupKey][configKey] = url;
}
/**
* 处理文件上传成功(协议文档等)
*/
function handleFileUploadSuccess(files: any[], groupKey: string, configKey: string) {
if (files && files.length > 0) {
configData[groupKey][configKey] = files[0].fileID || '';
}
}
/**
* 预览协议文件新开tab页
*/
function previewAgreementFile(fileId: string) {
if (fileId) {
// 使用文件下载URL在新标签页打开
const url = `${import.meta.env.VITE_API_BASE_URL || ''}/api/file/preview/${fileId}`;
window.open(url, '_blank');
}
}
/**
* 保存指定分组的配置
*/
@@ -440,5 +497,16 @@ async function saveConfig(groupKey: string) {
color: #606266;
font-size: 14px;
}
.file-upload-wrapper {
width: 100%;
.uploaded-file-info {
margin-top: 10px;
display: flex;
align-items: center;
gap: 12px;
}
}
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<div class="agreement-container">
<div class="agreement-header">
<h1 class="agreement-title">{{ title }}</h1>
</div>
<div class="agreement-content" v-loading="loading">
<!-- PDF预览 -->
<div v-if="fileType === 'pdf'" class="pdf-viewer">
<VuePdfEmbed
:source="pdfSource"
:page="currentPage"
@loaded="onPdfLoaded"
@rendered="onPdfRendered"
/>
<!-- PDF分页控制 -->
<div v-if="totalPages > 1" class="pdf-pagination">
<el-button :disabled="currentPage <= 1" @click="currentPage--">上一页</el-button>
<span class="page-info">{{ currentPage }} / {{ totalPages }}</span>
<el-button :disabled="currentPage >= totalPages" @click="currentPage++">下一页</el-button>
</div>
</div>
<!-- TXT预览 -->
<div v-else-if="fileType === 'txt'" class="txt-viewer">
<pre>{{ textContent }}</pre>
</div>
<!-- 无内容 -->
<div v-else-if="!loading" class="empty-content">
<el-empty description="协议内容暂未上传" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
import { FILE_DOWNLOAD_URL } from '@/config';
import VuePdfEmbed from 'vue-pdf-embed';
const route = useRoute();
const store = useStore();
const loading = ref(true);
const textContent = ref('');
const fileType = ref<'pdf' | 'txt' | ''>('');
const pdfSource = ref<string>('');
const currentPage = ref(1);
const totalPages = ref(0);
// 协议类型对应的标题
const titleMap: Record<string, string> = {
platform: '红色思政智能体平台协议',
user: '用户协议',
privacy: '隐私协议'
};
// 获取协议类型
const agreementType = computed(() => route.params.type as string);
// 标题
const title = computed(() => titleMap[agreementType.value] || '协议');
// 获取文件ID
const fileId = computed(() => {
const baseInfo = store.state.system.baseInfo;
if (!baseInfo) return '';
switch (agreementType.value) {
case 'platform':
return baseInfo.platformAgreement || '';
case 'user':
return baseInfo.userAgreement || '';
case 'privacy':
return baseInfo.privacyAgreement || '';
default:
return '';
}
});
// 文件URL
const fileUrl = computed(() => {
if (!fileId.value) return '';
return `${FILE_DOWNLOAD_URL}${fileId.value}`;
});
// PDF加载完成
function onPdfLoaded(pdf: any) {
totalPages.value = pdf.numPages;
}
// PDF渲染完成
function onPdfRendered() {
loading.value = false;
}
onMounted(async () => {
// 确保系统配置已加载
if (!store.state.system.baseInfo) {
await store.dispatch('system/loadBaseInfo');
}
if (!fileId.value) {
loading.value = false;
return;
}
// 根据文件URL判断类型并加载
try {
const response = await fetch(fileUrl.value);
const contentType = response.headers.get('content-type') || '';
const contentDisposition = response.headers.get('content-disposition') || '';
// 判断是否为PDF
const isPdf = contentType.includes('pdf') ||
contentDisposition.includes('.pdf') ||
fileUrl.value.toLowerCase().includes('.pdf');
if (isPdf) {
fileType.value = 'pdf';
// 获取PDF的blob URL
const blob = await response.blob();
pdfSource.value = URL.createObjectURL(blob);
} else {
// 作为文本处理
fileType.value = 'txt';
textContent.value = await response.text();
loading.value = false;
}
} catch (error) {
console.error('加载协议文件失败:', error);
loading.value = false;
}
});
</script>
<style lang="scss" scoped>
.agreement-container {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.agreement-header {
background: #fff;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.agreement-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #333;
text-align: center;
}
}
.agreement-content {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
}
.pdf-viewer {
flex: 1;
background: #fff;
border-radius: 8px;
padding: 20px;
overflow-x: hidden;
overflow-y: auto;
:deep(.vue-pdf-embed) {
display: flex;
justify-content: center;
}
:deep(canvas) {
max-width: 100%;
height: auto !important;
display: block;
}
}
.pdf-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
.page-info {
font-size: 14px;
color: #666;
}
}
.txt-viewer {
flex: 1;
background: #fff;
border-radius: 8px;
padding: 24px;
overflow: auto;
pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 14px;
line-height: 1.8;
color: #333;
}
}
.empty-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 8px;
}
</style>

View File

@@ -181,8 +181,8 @@
<div class="agreement-wrapper">
<el-checkbox v-model="loginForm.agree" />
<span class="agreement-text">
登录即为同意&nbsp;<span class="agreement-link">红色思政智能体平台</span><span class="agreement-link">用户协议</span>
<span class="agreement-link">隐私协议</span>
登录即为同意&nbsp;<span class="agreement-link" @click="openAgreement('platform')">红色思政智能体平台</span><span class="agreement-link" @click="openAgreement('user')">用户协议</span>
<span class="agreement-link" @click="openAgreement('privacy')">隐私协议</span>
</span>
</div>
</el-form-item>
@@ -519,6 +519,15 @@ function goToForgotPassword() {
router.push('/forgot-password');
}
/**
* 打开协议文件(新标签页)
*/
function openAgreement(type: 'platform' | 'user' | 'privacy') {
// 跳转到协议预览页面
const url = router.resolve({ path: `/agreement/${type}` }).href;
window.open(url, '_blank');
}
// 组件挂载时检查是否需要显示验证码
onMounted(() => {
// 可以根据需要决定是否默认显示验证码

View File

@@ -181,9 +181,9 @@
<div class="agreement-wrapper">
<el-checkbox v-model="loginForm.agree" />
<span class="agreement-text">
登录即为同意&nbsp;<span class="agreement-link">红色思政智能体平台</span>
<span class="agreement-link">用户协议</span>
<span class="agreement-link">隐私协议</span>
登录即为同意&nbsp;<span class="agreement-link" @click="openAgreement('platform')">红色思政智能体平台</span>
<span class="agreement-link" @click="openAgreement('user')">用户协议</span>
<span class="agreement-link" @click="openAgreement('privacy')">隐私协议</span>
</span>
</div>
</el-form-item>
@@ -521,6 +521,15 @@ function goToForgotPassword() {
router.push('/forgot-password');
}
/**
* 打开协议文件(新标签页)
*/
function openAgreement(type: 'platform' | 'user' | 'privacy') {
// 跳转到协议预览页面
const url = router.resolve({ path: `/agreement/${type}` }).href;
window.open(url, '_blank');
}
// 组件挂载时检查是否需要显示验证码
onMounted(() => {
// 可以根据需要决定是否默认显示验证码

View File

@@ -167,8 +167,8 @@
<div class="agreement-wrapper">
<el-checkbox v-model="registerForm.agree" />
<span class="agreement-text">
注册即为同意&nbsp;<span class="agreement-link">《红色思政智能体平台》</span><span class="agreement-link">《用户协议》</span>
<span class="agreement-link">《隐私协议》</span>
注册即为同意&nbsp;<span class="agreement-link" @click="openAgreement('platform')">《红色思政智能体平台》</span><span class="agreement-link" @click="openAgreement('user')">《用户协议》</span>
<span class="agreement-link" @click="openAgreement('privacy')">《隐私协议》</span>
</span>
</div>
</el-form-item>
@@ -515,6 +515,15 @@ onUnmounted(() => {
clearInterval(emailTimer);
}
});
/**
* 打开协议文件(新标签页)
*/
function openAgreement(type: 'platform' | 'user' | 'privacy') {
// 跳转到协议预览页面
const url = router.resolve({ path: `/agreement/${type}` }).href;
window.open(url, '_blank');
}
</script>
<style lang="scss" scoped>

View File

@@ -174,8 +174,8 @@
<div class="agreement-wrapper">
<el-checkbox v-model="registerForm.agree" />
<span class="agreement-text">
注册即为同意&nbsp;<span class="agreement-link">红色思政智能体平台</span><span class="agreement-link">用户协议</span>
<span class="agreement-link">隐私协议</span>
注册即为同意&nbsp;<span class="agreement-link" @click="openAgreement('platform')">红色思政智能体平台</span><span class="agreement-link" @click="openAgreement('user')">用户协议</span>
<span class="agreement-link" @click="openAgreement('privacy')">隐私协议</span>
</span>
</div>
</el-form-item>
@@ -522,6 +522,15 @@ function goToLogin() {
router.push('/login');
}
/**
* 打开协议文件(新标签页)
*/
function openAgreement(type: 'platform' | 'user' | 'privacy') {
// 跳转到协议预览页面
const url = router.resolve({ path: `/agreement/${type}` }).href;
window.open(url, '_blank');
}
// 组件卸载时清除定时器
import { onUnmounted } from 'vue';
onUnmounted(() => {