2025-06-02 21:36:36 +08:00
|
|
|
|
<script setup lang="ts">
|
2025-06-02 21:36:36 +08:00
|
|
|
|
import { ref, onMounted, computed, reactive } from 'vue'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
import { useRoute } from 'vue-router'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
import {
|
|
|
|
|
getPageDetail,
|
|
|
|
|
updateViewCount,
|
|
|
|
|
addFeedback,
|
|
|
|
|
listFeedback,
|
|
|
|
|
queryFeedbackDetail,
|
|
|
|
|
} from '../services/api'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
import TheNavbar from '../components/TheNavbar.vue'
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
const error = ref(false)
|
|
|
|
|
const page = ref<any>(null)
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const feedbackForm = reactive({
|
|
|
|
|
legalArticle: '',
|
|
|
|
|
issueDescription: '',
|
|
|
|
|
userName: '',
|
|
|
|
|
phoneNumber: '',
|
|
|
|
|
idCardNumber: '',
|
|
|
|
|
contactInfoType: 1, // 默认手机号
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 计算附件列表
|
|
|
|
|
const attachmentList = computed(() => {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
if (!page.value || !page.value.multiAttachments) return []
|
2025-06-02 21:36:36 +08:00
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const parsed = JSON.parse(page.value.multiAttachments)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 过滤无效的附件项
|
2025-06-02 21:36:36 +08:00
|
|
|
|
return Array.isArray(parsed) ? parsed.filter((item) => item && (item.url || item.name)) : []
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('解析附件列表失败:', e)
|
|
|
|
|
return []
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
})
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const formatId = route.query.Id as string
|
|
|
|
|
if (!formatId) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('缺少必要的formatId参数')
|
|
|
|
|
error.value = true
|
|
|
|
|
loading.value = false
|
|
|
|
|
return
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.log('开始获取表单详情,formatId:', formatId)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 获取表单数据
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const result = await getPageDetail('form', formatId)
|
|
|
|
|
console.log('表单详情请求结果:', result)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
if (result && result.code === 200 && result.data) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value = result.data
|
|
|
|
|
console.log('成功获取表单详情:', page.value)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 更新浏览量
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const viewResult = (await updateViewCount('form', formatId)) as any
|
|
|
|
|
console.log('更新浏览量结果:', viewResult)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 确认服务器端更新成功
|
|
|
|
|
if (viewResult && viewResult.code === 200) {
|
|
|
|
|
// 如果后端返回了新的浏览量数据
|
|
|
|
|
if (viewResult.data && typeof viewResult.data.viewCount === 'number') {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.viewCount = viewResult.data.viewCount
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
// 否则本地递增浏览量
|
2025-06-02 21:36:36 +08:00
|
|
|
|
else if (
|
|
|
|
|
typeof page.value.viewCount === 'number' ||
|
|
|
|
|
typeof page.value.viewCount === 'string'
|
|
|
|
|
) {
|
|
|
|
|
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} else {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.viewCount = 1 // 初始浏览量
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.log('浏览量更新为:', page.value.viewCount)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} else {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误')
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('更新浏览量失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 出错时也尝试本地增加浏览量
|
|
|
|
|
if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
|
|
|
|
console.log('API出错,本地更新浏览量为:', page.value.viewCount)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查返回的数据是否确实是表单类型
|
|
|
|
|
if (page.value.pageType && page.value.pageType !== 'form') {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error(`错误: 请求的是表单(form),但返回的是${page.value.pageType}类型的内容`)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 不再尝试重新获取,而是直接显示错误
|
2025-06-02 21:36:36 +08:00
|
|
|
|
error.value = true
|
|
|
|
|
loading.value = false
|
|
|
|
|
return
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理可能的中文乱码问题
|
|
|
|
|
if (page.value.title && /\\u|%/.test(page.value.title)) {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.title = decodeURIComponent(page.value.title)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('标题解码失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查内容是否为HTML格式
|
|
|
|
|
if (page.value.content) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.log('内容类型:', typeof page.value.content)
|
|
|
|
|
console.log('内容前50个字符:', page.value.content.substring(0, 50))
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 处理可能的中文乱码问题
|
|
|
|
|
if (/\\u|%/.test(page.value.content)) {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.content = decodeURIComponent(page.value.content)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('内容解码失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保内容是HTML字符串
|
|
|
|
|
if (typeof page.value.content !== 'string') {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.content = JSON.stringify(page.value.content)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('内容格式转换失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.warn('页面内容为空')
|
|
|
|
|
page.value.content = '<p>暂无内容</p>'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理附件信息
|
|
|
|
|
if (page.value.attachmentUrl) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.log('附件URL:', page.value.attachmentUrl)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 处理可能的附件URL乱码
|
|
|
|
|
if (/\\u|%/.test(page.value.attachmentUrl)) {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.attachmentUrl = decodeURIComponent(page.value.attachmentUrl)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('附件URL解码失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (page.value.multiAttachments) {
|
|
|
|
|
try {
|
|
|
|
|
// 处理可能的多附件数据乱码
|
2025-06-02 21:36:36 +08:00
|
|
|
|
let attachmentsStr = page.value.multiAttachments
|
2025-06-02 21:36:36 +08:00
|
|
|
|
if (/\\u|%/.test(attachmentsStr)) {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
attachmentsStr = decodeURIComponent(attachmentsStr)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('多附件数据解码失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const attachments = JSON.parse(attachmentsStr)
|
|
|
|
|
console.log('多附件数据:', attachments)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 处理附件数组中的每个附件URL
|
|
|
|
|
if (Array.isArray(attachments)) {
|
|
|
|
|
attachments.forEach((attachment, index) => {
|
|
|
|
|
if (attachment.url && /\\u|%/.test(attachment.url)) {
|
|
|
|
|
try {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
attachment.url = decodeURIComponent(attachment.url)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error(`附件${index}URL解码失败:`, e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
})
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 更新页面的多附件数据
|
2025-06-02 21:36:36 +08:00
|
|
|
|
page.value.multiAttachments = JSON.stringify(attachments)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('解析多附件数据失败:', e)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新页面标题
|
2025-06-02 21:36:36 +08:00
|
|
|
|
document.title = `${page.value.title || '表单详情'} - 表单下载`
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} else {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('获取表单详情失败:', result?.msg || '未知错误')
|
|
|
|
|
error.value = true
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
console.error('获取表单详情异常:', e)
|
|
|
|
|
error.value = true
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} finally {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
loading.value = false
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 添加一个初始化折叠卡片功能的方法
|
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 格式化日期
|
|
|
|
|
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) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'pdf':
|
|
|
|
|
return 'icon-pdf'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'doc':
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'docx':
|
|
|
|
|
return 'icon-word'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'xls':
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'xlsx':
|
|
|
|
|
return 'icon-excel'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'ppt':
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'pptx':
|
|
|
|
|
return 'icon-ppt'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'zip':
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'rar':
|
|
|
|
|
return 'icon-archive'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'png':
|
|
|
|
|
case 'jpg':
|
|
|
|
|
case 'jpeg':
|
2025-06-02 21:36:36 +08:00
|
|
|
|
case 'gif':
|
|
|
|
|
return 'icon-image'
|
|
|
|
|
default:
|
|
|
|
|
return 'icon-file'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理可能的格式问题
|
2025-06-02 21:36:36 +08:00
|
|
|
|
let processedUrl = url
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 替换反斜杠为正斜杠
|
2025-06-02 21:36:36 +08:00
|
|
|
|
processedUrl = processedUrl.replace(/\\/g, '/')
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 确保不存在连续的双斜杠
|
|
|
|
|
while (processedUrl.includes('//')) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
processedUrl = processedUrl.replace('//', '/')
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 去除开头的斜杠,因为后续我们会添加
|
|
|
|
|
if (processedUrl.startsWith('/')) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
processedUrl = processedUrl.substring(1)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否已经包含profile前缀
|
|
|
|
|
if (processedUrl.startsWith('profile/')) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
return `${import.meta.env.VITE_APP_BASE_API}/${processedUrl}`
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否为年月日格式的路径
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const datePattern = /^\d{4}\/\d{2}\/\d{2}\//
|
2025-06-02 21:36:36 +08:00
|
|
|
|
if (datePattern.test(processedUrl)) {
|
|
|
|
|
// 如果不包含files/master前缀但符合日期格式,则添加
|
|
|
|
|
if (!processedUrl.startsWith('files/master/')) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
processedUrl = `files/master/${processedUrl}`
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回完整的URL
|
2025-06-02 21:36:36 +08:00
|
|
|
|
return `${import.meta.env.VITE_APP_BASE_API}/profile/${processedUrl}`
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加一个直接文件下载处理函数,处理点击下载事件
|
|
|
|
|
function handleDownload(url: string, filename: string) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const fullUrl = getAttachmentUrl(url)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 显示下载中状态
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const downloadStatus = ref('')
|
|
|
|
|
downloadStatus.value = '下载中...'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 使用fetch API下载文件
|
|
|
|
|
fetch(fullUrl)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.then((response) => {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
if (!response.ok) {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
throw new Error(`下载失败: ${response.status}`)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
return response.blob()
|
2025-06-02 21:36:36 +08:00
|
|
|
|
})
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.then((blob) => {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
// 创建临时下载链接
|
2025-06-02 21:36:36 +08:00
|
|
|
|
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 = '下载成功'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
})
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.catch((error) => {
|
|
|
|
|
console.error('下载文件时出错:', error, fullUrl)
|
|
|
|
|
downloadStatus.value = '下载失败'
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 如果下载失败,尝试直接打开链接
|
2025-06-02 21:36:36 +08:00
|
|
|
|
window.open(fullUrl, '_blank')
|
|
|
|
|
})
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const submitFeedback = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 前端验证
|
|
|
|
|
if (!feedbackForm.legalArticle.trim()) {
|
|
|
|
|
alert('有问题的表单或案例不能为空。')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (!feedbackForm.issueDescription.trim()) {
|
|
|
|
|
alert('问题描述不能为空。')
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
if (feedbackForm.contactInfoType === 1 && !feedbackForm.phoneNumber.trim()) {
|
|
|
|
|
alert('手机号不能为空。')
|
|
|
|
|
return
|
|
|
|
|
} else if (feedbackForm.contactInfoType === 2 && !feedbackForm.idCardNumber.trim()) {
|
|
|
|
|
alert('身份证号不能为空。')
|
|
|
|
|
return
|
|
|
|
|
} else if (feedbackForm.contactInfoType === 3 && !feedbackForm.userName.trim()) {
|
|
|
|
|
alert('姓名不能为空。')
|
|
|
|
|
return
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
const dataToSend = { ...feedbackForm }
|
|
|
|
|
// 根据选择的联系方式类型设置对应的值,其他值置空
|
|
|
|
|
if (feedbackForm.contactInfoType === 1) {
|
|
|
|
|
dataToSend.idCardNumber = ''
|
|
|
|
|
dataToSend.userName = ''
|
|
|
|
|
} else if (feedbackForm.contactInfoType === 2) {
|
|
|
|
|
dataToSend.phoneNumber = ''
|
|
|
|
|
dataToSend.userName = ''
|
|
|
|
|
} else if (feedbackForm.contactInfoType === 3) {
|
|
|
|
|
dataToSend.phoneNumber = ''
|
|
|
|
|
dataToSend.idCardNumber = ''
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
const res = await addFeedback(dataToSend)
|
|
|
|
|
if (res && res.code === 200) {
|
|
|
|
|
feedbackSubmitSuccess.value = true
|
|
|
|
|
feedbackNoReturned.value = res.data?.feedbackNo || '未知'
|
|
|
|
|
// 清空表单
|
|
|
|
|
feedbackForm.legalArticle = ''
|
|
|
|
|
feedbackForm.issueDescription = ''
|
|
|
|
|
feedbackForm.userName = ''
|
|
|
|
|
feedbackForm.phoneNumber = ''
|
|
|
|
|
feedbackForm.idCardNumber = ''
|
|
|
|
|
feedbackForm.contactInfoType = 1
|
|
|
|
|
console.log('留言提交成功,查询编号:', feedbackNoReturned.value)
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} else {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
alert('提交留言失败:' + (res.msg || '未知错误'))
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('提交留言异常:', e)
|
|
|
|
|
alert('提交留言异常,请稍后再试')
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
const queryFeedback = async () => {
|
|
|
|
|
// 实现查询留言回复的逻辑
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
// 删除有类型错误的后台管理相关的函数,因为它们在前台页面不会被使用
|
2025-06-02 21:36:36 +08:00
|
|
|
|
</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>
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
<div class="body" v-html="processedContent"></div>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
<!-- 附件列表 -->
|
|
|
|
|
<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">
|
2025-06-02 21:36:36 +08:00
|
|
|
|
<span
|
|
|
|
|
class="attachment-icon"
|
|
|
|
|
:class="getAttachmentIconClass(item.name || item.url)"
|
|
|
|
|
></span>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
<span class="attachment-name">{{ item.name || getFileName(item.url) }}</span>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
<a
|
|
|
|
|
@click.prevent="handleDownload(item.url, item.name || getFileName(item.url))"
|
|
|
|
|
class="download-btn"
|
|
|
|
|
href="javascript:void(0)"
|
|
|
|
|
>下载</a
|
|
|
|
|
>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="footer">
|
|
|
|
|
<router-link to="/hasfjform" class="btn-more">查看更多表单</router-link>
|
|
|
|
|
<router-link to="/" class="btn-home">返回首页</router-link>
|
|
|
|
|
</div>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
<!-- 用户留言模块 -->
|
|
|
|
|
<div class="feedback-section">
|
|
|
|
|
<h3><i class="icon-chat"></i> 表单咨询与留言</h3>
|
|
|
|
|
<div v-if="!feedbackSubmitSuccess" class="feedback-form">
|
|
|
|
|
<p class="section-description">如果您对表单下载有任何疑问或建议,欢迎在此留言。</p>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="legalArticle">有问题的表单或案例:<span class="required">*</span></label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="legalArticle"
|
|
|
|
|
v-model="feedbackForm.legalArticle"
|
|
|
|
|
placeholder="如:公司设立登记申请表"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<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="contactInfoType">联系方式类型:</label>
|
|
|
|
|
<select id="contactInfoType" v-model="feedbackForm.contactInfoType">
|
|
|
|
|
<option :value="1">手机号</option>
|
|
|
|
|
<option :value="2">身份证号</option>
|
|
|
|
|
<option :value="3">姓名</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="phoneNumber"
|
|
|
|
|
>手机号:
|
|
|
|
|
<span class="required" v-show="feedbackForm.contactInfoType === 1">*</span></label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="phoneNumber"
|
|
|
|
|
v-model="feedbackForm.phoneNumber"
|
|
|
|
|
placeholder="请输入手机号"
|
|
|
|
|
:required="feedbackForm.contactInfoType === 1"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="idCardNumber"
|
|
|
|
|
>身份证号:
|
|
|
|
|
<span class="required" v-show="feedbackForm.contactInfoType === 2">*</span></label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="idCardNumber"
|
|
|
|
|
v-model="feedbackForm.idCardNumber"
|
|
|
|
|
placeholder="请输入身份证号"
|
|
|
|
|
:required="feedbackForm.contactInfoType === 2"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="userNameContact"
|
|
|
|
|
>姓名:
|
|
|
|
|
<span class="required" v-show="feedbackForm.contactInfoType === 3">*</span></label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="userNameContact"
|
|
|
|
|
v-model="feedbackForm.userName"
|
|
|
|
|
placeholder="请输入您的姓名"
|
|
|
|
|
:required="feedbackForm.contactInfoType === 3"
|
|
|
|
|
/>
|
|
|
|
|
</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>
|
2025-06-02 21:36:36 +08:00
|
|
|
|
</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 {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
to {
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
/* 添加法律卡片相关样式 */
|
|
|
|
|
: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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.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;
|
2025-06-02 21:36:36 +08:00
|
|
|
|
transition:
|
|
|
|
|
background-color 0.3s,
|
|
|
|
|
transform 0.3s;
|
2025-06-02 21:36:36 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.btn-home,
|
|
|
|
|
.btn-more {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
display: inline-block;
|
|
|
|
|
padding: 0.6rem 1.5rem;
|
|
|
|
|
background-color: var(--color-primary);
|
|
|
|
|
color: white;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
border-radius: 4px;
|
2025-06-02 21:36:36 +08:00
|
|
|
|
transition:
|
|
|
|
|
background-color 0.3s,
|
|
|
|
|
transform 0.3s;
|
2025-06-02 21:36:36 +08:00
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.btn-home:hover,
|
|
|
|
|
.btn-more:hover {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
:deep(.law-card-title) {
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-02 21:36:36 +08:00
|
|
|
|
.btn-home,
|
|
|
|
|
.btn-more {
|
2025-06-02 21:36:36 +08:00
|
|
|
|
padding: 0.5rem 1.2rem;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
: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;
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (min-width: 1200px) {
|
|
|
|
|
.content-card {
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content .body {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-02 21:36:36 +08:00
|
|
|
|
</style>
|