mirror of
https://gitee.com/myxzgzs/boyuehasfj-vue3-html.git
synced 2025-08-08 07:02:42 +08:00
1453 lines
33 KiB
Vue
1453 lines
33 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted, computed, reactive } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import {
|
||
getPageDetail,
|
||
updateViewCount,
|
||
addFeedback,
|
||
listFeedback,
|
||
queryFeedbackDetail,
|
||
} from '../services/api'
|
||
import TheNavbar from '../components/TheNavbar.vue'
|
||
const route = useRoute()
|
||
const loading = ref(true)
|
||
const error = ref(false)
|
||
const page = ref<any>(null)
|
||
|
||
const feedbackForm = reactive({
|
||
issueDescription: '',
|
||
phoneNumber: '',
|
||
})
|
||
|
||
const feedbackSubmitSuccess = ref(false)
|
||
const feedbackNoReturned = ref('')
|
||
|
||
// 处理内容,将#标题#格式的文本转换为可折叠卡片
|
||
const processedContent = computed(() => {
|
||
if (!page.value || !page.value.content) return ''
|
||
|
||
// 正则表达式匹配 #标题# 格式
|
||
const regex = /#([^#]+)#([\s\S]*?)(?=#[^#]+#|$)/g
|
||
let index = 0
|
||
|
||
// 替换为可折叠卡片HTML,不使用内联onclick
|
||
const result = page.value.content.replace(
|
||
regex,
|
||
(match: string, title: string, content: string) => {
|
||
// 去除前后空白
|
||
title = title.trim()
|
||
content = content.trim()
|
||
const currentIndex = index++
|
||
|
||
return `
|
||
<div class="law-card" data-card-index="${currentIndex}">
|
||
<div class="law-card-header" data-card-index="${currentIndex}">
|
||
<span class="law-card-title">${title}</span>
|
||
<span class="law-card-toggle">▼</span>
|
||
</div>
|
||
<div class="law-card-content">
|
||
${content}
|
||
</div>
|
||
</div>
|
||
`
|
||
},
|
||
)
|
||
|
||
return result
|
||
})
|
||
|
||
// 展开或折叠卡片的处理函数
|
||
const expandedCards = ref<Set<number>>(new Set())
|
||
|
||
const toggleCard = (index: number) => {
|
||
const card = document.querySelector(`.law-card[data-card-index="${index}"]`)
|
||
if (card) {
|
||
if (expandedCards.value.has(index)) {
|
||
expandedCards.value.delete(index)
|
||
card.classList.remove('expanded')
|
||
} else {
|
||
expandedCards.value.add(index)
|
||
card.classList.add('expanded')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 计算附件列表
|
||
const attachmentList = computed(() => {
|
||
if (!page.value || !page.value.multiAttachments) return []
|
||
try {
|
||
const parsed = JSON.parse(page.value.multiAttachments)
|
||
// 过滤无效的附件项
|
||
return Array.isArray(parsed) ? parsed.filter((item) => item && (item.url || item.name)) : []
|
||
} catch (e) {
|
||
console.error('解析附件列表失败:', e)
|
||
return []
|
||
}
|
||
})
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
const formatId = route.query.Id as string
|
||
if (!formatId) {
|
||
console.error('缺少必要的formatId参数')
|
||
error.value = true
|
||
loading.value = false
|
||
return
|
||
}
|
||
|
||
console.log('开始获取表单详情,formatId:', formatId)
|
||
// 获取表单数据
|
||
const result = await getPageDetail('form', formatId)
|
||
console.log('表单详情请求结果:', result)
|
||
|
||
if (result && result.code === 200 && result.data) {
|
||
page.value = result.data
|
||
console.log('成功获取表单详情:', page.value)
|
||
|
||
// 更新浏览量
|
||
try {
|
||
const viewResult = (await updateViewCount('form', formatId)) as any
|
||
console.log('更新浏览量结果:', viewResult)
|
||
|
||
// 确认服务器端更新成功
|
||
if (viewResult && viewResult.code === 200) {
|
||
// 如果后端返回了新的浏览量数据
|
||
if (viewResult.data && typeof viewResult.data.viewCount === 'number') {
|
||
page.value.viewCount = viewResult.data.viewCount
|
||
}
|
||
// 否则本地递增浏览量
|
||
else if (
|
||
typeof page.value.viewCount === 'number' ||
|
||
typeof page.value.viewCount === 'string'
|
||
) {
|
||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||
} else {
|
||
page.value.viewCount = 1 // 初始浏览量
|
||
}
|
||
|
||
console.log('浏览量更新为:', page.value.viewCount)
|
||
} else {
|
||
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误')
|
||
}
|
||
} catch (e) {
|
||
console.error('更新浏览量失败:', e)
|
||
// 出错时也尝试本地增加浏览量
|
||
if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||
console.log('API出错,本地更新浏览量为:', page.value.viewCount)
|
||
}
|
||
}
|
||
|
||
// 检查返回的数据是否确实是表单类型
|
||
if (page.value.pageType && page.value.pageType !== 'form') {
|
||
console.error(`错误: 请求的是表单(form),但返回的是${page.value.pageType}类型的内容`)
|
||
// 不再尝试重新获取,而是直接显示错误
|
||
error.value = true
|
||
loading.value = false
|
||
return
|
||
}
|
||
|
||
// 处理可能的中文乱码问题
|
||
if (page.value.title && /\\u|%/.test(page.value.title)) {
|
||
try {
|
||
page.value.title = decodeURIComponent(page.value.title)
|
||
} catch (e) {
|
||
console.error('标题解码失败:', e)
|
||
}
|
||
}
|
||
|
||
// 检查内容是否为HTML格式
|
||
if (page.value.content) {
|
||
console.log('内容类型:', typeof page.value.content)
|
||
console.log('内容前50个字符:', page.value.content.substring(0, 50))
|
||
|
||
// 处理可能的中文乱码问题
|
||
if (/\\u|%/.test(page.value.content)) {
|
||
try {
|
||
page.value.content = decodeURIComponent(page.value.content)
|
||
} catch (e) {
|
||
console.error('内容解码失败:', e)
|
||
}
|
||
}
|
||
|
||
// 确保内容是HTML字符串
|
||
if (typeof page.value.content !== 'string') {
|
||
try {
|
||
page.value.content = JSON.stringify(page.value.content)
|
||
} catch (e) {
|
||
console.error('内容格式转换失败:', e)
|
||
}
|
||
}
|
||
} else {
|
||
console.warn('页面内容为空')
|
||
page.value.content = '<p>暂无内容</p>'
|
||
}
|
||
|
||
// 处理附件信息
|
||
if (page.value.attachmentUrl) {
|
||
console.log('附件URL:', page.value.attachmentUrl)
|
||
|
||
// 处理可能的附件URL乱码
|
||
if (/\\u|%/.test(page.value.attachmentUrl)) {
|
||
try {
|
||
page.value.attachmentUrl = decodeURIComponent(page.value.attachmentUrl)
|
||
} catch (e) {
|
||
console.error('附件URL解码失败:', e)
|
||
}
|
||
}
|
||
}
|
||
|
||
if (page.value.multiAttachments) {
|
||
try {
|
||
// 处理可能的多附件数据乱码
|
||
let attachmentsStr = page.value.multiAttachments
|
||
if (/\\u|%/.test(attachmentsStr)) {
|
||
try {
|
||
attachmentsStr = decodeURIComponent(attachmentsStr)
|
||
} catch (e) {
|
||
console.error('多附件数据解码失败:', e)
|
||
}
|
||
}
|
||
|
||
const attachments = JSON.parse(attachmentsStr)
|
||
console.log('多附件数据:', attachments)
|
||
|
||
// 处理附件数组中的每个附件URL
|
||
if (Array.isArray(attachments)) {
|
||
attachments.forEach((attachment, index) => {
|
||
if (attachment.url && /\\u|%/.test(attachment.url)) {
|
||
try {
|
||
attachment.url = decodeURIComponent(attachment.url)
|
||
} catch (e) {
|
||
console.error(`附件${index}URL解码失败:`, e)
|
||
}
|
||
}
|
||
})
|
||
|
||
// 更新页面的多附件数据
|
||
page.value.multiAttachments = JSON.stringify(attachments)
|
||
}
|
||
} catch (e) {
|
||
console.error('解析多附件数据失败:', e)
|
||
}
|
||
}
|
||
|
||
// 更新页面标题
|
||
document.title = `${page.value.title || '表单详情'} - 表单下载`
|
||
} else {
|
||
console.error('获取表单详情失败:', result?.msg || '未知错误')
|
||
error.value = true
|
||
}
|
||
} catch (e) {
|
||
console.error('获取表单详情异常:', e)
|
||
error.value = true
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
})
|
||
|
||
// 添加一个初始化折叠卡片功能的方法
|
||
const initCollapsibleCards = () => {
|
||
console.log('初始化折叠卡片...')
|
||
// 使用事件委托在父元素上监听点击事件
|
||
document.querySelector('.body')?.addEventListener('click', (e) => {
|
||
const target = e.target as HTMLElement
|
||
const header = target.closest('.law-card-header')
|
||
|
||
if (header) {
|
||
const index = parseInt(header.getAttribute('data-card-index') || '-1')
|
||
if (index >= 0) {
|
||
toggleCard(index)
|
||
console.log('切换卡片状态:', index)
|
||
}
|
||
}
|
||
})
|
||
|
||
console.log('折叠卡片初始化完成')
|
||
}
|
||
|
||
// 在内容更新后初始化折叠卡片
|
||
onMounted(() => {
|
||
// 内容加载完成后初始化折叠卡片
|
||
setTimeout(() => {
|
||
initCollapsibleCards()
|
||
}, 1000)
|
||
})
|
||
|
||
// 格式化日期
|
||
function formatDate(dateStr: string): string {
|
||
if (!dateStr) return '未知'
|
||
const date = new Date(dateStr)
|
||
return `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())}`
|
||
}
|
||
|
||
// 数字补零
|
||
function padZero(num: number): string {
|
||
return num < 10 ? `0${num}` : `${num}`
|
||
}
|
||
|
||
// 获取附件图标类
|
||
function getAttachmentIconClass(filename: string): string {
|
||
if (!filename) return 'icon-file'
|
||
|
||
const ext = filename.split('.').pop()?.toLowerCase() || ''
|
||
|
||
switch (ext) {
|
||
case 'pdf':
|
||
return 'icon-pdf'
|
||
case 'doc':
|
||
case 'docx':
|
||
return 'icon-word'
|
||
case 'xls':
|
||
case 'xlsx':
|
||
return 'icon-excel'
|
||
case 'ppt':
|
||
case 'pptx':
|
||
return 'icon-ppt'
|
||
case 'zip':
|
||
case 'rar':
|
||
return 'icon-archive'
|
||
case 'png':
|
||
case 'jpg':
|
||
case 'jpeg':
|
||
case 'gif':
|
||
return 'icon-image'
|
||
default:
|
||
return 'icon-file'
|
||
}
|
||
}
|
||
|
||
// 从URL中获取文件名
|
||
function getFileName(url: string): string {
|
||
if (!url) return '未知文件'
|
||
return url.split('/').pop() || '未知文件'
|
||
}
|
||
|
||
// 获取附件完整URL - 修复版本
|
||
function getAttachmentUrl(url: string): string {
|
||
if (!url) return '#'
|
||
|
||
// 如果已经是完整URL则直接返回
|
||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||
return url
|
||
}
|
||
|
||
// 处理可能的格式问题
|
||
let processedUrl = url
|
||
|
||
// 替换反斜杠为正斜杠
|
||
processedUrl = processedUrl.replace(/\\/g, '/')
|
||
|
||
// 确保不存在连续的双斜杠
|
||
while (processedUrl.includes('//')) {
|
||
processedUrl = processedUrl.replace('//', '/')
|
||
}
|
||
|
||
// 去除开头的斜杠,因为后续我们会添加
|
||
if (processedUrl.startsWith('/')) {
|
||
processedUrl = processedUrl.substring(1)
|
||
}
|
||
|
||
// 检查是否已经包含profile前缀
|
||
if (processedUrl.startsWith('profile/')) {
|
||
return `${import.meta.env.VITE_APP_BASE_API}/${processedUrl}`
|
||
}
|
||
|
||
// 检查是否为年月日格式的路径
|
||
const datePattern = /^\d{4}\/\d{2}\/\d{2}\//
|
||
if (datePattern.test(processedUrl)) {
|
||
// 如果不包含files/master前缀但符合日期格式,则添加
|
||
if (!processedUrl.startsWith('files/master/')) {
|
||
processedUrl = `files/master/${processedUrl}`
|
||
}
|
||
}
|
||
|
||
// 返回完整的URL
|
||
return `${import.meta.env.VITE_APP_BASE_API}/profile/${processedUrl}`
|
||
}
|
||
|
||
// 添加一个检测当前环境是否为微信浏览器的函数
|
||
const isWeixinBrowser = () => {
|
||
const ua = navigator.userAgent.toLowerCase()
|
||
return /micromessenger/i.test(ua)
|
||
}
|
||
|
||
// 添加一个直接文件下载处理函数,处理点击下载事件
|
||
function handleDownload(url: string, filename: string) {
|
||
const fullUrl = getAttachmentUrl(url)
|
||
|
||
// 检测是否在微信浏览器中
|
||
if (isWeixinBrowser()) {
|
||
// 显示微信浏览器中的提示模态框
|
||
showWeixinTips(fullUrl, filename)
|
||
return
|
||
}
|
||
|
||
// 显示下载中状态
|
||
const downloadStatus = ref('')
|
||
downloadStatus.value = '下载中...'
|
||
|
||
// 使用fetch API下载文件
|
||
fetch(fullUrl)
|
||
.then((response) => {
|
||
if (!response.ok) {
|
||
throw new Error(`下载失败: ${response.status}`)
|
||
}
|
||
return response.blob()
|
||
})
|
||
.then((blob) => {
|
||
// 创建临时下载链接
|
||
const downloadUrl = window.URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = downloadUrl
|
||
a.download = filename || getFileName(url)
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
window.URL.revokeObjectURL(downloadUrl)
|
||
document.body.removeChild(a)
|
||
downloadStatus.value = '下载成功'
|
||
})
|
||
.catch((error) => {
|
||
console.error('下载文件时出错:', error, fullUrl)
|
||
downloadStatus.value = '下载失败'
|
||
|
||
// 如果下载失败,尝试直接打开链接
|
||
window.open(fullUrl, '_blank')
|
||
})
|
||
}
|
||
|
||
// 微信浏览器中显示提示信息
|
||
const showWeixinModal = ref(false)
|
||
const currentDownloadUrl = ref('')
|
||
const currentFileName = ref('')
|
||
|
||
function showWeixinTips(url: string, filename: string) {
|
||
currentDownloadUrl.value = url
|
||
currentFileName.value = filename || getFileName(url)
|
||
showWeixinModal.value = true
|
||
}
|
||
|
||
// 复制下载链接到剪贴板
|
||
function copyDownloadLink() {
|
||
try {
|
||
navigator.clipboard.writeText(currentDownloadUrl.value)
|
||
alert('下载链接已复制到剪贴板,请在浏览器中粘贴访问')
|
||
} catch (err) {
|
||
// 如果不支持clipboard API,使用传统方法
|
||
const input = document.createElement('input')
|
||
input.value = currentDownloadUrl.value
|
||
document.body.appendChild(input)
|
||
input.select()
|
||
document.execCommand('copy')
|
||
document.body.removeChild(input)
|
||
alert('下载链接已复制到剪贴板,请在浏览器中粘贴访问')
|
||
}
|
||
}
|
||
|
||
// 在浏览器中打开
|
||
function openInBrowser() {
|
||
// 使用通用方案,在微信中会提示跳转到浏览器
|
||
window.location.href = currentDownloadUrl.value
|
||
|
||
// 关闭弹窗
|
||
setTimeout(() => {
|
||
showWeixinModal.value = false
|
||
}, 100)
|
||
}
|
||
|
||
const submitFeedback = async () => {
|
||
try {
|
||
// 前端验证
|
||
if (!feedbackForm.issueDescription.trim()) {
|
||
alert('问题描述不能为空。')
|
||
return
|
||
}
|
||
|
||
if (!feedbackForm.phoneNumber.trim()) {
|
||
alert('手机号不能为空。')
|
||
return
|
||
}
|
||
|
||
const dataToSend = {
|
||
issueDescription: feedbackForm.issueDescription,
|
||
phoneNumber: feedbackForm.phoneNumber
|
||
}
|
||
|
||
const res = await addFeedback(dataToSend)
|
||
if (res && res.code === 200) {
|
||
feedbackSubmitSuccess.value = true
|
||
feedbackNoReturned.value = res.data?.feedbackNo || '未知'
|
||
// 清空表单
|
||
feedbackForm.issueDescription = ''
|
||
feedbackForm.phoneNumber = ''
|
||
console.log('留言提交成功,查询编号:', feedbackNoReturned.value)
|
||
} else {
|
||
alert('提交留言失败:' + (res.msg || '未知错误'))
|
||
}
|
||
} catch (e) {
|
||
console.error('提交留言异常:', e)
|
||
alert('提交留言异常,请稍后再试')
|
||
}
|
||
}
|
||
|
||
const queryFeedback = async () => {
|
||
// 实现查询留言回复的逻辑
|
||
}
|
||
|
||
// 删除有类型错误的后台管理相关的函数,因为它们在前台页面不会被使用
|
||
</script>
|
||
|
||
<template>
|
||
<div class="form-container">
|
||
<TheNavbar />
|
||
|
||
<div v-if="loading" class="loading">
|
||
<div class="spinner"></div>
|
||
<p>加载中...</p>
|
||
</div>
|
||
|
||
<div v-else-if="error" class="error">
|
||
<div class="container">
|
||
<h3>内容加载失败</h3>
|
||
<p>无法找到该表单内容或发生网络错误</p>
|
||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="content">
|
||
<div class="container">
|
||
<div class="content-card">
|
||
<div class="header">
|
||
<div class="form-category">表单下载</div>
|
||
<h2>{{ page.title }}</h2>
|
||
<div class="meta">
|
||
<span><i class="icon-time"></i>{{ formatDate(page.createTime) }}</span>
|
||
<span><i class="icon-eye"></i>浏览次数: {{ page.viewCount }}</span>
|
||
<span v-if="page.author"><i class="icon-user"></i>作者: {{ page.author }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="body" v-html="processedContent"></div>
|
||
|
||
<!-- 附件列表 -->
|
||
<div class="attachments" v-if="attachmentList.length > 0">
|
||
<h3 class="attachments-title">附件下载</h3>
|
||
<ul class="attachment-list">
|
||
<li v-for="(item, index) in attachmentList" :key="index" class="attachment-item">
|
||
<span
|
||
class="attachment-icon"
|
||
:class="getAttachmentIconClass(item.name || item.url)"
|
||
></span>
|
||
<span class="attachment-name">{{ item.name || getFileName(item.url) }}</span>
|
||
<a
|
||
@click.prevent="handleDownload(item.url, item.name || getFileName(item.url))"
|
||
class="download-btn"
|
||
href="javascript:void(0)"
|
||
>下载</a
|
||
>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<router-link to="/hasfjform" class="btn-more">查看更多表单</router-link>
|
||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||
</div>
|
||
<!-- 用户留言模块 -->
|
||
<div class="feedback-section">
|
||
<h3><i class="icon-chat"></i> 表单咨询与留言</h3>
|
||
<div v-if="!feedbackSubmitSuccess" class="feedback-form">
|
||
<p class="section-description">
|
||
如果您有以下问题可以给我们留言,我们会在十个工作日内反馈,
|
||
</p>
|
||
<p class="section-description">1.企业经营中遇到的法律问题;</p>
|
||
<p class="section-description">2.企业遇到行政执法不规范问题;</p>
|
||
<p class="section-description">3.对我们工作提出的意见和建议。</p>
|
||
|
||
<div class="form-group">
|
||
<label for="issueDescription">问题描述: <span class="required">*</span></label>
|
||
<textarea
|
||
id="issueDescription"
|
||
v-model="feedbackForm.issueDescription"
|
||
rows="5"
|
||
placeholder="请详细描述您的问题,字数在10-500字之间"
|
||
required
|
||
></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="phoneNumber">手机号: <span class="required">*</span></label>
|
||
<input
|
||
type="text"
|
||
id="phoneNumber"
|
||
v-model="feedbackForm.phoneNumber"
|
||
placeholder="请输入手机号"
|
||
required
|
||
/>
|
||
</div>
|
||
<button @click="submitFeedback" class="submit-btn">提交留言</button>
|
||
</div>
|
||
<div v-else class="feedback-success">
|
||
<h4>留言提交成功!</h4>
|
||
<p>
|
||
您的留言查询编号是:<span class="feedback-no">{{ feedbackNoReturned }}</span>
|
||
</p>
|
||
<p>请牢记此编号,以便查询回复。</p>
|
||
<button
|
||
@click="((feedbackSubmitSuccess = false), (feedbackNoReturned = ''))"
|
||
class="submit-btn"
|
||
>
|
||
继续留言
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 微信浏览器下载提示弹窗 -->
|
||
<div v-if="showWeixinModal" class="weixin-modal-overlay">
|
||
<div class="weixin-modal">
|
||
<div class="weixin-modal-header">
|
||
<h3>提示</h3>
|
||
<span class="close-btn" @click="showWeixinModal = false">×</span>
|
||
</div>
|
||
<div class="weixin-modal-content">
|
||
<p>由于微信限制,无法直接下载文件。</p>
|
||
<p>请选择以下方式下载:</p>
|
||
<div class="filename-display">
|
||
<strong>文件名:</strong> {{ currentFileName }}
|
||
</div>
|
||
<div class="weixin-modal-actions">
|
||
<button @click="copyDownloadLink" class="copy-link-btn">复制下载链接</button>
|
||
<button @click="openInBrowser" class="open-browser-btn">在浏览器中打开</button>
|
||
</div>
|
||
<div class="weixin-modal-steps">
|
||
<p><strong>或按照以下步骤操作:</strong></p>
|
||
<ol>
|
||
<li>点击右上角 <span class="icon">⋮</span> 或 <span class="icon">⋯</span> 按钮</li>
|
||
<li>选择"在浏览器中打开"</li>
|
||
<li>在浏览器中重新下载文件</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.form-container {
|
||
min-height: 100vh;
|
||
background-color: var(--color-background);
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 50vh;
|
||
font-size: 1rem;
|
||
color: var(--color-text-light);
|
||
}
|
||
|
||
.spinner {
|
||
width: 50px;
|
||
height: 50px;
|
||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||
border-radius: 50%;
|
||
border-top-color: var(--color-primary);
|
||
animation: spin 1s ease-in-out infinite;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.error {
|
||
text-align: center;
|
||
padding: 4rem 0;
|
||
}
|
||
|
||
.error h3 {
|
||
margin-bottom: 1rem;
|
||
color: var(--color-danger);
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.error p {
|
||
margin-bottom: 2rem;
|
||
color: var(--color-text-light);
|
||
}
|
||
|
||
.content {
|
||
padding: 0;
|
||
flex: 1;
|
||
background-color: var(--color-background);
|
||
width: 100%;
|
||
}
|
||
|
||
.content-card {
|
||
background-color: white;
|
||
border-radius: 0;
|
||
box-shadow: none;
|
||
padding: 2.5rem;
|
||
overflow: hidden;
|
||
max-width: 100%;
|
||
margin: 0;
|
||
}
|
||
|
||
.content .header {
|
||
text-align: center;
|
||
margin-bottom: 2.5rem;
|
||
padding-bottom: 1.5rem;
|
||
border-bottom: 1px solid var(--color-border-light);
|
||
position: relative;
|
||
}
|
||
|
||
.form-category {
|
||
display: inline-block;
|
||
background-color: var(--color-primary);
|
||
color: white;
|
||
padding: 0.3rem 1rem;
|
||
border-radius: 20px;
|
||
font-size: 0.85rem;
|
||
margin-bottom: 0.8rem;
|
||
}
|
||
|
||
.content .header h2 {
|
||
margin-bottom: 1.5rem;
|
||
color: var(--color-text);
|
||
font-size: 1.8rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.content .meta {
|
||
color: var(--color-text-light);
|
||
font-size: 0.9rem;
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.content .meta span {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.icon-time::before {
|
||
content: '🕒';
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.icon-eye::before {
|
||
content: '👁️';
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.icon-user::before {
|
||
content: '👤';
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.content .body {
|
||
line-height: 1.8;
|
||
color: var(--color-text);
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.content .body img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
margin: 1rem auto;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.content .body p {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.content .body h1,
|
||
.content .body h2,
|
||
.content .body h3,
|
||
.content .body h4,
|
||
.content .body h5,
|
||
.content .body h6 {
|
||
margin: 1.5rem 0 1rem;
|
||
color: var(--color-text);
|
||
}
|
||
|
||
.content .body table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.content .body table th,
|
||
.content .body table td {
|
||
border: 1px solid #eee;
|
||
padding: 0.5rem;
|
||
text-align: left;
|
||
}
|
||
|
||
.content .body table th {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
/* 添加法律卡片相关样式 */
|
||
:deep(.law-card) {
|
||
margin: 1.5rem 0;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
:deep(.law-card-header) {
|
||
padding: 1rem 1.5rem;
|
||
background-color: #f0f0f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
user-select: none; /* 防止选中文本 */
|
||
}
|
||
|
||
:deep(.law-card-header:hover) {
|
||
background-color: #e0e0e0;
|
||
}
|
||
|
||
:deep(.law-card-title) {
|
||
font-weight: bold;
|
||
color: #333;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
:deep(.law-card-toggle) {
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
:deep(.law-card-content) {
|
||
padding: 0;
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
transition:
|
||
max-height 0.5s ease,
|
||
padding 0.5s ease;
|
||
}
|
||
|
||
:deep(.law-card.expanded .law-card-content) {
|
||
padding: 1.5rem;
|
||
max-height: 2000px;
|
||
}
|
||
|
||
:deep(.law-card.expanded .law-card-toggle) {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.attachments {
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
margin-top: 2rem;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.attachments-title {
|
||
margin-bottom: 1.2rem;
|
||
color: var(--color-text);
|
||
font-size: 1.2rem;
|
||
font-weight: 600;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||
padding-bottom: 0.8rem;
|
||
}
|
||
|
||
.attachment-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.attachment-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0.8rem 0;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.attachment-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.attachment-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
background-size: contain;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.attachment-name {
|
||
flex: 1;
|
||
color: var(--color-text);
|
||
font-size: 0.95rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.download-btn {
|
||
background-color: var(--color-primary);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.85rem;
|
||
text-decoration: none;
|
||
transition:
|
||
background-color 0.3s,
|
||
transform 0.3s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.download-btn:hover {
|
||
background-color: var(--color-primary-dark);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.content .footer {
|
||
margin-top: 3rem;
|
||
padding-top: 1.5rem;
|
||
border-top: 1px dashed #eee;
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-home,
|
||
.btn-more {
|
||
display: inline-block;
|
||
padding: 0.6rem 1.5rem;
|
||
background-color: var(--color-primary);
|
||
color: white;
|
||
text-decoration: none;
|
||
border-radius: 4px;
|
||
transition:
|
||
background-color 0.3s,
|
||
transform 0.3s;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.btn-home:hover,
|
||
.btn-more:hover {
|
||
background-color: #0069d9;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.btn-more {
|
||
background-color: var(--color-success);
|
||
}
|
||
|
||
.btn-more:hover {
|
||
background-color: #218838;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.content {
|
||
padding: 0;
|
||
}
|
||
|
||
.content-card {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.content .header h2 {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.content .body {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.attachments {
|
||
padding: 1.2rem;
|
||
}
|
||
|
||
:deep(.law-card-title) {
|
||
font-size: 1rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 576px) {
|
||
.content {
|
||
padding: 0;
|
||
}
|
||
|
||
.content-card {
|
||
padding: 1rem;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.content .header {
|
||
margin-bottom: 1.5rem;
|
||
padding-bottom: 1rem;
|
||
}
|
||
|
||
.content .header h2 {
|
||
font-size: 1.3rem;
|
||
margin-bottom: 0.8rem;
|
||
}
|
||
|
||
.content .meta {
|
||
font-size: 0.8rem;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.content .body {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.attachments {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.attachment-item {
|
||
padding: 0.6rem 0;
|
||
}
|
||
|
||
.content .footer {
|
||
margin-top: 2rem;
|
||
padding-top: 1rem;
|
||
}
|
||
|
||
.btn-home,
|
||
.btn-more {
|
||
padding: 0.5rem 1.2rem;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
:deep(.law-card-header) {
|
||
padding: 0.8rem 1rem;
|
||
}
|
||
|
||
:deep(.law-card-title) {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
:deep(.law-card.expanded .law-card-content) {
|
||
padding: 1rem;
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1200px) {
|
||
.content-card {
|
||
border-radius: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.content .body {
|
||
padding: 0;
|
||
}
|
||
|
||
.content {
|
||
padding: 0;
|
||
}
|
||
}
|
||
|
||
.feedback-section {
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
margin-top: 2rem;
|
||
padding: 2rem;
|
||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.feedback-section h3 {
|
||
font-size: 1.5rem;
|
||
color: var(--color-primary);
|
||
margin-bottom: 1.5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.feedback-section .icon-chat::before {
|
||
content: '💬'; /* Chat bubble icon */
|
||
margin-right: 10px;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.feedback-section .icon-search::before {
|
||
content: '🔍'; /* Search icon */
|
||
margin-right: 10px;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.feedback-section .section-description {
|
||
color: var(--color-text-light);
|
||
margin-bottom: 1.5rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.feedback-form .form-group,
|
||
.feedback-query .form-group {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.feedback-form label,
|
||
.feedback-query label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 500;
|
||
color: var(--color-text);
|
||
}
|
||
|
||
.feedback-form input[type='text'],
|
||
.feedback-form textarea,
|
||
.feedback-form select,
|
||
.feedback-query input[type='text'],
|
||
.feedback-query select {
|
||
width: calc(100% - 20px);
|
||
padding: 0.8rem 10px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
font-size: 1rem;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.feedback-form textarea {
|
||
resize: vertical;
|
||
}
|
||
|
||
.feedback-form .required,
|
||
.feedback-query .required {
|
||
color: var(--color-danger);
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.feedback-form .submit-btn,
|
||
.feedback-query .submit-btn {
|
||
display: inline-block;
|
||
padding: 0.8rem 2rem;
|
||
background-color: var(--color-primary);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 1rem;
|
||
transition: background-color 0.3s ease;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.feedback-form .submit-btn:hover,
|
||
.feedback-query .submit-btn:hover {
|
||
background-color: #0069d9;
|
||
}
|
||
|
||
.feedback-query .submit-btn:disabled {
|
||
background-color: #cccccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.feedback-success {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
background-color: #e6ffe6;
|
||
border: 1px solid #aaffaa;
|
||
border-radius: 8px;
|
||
color: #336633;
|
||
}
|
||
|
||
.feedback-success h4 {
|
||
color: #28a745;
|
||
margin-bottom: 1rem;
|
||
font-size: 1.3rem;
|
||
}
|
||
|
||
.feedback-success .feedback-no {
|
||
font-weight: bold;
|
||
color: var(--color-primary);
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.feedback-success button {
|
||
margin-top: 1.5rem;
|
||
padding: 0.7rem 1.5rem;
|
||
background-color: var(--color-success);
|
||
border: none;
|
||
border-radius: 4px;
|
||
color: white;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.feedback-success button:hover {
|
||
background-color: #218838;
|
||
}
|
||
|
||
.query-title {
|
||
margin-top: 3rem;
|
||
border-top: 1px dashed #eee;
|
||
padding-top: 2rem;
|
||
}
|
||
|
||
.query-result {
|
||
background-color: #e9f7fe;
|
||
border: 1px solid #b3e0ff;
|
||
border-radius: 8px;
|
||
padding: 1.5rem;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.query-result h4 {
|
||
color: #007bff;
|
||
margin-bottom: 1rem;
|
||
font-size: 1.3rem;
|
||
padding-bottom: 0.8rem;
|
||
border-bottom: 1px dashed #cceeff;
|
||
}
|
||
|
||
.query-result .result-item {
|
||
margin-bottom: 0.8rem;
|
||
color: var(--color-text);
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.query-result .result-item strong {
|
||
color: #333;
|
||
}
|
||
|
||
.query-result .admin-reply-content {
|
||
background-color: #ffffff;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
padding: 1rem;
|
||
margin-top: 0.5rem;
|
||
line-height: 1.6;
|
||
color: #555;
|
||
}
|
||
|
||
.error-message {
|
||
color: var(--color-danger);
|
||
margin-top: 1rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.status-pending {
|
||
color: orange;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status-replied {
|
||
color: green;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status-closed {
|
||
color: gray;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.feedback-section {
|
||
padding: 1.5rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 576px) {
|
||
.feedback-section {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.feedback-section h3 {
|
||
font-size: 1.3rem;
|
||
}
|
||
|
||
.feedback-section .icon-chat::before,
|
||
.feedback-section .icon-search::before {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.feedback-form input[type='text'],
|
||
.feedback-form textarea,
|
||
.feedback-form select,
|
||
.feedback-query input[type='text'],
|
||
.feedback-query select {
|
||
padding: 0.6rem 8px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.feedback-form .submit-btn,
|
||
.feedback-query .submit-btn {
|
||
padding: 0.6rem 1.5rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.feedback-success h4 {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.feedback-success .feedback-no {
|
||
font-size: 1.3rem;
|
||
}
|
||
|
||
.query-result h4 {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.query-result .result-item {
|
||
font-size: 0.85rem;
|
||
}
|
||
}
|
||
|
||
/* 微信下载弹窗样式 */
|
||
.weixin-modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.weixin-modal {
|
||
background-color: white;
|
||
border-radius: 8px;
|
||
width: 90%;
|
||
max-width: 400px;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.weixin-modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px 20px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.weixin-modal-header h3 {
|
||
margin: 0;
|
||
color: var(--color-primary);
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.close-btn {
|
||
font-size: 1.5rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
color: #888;
|
||
}
|
||
|
||
.close-btn:hover {
|
||
color: #333;
|
||
}
|
||
|
||
.weixin-modal-content {
|
||
padding: 20px;
|
||
}
|
||
|
||
.filename-display {
|
||
background-color: #f5f5f5;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
margin: 15px 0;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.weixin-modal-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
margin: 15px 0;
|
||
}
|
||
|
||
.copy-link-btn, .open-browser-btn {
|
||
padding: 12px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.copy-link-btn {
|
||
background-color: var(--color-primary);
|
||
color: white;
|
||
}
|
||
|
||
.open-browser-btn {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
}
|
||
|
||
.copy-link-btn:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.open-browser-btn:hover {
|
||
background-color: #3d8b40;
|
||
}
|
||
|
||
.weixin-modal-steps {
|
||
margin-top: 20px;
|
||
border-top: 1px dashed #ddd;
|
||
padding-top: 15px;
|
||
}
|
||
|
||
.weixin-modal-steps ol {
|
||
padding-left: 20px;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.weixin-modal-steps li {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.icon {
|
||
background-color: #f5f5f5;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 在小屏幕上调整弹窗样式 */
|
||
@media (max-width: 576px) {
|
||
.weixin-modal {
|
||
width: 95%;
|
||
}
|
||
|
||
.weixin-modal-header {
|
||
padding: 12px 15px;
|
||
}
|
||
|
||
.weixin-modal-content {
|
||
padding: 15px;
|
||
}
|
||
|
||
.weixin-modal-actions {
|
||
gap: 8px;
|
||
}
|
||
|
||
.copy-link-btn, .open-browser-btn {
|
||
padding: 10px;
|
||
}
|
||
}
|
||
</style>
|