mirror of
https://gitee.com/myxzgzs/boyuehasfj-vue3-html.git
synced 2025-08-07 22:52:42 +08:00
boyuehasfj-vue3-html
This commit is contained in:
commit
320265cc07
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# boyue-vue3-html
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
15
api.conf
Normal file
15
api.conf
Normal file
@ -0,0 +1,15 @@
|
||||
location ^~ /api {
|
||||
proxy_pass http://222.184.49.22:9799;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
proxy_ssl_name $proxy_host;
|
||||
}
|
1
boyuehasfj-html-bak/assets/CaseListView-2AeSN89n.js
Normal file
1
boyuehasfj-html-bak/assets/CaseListView-2AeSN89n.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as y,r,w as k,o as C,a as b,c as l,b as v,e as s,T as N,t as p,f as m,g as z,F as L,h as P,i as x,j as T,k as i,_ as V}from"./index-BcR-H1Jh.js";import{P as D}from"./Pagination-B3o8MPEa.js";const I={class:"case-list"},B={class:"container"},S={class:"content-wrapper"},F={class:"cases-section"},U={key:0,class:"loading"},j={key:1,class:"error"},A={key:2,class:"empty"},E={key:3,class:"list-content"},M={class:"case-grid"},$={class:"case-content"},q={class:"case-title"},G={class:"case-meta"},H={class:"case-date"},J={class:"case-views"},K=y({__name:"CaseListView",setup(O){const n=r([]),g=r(!0),c=r(""),_=r(0),o=r(1),u=r(12),d=async()=>{try{g.value=!0,c.value="";const t=await x("case",o.value,u.value);t.code===200?(n.value=t.rows||[],_.value=t.total||n.value.length,n.value.length===0&&o.value>1&&(o.value=o.value-1,await d())):(c.value=t.msg||"获取数据失败",n.value=[])}catch(t){console.error(t),c.value="获取数据失败,请稍后重试",n.value=[]}finally{g.value=!1}};k([o,u],()=>{d()});const h=(t,e)=>{o.value=t,u.value=e},w=t=>{if(!t)return"未知日期";try{const e=new Date(t);return isNaN(e.getTime())?"未知日期":e.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(e){return console.error("日期格式化错误:",e),"未知日期"}};return C(()=>{d()}),b(()=>{console.log("典型案例列表页面被激活,重新获取数据"),d()}),(t,e)=>{const f=z("router-link");return i(),l("div",I,[v(N),e[9]||(e[9]=s("div",{class:"page-header"},[s("div",{class:"container"},[s("h1",null,"典型案例"),s("p",{class:"subtitle"},"查看劳动纠纷的典型处理案例")])],-1)),s("div",B,[s("div",S,[s("div",F,[g.value?(i(),l("div",U,e[2]||(e[2]=[s("div",{class:"spinner"},null,-1),s("p",null,"加载中...",-1)]))):c.value?(i(),l("div",j,[e[3]||(e[3]=s("h3",null,"内容加载失败",-1)),s("p",null,p(c.value),1),s("button",{onClick:d,class:"btn-retry"},"重试")])):n.value.length===0?(i(),l("div",A,[e[5]||(e[5]=s("h3",null,"暂无内容",-1)),e[6]||(e[6]=s("p",null,"当前没有典型案例相关内容",-1)),v(f,{to:"/",class:"btn-home"},{default:m(()=>e[4]||(e[4]=[T("返回首页")])),_:1,__:[4]})])):(i(),l("div",E,[s("div",M,[(i(!0),l(L,null,P(n.value,a=>(i(),l("div",{key:a.formatId,class:"case-card"},[v(f,{to:`/showcase.html?Id=${a.formatId}`,class:"case-link"},{default:m(()=>[e[8]||(e[8]=s("div",{class:"case-icon"},null,-1)),s("div",$,[s("h3",q,p(a.title),1),s("div",G,[s("span",H,p(w(a.createTime)),1),s("span",J,"浏览: "+p(a.viewCount),1)]),e[7]||(e[7]=s("div",{class:"case-footer"},[s("span",{class:"view-more"},"查看详情")],-1))])]),_:2,__:[8]},1032,["to"])]))),128))]),v(D,{total:_.value,"current-page":o.value,"onUpdate:currentPage":e[0]||(e[0]=a=>o.value=a),"page-size":u.value,"onUpdate:pageSize":e[1]||(e[1]=a=>u.value=a),onChange:h,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),W=V(K,[["__scopeId","data-v-be44cdff"]]);export{W as default};
|
1
boyuehasfj-html-bak/assets/CaseListView-bDxC-yBk.css
Normal file
1
boyuehasfj-html-bak/assets/CaseListView-bDxC-yBk.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html-bak/assets/FormListView-CAzcl-Z3.js
Normal file
1
boyuehasfj-html-bak/assets/FormListView-CAzcl-Z3.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as k,r as i,w as C,o as N,a as b,c as n,b as v,e,T as z,t as m,f as h,g as A,F as L,h as P,i as V,j as x,n as F,l as T,k as l,_ as D}from"./index-BcR-H1Jh.js";import{P as I}from"./Pagination-B3o8MPEa.js";const S={class:"form-list"},B={class:"container"},U={class:"content-wrapper"},j={class:"forms-section"},E={key:0,class:"loading"},J={key:1,class:"error"},M={key:2,class:"empty"},O={key:3,class:"list-content"},$={class:"form-grid"},q={class:"form-content"},G={class:"form-title"},H={class:"form-meta"},K={class:"form-date"},Q={class:"form-views"},R={key:0,class:"form-download-tag"},W=k({__name:"FormListView",setup(X){const r=i([]),f=i(!0),u=i(""),p=i(0),o=i(1),c=i(15),d=async()=>{try{f.value=!0,u.value="";const s=await V("form",o.value,c.value);s.code===200?(r.value=s.rows||[],p.value=s.total||r.value.length,r.value.length===0&&o.value>1&&(o.value=o.value-1,await d())):(u.value=s.msg||"获取数据失败",r.value=[])}catch(s){console.error(s),u.value="获取数据失败,请稍后重试",r.value=[]}finally{f.value=!1}};C([o,c],()=>{d()});const y=(s,t)=>{o.value=s,c.value=t};N(()=>{d()}),b(()=>{console.log("表单下载列表页面被激活,重新获取数据"),d()});function g(s){if(s.attachmentUrl)return!0;if(!s.multiAttachments)return!1;try{const t=JSON.parse(s.multiAttachments);return Array.isArray(t)&&t.length>0}catch(t){return console.error("解析附件列表失败:",t),!1}}const w=s=>{if(!s)return"未知日期";try{const t=new Date(s);return isNaN(t.getTime())?"未知日期":t.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(t){return console.error("日期格式化错误:",t),"未知日期"}};return(s,t)=>{const _=A("router-link");return l(),n("div",S,[v(z),t[8]||(t[8]=e("div",{class:"page-header"},[e("div",{class:"container"},[e("h1",null,"表单下载"),e("p",{class:"subtitle"},"下载各类表单模板,便捷办理相关业务")])],-1)),e("div",B,[e("div",U,[e("div",j,[f.value?(l(),n("div",E,t[2]||(t[2]=[e("div",{class:"spinner"},null,-1),e("p",null,"加载中...",-1)]))):u.value?(l(),n("div",J,[t[3]||(t[3]=e("h3",null,"内容加载失败",-1)),e("p",null,m(u.value),1),e("button",{onClick:d,class:"btn-retry"},"重试")])):r.value.length===0?(l(),n("div",M,[t[5]||(t[5]=e("h3",null,"暂无内容",-1)),t[6]||(t[6]=e("p",null,"当前没有表单下载相关内容",-1)),v(_,{to:"/",class:"btn-home"},{default:h(()=>t[4]||(t[4]=[x("返回首页")])),_:1,__:[4]})])):(l(),n("div",O,[e("div",$,[(l(!0),n(L,null,P(r.value,a=>(l(),n("div",{key:a.formatId,class:"form-card"},[v(_,{to:`/table.html?Id=${a.formatId}`,class:"form-link"},{default:h(()=>[e("div",{class:F(["form-icon",{"has-download":g(a)}])},null,2),e("div",q,[e("h3",G,m(a.title),1),e("div",H,[e("span",K,m(w(a.createTime)),1),e("span",Q,"浏览: "+m(a.viewCount),1),g(a)?(l(),n("span",R,"可下载")):T("",!0)]),t[7]||(t[7]=e("div",{class:"form-footer"},[e("span",{class:"view-more"},"查看详情")],-1))])]),_:2},1032,["to"])]))),128))]),v(I,{total:p.value,"current-page":o.value,"onUpdate:currentPage":t[0]||(t[0]=a=>o.value=a),"page-size":c.value,"onUpdate:pageSize":t[1]||(t[1]=a=>c.value=a),onChange:y,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),tt=D(W,[["__scopeId","data-v-79d36d19"]]);export{tt as default};
|
1
boyuehasfj-html-bak/assets/FormListView-aeZL7PcF.css
Normal file
1
boyuehasfj-html-bak/assets/FormListView-aeZL7PcF.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html-bak/assets/LawListView-DfvmjveR.js
Normal file
1
boyuehasfj-html-bak/assets/LawListView-DfvmjveR.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as y,r,w as k,o as C,a as L,c as n,b as v,e as t,T as b,t as p,f as m,g as N,F as z,h as P,i as x,j as T,k as i,_ as V}from"./index-BcR-H1Jh.js";import{P as D}from"./Pagination-B3o8MPEa.js";const B={class:"law-list-container"},I={class:"container"},S={class:"content-wrapper"},F={class:"law-list"},U={key:0,class:"loading"},j={key:1,class:"error"},A={key:2,class:"empty"},E={key:3,class:"list-content"},M={class:"law-grid"},$={class:"law-content"},q={class:"law-title"},G={class:"law-meta"},H={class:"law-date"},J={class:"law-views"},K=y({__name:"LawListView",setup(O){const o=r([]),g=r(!0),u=r(""),_=r(0),l=r(1),d=r(15),c=async()=>{try{g.value=!0,u.value="";const s=await x("law",l.value,d.value);s.code===200?(o.value=s.rows||[],_.value=s.total||o.value.length,o.value.length===0&&l.value>1&&(l.value=l.value-1,await c())):(u.value=s.msg||"获取数据失败",o.value=[])}catch(s){console.error("获取法律规定列表失败:",s),u.value="获取数据失败,请稍后重试",o.value=[]}finally{g.value=!1}};k([l,d],()=>{c()});const f=(s,e)=>{l.value=s,d.value=e},h=s=>{if(!s)return"未知日期";try{const e=new Date(s);return isNaN(e.getTime())?"未知日期":e.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(e){return console.error("日期格式化错误:",e),"未知日期"}};return C(()=>{c()}),L(()=>{console.log("法律规定列表页面被激活,重新获取数据"),c()}),(s,e)=>{const w=N("router-link");return i(),n("div",B,[v(b),e[9]||(e[9]=t("div",{class:"page-header"},[t("div",{class:"container"},[t("h1",null,"法律规定"),t("p",{class:"subtitle"},"查看并了解最新的法律法规内容")])],-1)),t("div",I,[t("div",S,[t("div",F,[g.value?(i(),n("div",U,e[2]||(e[2]=[t("div",{class:"spinner"},null,-1),t("p",null,"加载中...",-1)]))):u.value?(i(),n("div",j,[e[3]||(e[3]=t("h3",null,"获取数据失败",-1)),t("p",null,p(u.value),1),t("button",{onClick:c,class:"btn-retry"},"重试")])):o.value.length===0?(i(),n("div",A,[e[5]||(e[5]=t("h3",null,"暂无内容",-1)),e[6]||(e[6]=t("p",null,"当前没有法律规定相关内容",-1)),v(w,{to:"/",class:"btn-home"},{default:m(()=>e[4]||(e[4]=[T("返回首页")])),_:1,__:[4]})])):(i(),n("div",E,[t("div",M,[(i(!0),n(z,null,P(o.value,a=>(i(),n("div",{key:a.id,class:"law-card"},[v(w,{to:`/show.html?Id=${a.formatId}`,class:"law-link"},{default:m(()=>[e[8]||(e[8]=t("div",{class:"law-icon"},null,-1)),t("div",$,[t("h3",q,p(a.title),1),t("div",G,[t("span",H,p(h(a.createTime)),1),t("span",J,"浏览: "+p(a.viewCount),1)]),e[7]||(e[7]=t("div",{class:"law-footer"},[t("span",{class:"view-more"},"查看详情")],-1))])]),_:2,__:[8]},1032,["to"])]))),128))]),v(D,{total:_.value,"current-page":l.value,"onUpdate:currentPage":e[0]||(e[0]=a=>l.value=a),"page-size":d.value,"onUpdate:pageSize":e[1]||(e[1]=a=>d.value=a),onChange:f,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),W=V(K,[["__scopeId","data-v-2e0ab612"]]);export{W as default};
|
1
boyuehasfj-html-bak/assets/LawListView-Te234Z7M.css
Normal file
1
boyuehasfj-html-bak/assets/LawListView-Te234Z7M.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html-bak/assets/Pagination-B3o8MPEa.js
Normal file
1
boyuehasfj-html-bak/assets/Pagination-B3o8MPEa.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as z,r as N,w,s as r,c as o,e as l,l as m,n as d,F as k,h as y,t as b,j as f,u as M,v as $,k as u,_ as A}from"./index-BcR-H1Jh.js";const E={class:"pagination-container"},F={class:"pagination"},L=["disabled"],D={key:0,class:"pagination-item"},j={key:1,class:"pagination-item ellipsis"},q=["onClick"],I={key:2,class:"pagination-item ellipsis"},R={key:3,class:"pagination-item"},T=["disabled"],U={class:"pagination-info"},G={class:"total"},H=["value"],J=z({__name:"Pagination",props:{total:{type:Number,required:!0},currentPage:{type:Number,default:1},pageSize:{type:Number,default:10},pageSizes:{type:Array,default:()=>[10,20,50,100]},maxVisibleButtons:{type:Number,default:5}},emits:["update:currentPage","update:pageSize","change"],setup(s,{emit:x}){const n=s,g=x,P=N(n.pageSize);w(P,i=>{g("update:pageSize",i),g("change",1,i)});const a=r(()=>n.total<=0?0:Math.ceil(n.total/n.pageSize)),v=r(()=>{if(a.value<=n.maxVisibleButtons)return Array.from({length:a.value},(h,p)=>p+1);const i=Math.floor(n.maxVisibleButtons/2);let e=n.currentPage-i,t=n.currentPage+i;return e<2&&(e=2,t=Math.min(a.value-1,e+n.maxVisibleButtons-3)),t>=a.value&&(t=a.value-1,e=Math.max(2,t-n.maxVisibleButtons+3)),Array.from({length:t-e+1},(h,p)=>e+p)}),S=r(()=>a.value>0),B=r(()=>a.value>1),C=r(()=>v.value[0]>2),V=r(()=>v.value[v.value.length-1]<a.value-1),c=i=>{i<1||i>a.value||i===n.currentPage||(g("update:currentPage",i),g("change",i,n.pageSize))};return(i,e)=>(u(),o("div",E,[l("ul",F,[l("li",{class:d(["pagination-item",{disabled:s.currentPage===1}])},[l("button",{onClick:e[0]||(e[0]=t=>c(s.currentPage-1)),disabled:s.currentPage===1}," 上一页 ",8,L)],2),S.value?(u(),o("li",D,[l("button",{onClick:e[1]||(e[1]=t=>c(1)),class:d({active:s.currentPage===1})},"1",2)])):m("",!0),C.value?(u(),o("li",j,"...")):m("",!0),(u(!0),o(k,null,y(v.value,t=>(u(),o("li",{key:t,class:"pagination-item"},[l("button",{onClick:h=>c(t),class:d({active:s.currentPage===t})},b(t),11,q)]))),128)),V.value?(u(),o("li",I,"...")):m("",!0),B.value?(u(),o("li",R,[l("button",{onClick:e[2]||(e[2]=t=>c(a.value)),class:d({active:s.currentPage===a.value})},b(a.value),3)])):m("",!0),l("li",{class:d(["pagination-item",{disabled:s.currentPage===a.value||a.value===0}])},[l("button",{onClick:e[3]||(e[3]=t=>c(s.currentPage+1)),disabled:s.currentPage===a.value||a.value===0}," 下一页 ",8,T)],2)]),l("div",U,[e[5]||(e[5]=f(" 共 ")),l("span",G,b(s.total),1),e[6]||(e[6]=f(" 条数据,每页显示 ")),M(l("select",{"onUpdate:modelValue":e[4]||(e[4]=t=>P.value=t),class:"page-size-select"},[(u(!0),o(k,null,y(s.pageSizes,t=>(u(),o("option",{key:t,value:t},b(t),9,H))),128))],512),[[$,P.value]]),e[7]||(e[7]=f(" 条 "))])]))}}),O=A(J,[["__scopeId","data-v-be628029"]]);export{O as P};
|
1
boyuehasfj-html-bak/assets/Pagination-wcAoWHYR.css
Normal file
1
boyuehasfj-html-bak/assets/Pagination-wcAoWHYR.css
Normal file
@ -0,0 +1 @@
|
||||
.pagination-container[data-v-be628029]{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;margin:2rem 0}.pagination[data-v-be628029]{display:flex;list-style:none;padding:0;margin:0;flex-wrap:wrap;gap:.5rem}.pagination-item[data-v-be628029]{margin:0}.pagination-item button[data-v-be628029]{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;font-size:14px;background-color:#fff;border:1px solid #d9d9d9;color:var(--color-text);cursor:pointer;border-radius:4px;transition:all .3s}.pagination-item button[data-v-be628029]:hover{border-color:var(--color-primary);color:var(--color-primary)}.pagination-item button.active[data-v-be628029]{background-color:var(--color-primary);border-color:var(--color-primary);color:#fff}.pagination-item.disabled button[data-v-be628029]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.pagination-item.ellipsis[data-v-be628029]{display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 8px;font-size:14px;color:#00000073}.pagination-info[data-v-be628029]{color:var(--color-text-light);font-size:14px}.total[data-v-be628029]{color:var(--color-primary);font-weight:700}.page-size-select[data-v-be628029]{margin:0 .5rem;padding:.2rem .5rem;border:1px solid #d9d9d9;border-radius:4px;background-color:#fff;cursor:pointer}@media (max-width: 576px){.pagination-container[data-v-be628029]{flex-direction:column;align-items:center}.pagination[data-v-be628029]{margin-bottom:1rem}}
|
1
boyuehasfj-html-bak/assets/QRCodesView-TqtFwfb7.js
Normal file
1
boyuehasfj-html-bak/assets/QRCodesView-TqtFwfb7.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as $,r as o,w as I,o as _,c as i,b as y,e as a,T,n as b,j as v,t as m,F as C,h as Q,m as U,p as q,q as z,Q as B,k as n,_ as P}from"./index-BcR-H1Jh.js";import{P as R}from"./Pagination-B3o8MPEa.js";const V={class:"qrcodes-container"},x={class:"container"},N={class:"content-wrapper"},S={class:"type-tabs"},D={class:"qrcode-section"},F={class:"section-header"},j={key:0,class:"loading-container"},E={key:1,class:"error-container"},L={key:2,class:"empty-container"},M={class:"qrcode-grid"},A=$({__name:"QRCodesView",setup(G){const t=o("law"),u=o(6),r=o(1),w=o(0),d=o([]),p=o(!1),c=o(""),g=async()=>{try{p.value=!0,c.value="";const l=await U(t.value,r.value,u.value);if(l.code===200){const e=l.rows||[];d.value=e.map(s=>({id:s.id||s.formatId,formatId:s.formatId,title:s.title,url:k(s.formatId,t.value),pageType:s.pageType||t.value})),w.value=l.total||e.length}else c.value=l.msg||"获取数据失败",d.value=[]}catch(l){console.error("获取二维码数据失败:",l),c.value="获取数据失败,请稍后重试",d.value=[]}finally{p.value=!1}},k=(l,e)=>{let s="";return e==="law"?s=`/show.html?Id=${l}`:e==="case"?s=`/showcase.html?Id=${l}`:e==="form"?s=`/table.html?Id=${l}`:s=`/show.html?Id=${l}`,`${q()}${s}`},h=(l,e)=>{console.log(`页码切换到: 第${l}页, 每页${e}条`),r.value=l,u.value=e},f=l=>{l!==t.value&&(t.value=l,r.value=1)};return I([r,u,t],()=>{g()}),_(()=>{g()}),(l,e)=>(n(),i("div",V,[y(T),e[11]||(e[11]=a("div",{class:"page-header"},[a("div",{class:"container"},[a("h1",null,"二维码入口"),a("p",{class:"subtitle"},"扫描二维码快速访问各类资源")])],-1)),a("div",x,[a("div",N,[a("div",S,[a("button",{class:b(["type-tab",{active:t.value==="law"}]),onClick:e[0]||(e[0]=s=>f("law"))},e[5]||(e[5]=[a("i",{class:"tab-icon law-icon"},null,-1),v(" 法律规定 ")]),2),a("button",{class:b(["type-tab",{active:t.value==="case"}]),onClick:e[1]||(e[1]=s=>f("case"))},e[6]||(e[6]=[a("i",{class:"tab-icon case-icon"},null,-1),v(" 典型案例 ")]),2),a("button",{class:b(["type-tab",{active:t.value==="form"}]),onClick:e[2]||(e[2]=s=>f("form"))},e[7]||(e[7]=[a("i",{class:"tab-icon form-icon"},null,-1),v(" 表单下载 ")]),2)]),a("div",D,[a("div",F,[a("h2",null,m(t.value==="law"?"法律规定":t.value==="case"?"典型案例":"表单下载"),1)]),p.value?(n(),i("div",j,e[8]||(e[8]=[a("div",{class:"spinner"},null,-1),a("p",null,"加载中...",-1)]))):c.value?(n(),i("div",E,[e[10]||(e[10]=a("h3",null,"获取数据失败",-1)),a("p",null,m(c.value),1),a("button",{onClick:g,class:"retry-button"},e[9]||(e[9]=[a("i",{class:"retry-icon"},null,-1),v(" 重试 ")]))])):d.value.length===0?(n(),i("div",L,[a("p",null,"暂无"+m(t.value==="law"?"法律规定":t.value==="case"?"典型案例":"表单下载")+"相关二维码",1)])):(n(),i(C,{key:3},[a("div",M,[(n(!0),i(C,null,Q(d.value,s=>(n(),z(B,{key:s.id,url:s.url,title:s.title,id:s.formatId},null,8,["url","title","id"]))),128))]),y(R,{total:w.value,"current-page":r.value,"onUpdate:currentPage":e[3]||(e[3]=s=>r.value=s),"page-size":u.value,"onUpdate:pageSize":e[4]||(e[4]=s=>u.value=s),onChange:h,"page-sizes":[6,12,24,36]},null,8,["total","current-page","page-size"])],64))])])])]))}}),O=P(A,[["__scopeId","data-v-272e4945"]]);export{O as default};
|
1
boyuehasfj-html-bak/assets/QRCodesView-Y6kV9Tb7.css
Normal file
1
boyuehasfj-html-bak/assets/QRCodesView-Y6kV9Tb7.css
Normal file
File diff suppressed because one or more lines are too long
2
boyuehasfj-html-bak/assets/SearchResultsView-D_jfK11c.js
Normal file
2
boyuehasfj-html-bak/assets/SearchResultsView-D_jfK11c.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
38
boyuehasfj-html-bak/assets/index-BcR-H1Jh.js
Normal file
38
boyuehasfj-html-bak/assets/index-BcR-H1Jh.js
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html-bak/assets/index-Dd7JvF_n.css
Normal file
1
boyuehasfj-html-bak/assets/index-Dd7JvF_n.css
Normal file
File diff suppressed because one or more lines are too long
BIN
boyuehasfj-html-bak/favicon.ico
Normal file
BIN
boyuehasfj-html-bak/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
14
boyuehasfj-html-bak/index.html
Normal file
14
boyuehasfj-html-bak/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>淮安市司法局民营企业全流程法律风险百问百答</title>
|
||||
<script type="module" crossorigin src="/assets/index-BcR-H1Jh.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dd7JvF_n.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html-bak/show-fallback.html
Normal file
79
boyuehasfj-html-bak/show-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载法律规定详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/law/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到法律规定列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjlaw`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html-bak/showcase-fallback.html
Normal file
79
boyuehasfj-html-bak/showcase-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载典型案例详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/case/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到案例列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjcase`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
59
boyuehasfj-html-bak/static-fallback.html
Normal file
59
boyuehasfj-html-bak/static-fallback.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p>正在重定向到正确的页面...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取当前URL路径
|
||||
const path = window.location.pathname;
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// 根据路径重定向到前端路由
|
||||
if (path === '/hasfjlaw') {
|
||||
window.location.href = baseUrl + '/hasfjlaw';
|
||||
} else if (path === '/hasfjcase') {
|
||||
window.location.href = baseUrl + '/hasfjcase';
|
||||
} else if (path === '/hasfjform') {
|
||||
window.location.href = baseUrl + '/hasfjform';
|
||||
} else {
|
||||
// 默认重定向到首页
|
||||
window.location.href = baseUrl;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html-bak/table-fallback.html
Normal file
79
boyuehasfj-html-bak/table-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载表单下载详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/form/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到表单列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjform`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
BIN
boyuehasfj-html.zip
Normal file
BIN
boyuehasfj-html.zip
Normal file
Binary file not shown.
1
boyuehasfj-html/assets/CaseListView-7zanRSPt.js
Normal file
1
boyuehasfj-html/assets/CaseListView-7zanRSPt.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as k,r,w,o as C,a as N,c as n,b as v,e as s,T as b,t as g,f as m,g as z,F as L,h as P,i as x,j as T,k as i,_ as V}from"./index-p3-iJV3R.js";import{P as D}from"./Pagination-CPYGqmDh.js";const I={class:"case-list"},B={class:"container"},S={class:"content-wrapper"},F={class:"cases-section"},U={key:0,class:"loading"},j={key:1,class:"error"},A={key:2,class:"empty"},E={key:3,class:"list-content"},M={class:"case-grid"},$={class:"case-content"},q={class:"case-title"},G={class:"case-meta"},H={class:"case-date"},J=k({__name:"CaseListView",setup(K){const l=r([]),p=r(!0),c=r(""),_=r(0),a=r(1),u=r(12),d=async()=>{try{p.value=!0,c.value="";const t=await x("case",a.value,u.value);t.code===200?(l.value=t.rows||[],_.value=t.total||l.value.length,l.value.length===0&&a.value>1&&(a.value=a.value-1,await d())):(c.value=t.msg||"获取数据失败",l.value=[])}catch(t){console.error(t),c.value="获取数据失败,请稍后重试",l.value=[]}finally{p.value=!1}};w([a,u],()=>{d()});const h=(t,e)=>{a.value=t,u.value=e},y=t=>{if(!t)return"未知日期";try{const e=new Date(t);return isNaN(e.getTime())?"未知日期":e.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(e){return console.error("日期格式化错误:",e),"未知日期"}};return C(()=>{d()}),N(()=>{console.log("典型案例列表页面被激活,重新获取数据"),d()}),(t,e)=>{const f=z("router-link");return i(),n("div",I,[v(b),e[9]||(e[9]=s("div",{class:"page-header"},[s("div",{class:"container"},[s("h1",null,"典型案例"),s("p",{class:"subtitle"},"查看劳动纠纷的典型处理案例")])],-1)),s("div",B,[s("div",S,[s("div",F,[p.value?(i(),n("div",U,e[2]||(e[2]=[s("div",{class:"spinner"},null,-1),s("p",null,"加载中...",-1)]))):c.value?(i(),n("div",j,[e[3]||(e[3]=s("h3",null,"内容加载失败",-1)),s("p",null,g(c.value),1),s("button",{onClick:d,class:"btn-retry"},"重试")])):l.value.length===0?(i(),n("div",A,[e[5]||(e[5]=s("h3",null,"暂无内容",-1)),e[6]||(e[6]=s("p",null,"当前没有典型案例相关内容",-1)),v(f,{to:"/",class:"btn-home"},{default:m(()=>e[4]||(e[4]=[T("返回首页")])),_:1,__:[4]})])):(i(),n("div",E,[s("div",M,[(i(!0),n(L,null,P(l.value,o=>(i(),n("div",{key:o.formatId,class:"case-card"},[v(f,{to:`/showcase.html?Id=${o.formatId}`,class:"case-link"},{default:m(()=>[e[8]||(e[8]=s("div",{class:"case-icon"},null,-1)),s("div",$,[s("h3",q,g(o.title),1),s("div",G,[s("span",H,g(y(o.createTime)),1)]),e[7]||(e[7]=s("div",{class:"case-footer"},[s("span",{class:"view-more"},"查看详情")],-1))])]),_:2,__:[8]},1032,["to"])]))),128))]),v(D,{total:_.value,"current-page":a.value,"onUpdate:currentPage":e[0]||(e[0]=o=>a.value=o),"page-size":u.value,"onUpdate:pageSize":e[1]||(e[1]=o=>u.value=o),onChange:h,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),R=V(J,[["__scopeId","data-v-888171a2"]]);export{R as default};
|
1
boyuehasfj-html/assets/CaseListView-Df4BL-Ha.css
Normal file
1
boyuehasfj-html/assets/CaseListView-Df4BL-Ha.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html/assets/FormListView-D7hCt0sS.css
Normal file
1
boyuehasfj-html/assets/FormListView-D7hCt0sS.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html/assets/FormListView-DQyGqgEU.js
Normal file
1
boyuehasfj-html/assets/FormListView-DQyGqgEU.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as w,r as i,w as C,o as N,a as b,c as n,b as v,e,T as z,t as f,f as h,g as A,F as L,h as P,i as V,j as x,n as F,l as T,k as l,_ as D}from"./index-p3-iJV3R.js";import{P as I}from"./Pagination-CPYGqmDh.js";const S={class:"form-list"},B={class:"container"},U={class:"content-wrapper"},j={class:"forms-section"},E={key:0,class:"loading"},J={key:1,class:"error"},M={key:2,class:"empty"},O={key:3,class:"list-content"},$={class:"form-grid"},q={class:"form-content"},G={class:"form-title"},H={class:"form-meta"},K={class:"form-date"},Q={key:0,class:"form-download-tag"},R=w({__name:"FormListView",setup(W){const r=i([]),m=i(!0),u=i(""),p=i(0),o=i(1),c=i(15),d=async()=>{try{m.value=!0,u.value="";const s=await V("form",o.value,c.value);s.code===200?(r.value=s.rows||[],p.value=s.total||r.value.length,r.value.length===0&&o.value>1&&(o.value=o.value-1,await d())):(u.value=s.msg||"获取数据失败",r.value=[])}catch(s){console.error(s),u.value="获取数据失败,请稍后重试",r.value=[]}finally{m.value=!1}};C([o,c],()=>{d()});const y=(s,t)=>{o.value=s,c.value=t};N(()=>{d()}),b(()=>{console.log("表单下载列表页面被激活,重新获取数据"),d()});function g(s){if(s.attachmentUrl)return!0;if(!s.multiAttachments)return!1;try{const t=JSON.parse(s.multiAttachments);return Array.isArray(t)&&t.length>0}catch(t){return console.error("解析附件列表失败:",t),!1}}const k=s=>{if(!s)return"未知日期";try{const t=new Date(s);return isNaN(t.getTime())?"未知日期":t.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(t){return console.error("日期格式化错误:",t),"未知日期"}};return(s,t)=>{const _=A("router-link");return l(),n("div",S,[v(z),t[8]||(t[8]=e("div",{class:"page-header"},[e("div",{class:"container"},[e("h1",null,"表单下载"),e("p",{class:"subtitle"},"下载各类表单模板,便捷办理相关业务")])],-1)),e("div",B,[e("div",U,[e("div",j,[m.value?(l(),n("div",E,t[2]||(t[2]=[e("div",{class:"spinner"},null,-1),e("p",null,"加载中...",-1)]))):u.value?(l(),n("div",J,[t[3]||(t[3]=e("h3",null,"内容加载失败",-1)),e("p",null,f(u.value),1),e("button",{onClick:d,class:"btn-retry"},"重试")])):r.value.length===0?(l(),n("div",M,[t[5]||(t[5]=e("h3",null,"暂无内容",-1)),t[6]||(t[6]=e("p",null,"当前没有表单下载相关内容",-1)),v(_,{to:"/",class:"btn-home"},{default:h(()=>t[4]||(t[4]=[x("返回首页")])),_:1,__:[4]})])):(l(),n("div",O,[e("div",$,[(l(!0),n(L,null,P(r.value,a=>(l(),n("div",{key:a.formatId,class:"form-card"},[v(_,{to:`/table.html?Id=${a.formatId}`,class:"form-link"},{default:h(()=>[e("div",{class:F(["form-icon",{"has-download":g(a)}])},null,2),e("div",q,[e("h3",G,f(a.title),1),e("div",H,[e("span",K,f(k(a.createTime)),1),g(a)?(l(),n("span",Q,"可下载")):T("",!0)]),t[7]||(t[7]=e("div",{class:"form-footer"},[e("span",{class:"view-more"},"查看详情")],-1))])]),_:2},1032,["to"])]))),128))]),v(I,{total:p.value,"current-page":o.value,"onUpdate:currentPage":t[0]||(t[0]=a=>o.value=a),"page-size":c.value,"onUpdate:pageSize":t[1]||(t[1]=a=>c.value=a),onChange:y,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),Z=D(R,[["__scopeId","data-v-1da35e1a"]]);export{Z as default};
|
1
boyuehasfj-html/assets/LawListView-DaJADi1Z.css
Normal file
1
boyuehasfj-html/assets/LawListView-DaJADi1Z.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html/assets/LawListView-QP4siuzG.js
Normal file
1
boyuehasfj-html/assets/LawListView-QP4siuzG.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as y,r,w as k,o as L,a as C,c as n,b as v,e as t,T as N,t as g,f as m,g as b,F as z,h as P,i as x,j as T,k as i,_ as V}from"./index-p3-iJV3R.js";import{P as D}from"./Pagination-CPYGqmDh.js";const B={class:"law-list-container"},I={class:"container"},S={class:"content-wrapper"},F={class:"law-list"},U={key:0,class:"loading"},j={key:1,class:"error"},A={key:2,class:"empty"},E={key:3,class:"list-content"},M={class:"law-grid"},$={class:"law-content"},q={class:"law-title"},G={class:"law-meta"},H={class:"law-date"},J=y({__name:"LawListView",setup(K){const o=r([]),p=r(!0),u=r(""),_=r(0),a=r(1),d=r(15),c=async()=>{try{p.value=!0,u.value="";const s=await x("law",a.value,d.value);s.code===200?(o.value=s.rows||[],_.value=s.total||o.value.length,o.value.length===0&&a.value>1&&(a.value=a.value-1,await c())):(u.value=s.msg||"获取数据失败",o.value=[])}catch(s){console.error("获取法律规定列表失败:",s),u.value="获取数据失败,请稍后重试",o.value=[]}finally{p.value=!1}};k([a,d],()=>{c()});const f=(s,e)=>{a.value=s,d.value=e},h=s=>{if(!s)return"未知日期";try{const e=new Date(s);return isNaN(e.getTime())?"未知日期":e.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}catch(e){return console.error("日期格式化错误:",e),"未知日期"}};return L(()=>{c()}),C(()=>{console.log("法律规定列表页面被激活,重新获取数据"),c()}),(s,e)=>{const w=b("router-link");return i(),n("div",B,[v(N),e[9]||(e[9]=t("div",{class:"page-header"},[t("div",{class:"container"},[t("h1",null,"法律规定"),t("p",{class:"subtitle"},"查看并了解最新的法律法规内容")])],-1)),t("div",I,[t("div",S,[t("div",F,[p.value?(i(),n("div",U,e[2]||(e[2]=[t("div",{class:"spinner"},null,-1),t("p",null,"加载中...",-1)]))):u.value?(i(),n("div",j,[e[3]||(e[3]=t("h3",null,"获取数据失败",-1)),t("p",null,g(u.value),1),t("button",{onClick:c,class:"btn-retry"},"重试")])):o.value.length===0?(i(),n("div",A,[e[5]||(e[5]=t("h3",null,"暂无内容",-1)),e[6]||(e[6]=t("p",null,"当前没有法律规定相关内容",-1)),v(w,{to:"/",class:"btn-home"},{default:m(()=>e[4]||(e[4]=[T("返回首页")])),_:1,__:[4]})])):(i(),n("div",E,[t("div",M,[(i(!0),n(z,null,P(o.value,l=>(i(),n("div",{key:l.id,class:"law-card"},[v(w,{to:`/show.html?Id=${l.formatId}`,class:"law-link"},{default:m(()=>[e[8]||(e[8]=t("div",{class:"law-icon"},null,-1)),t("div",$,[t("h3",q,g(l.title),1),t("div",G,[t("span",H,g(h(l.createTime)),1)]),e[7]||(e[7]=t("div",{class:"law-footer"},[t("span",{class:"view-more"},"查看详情")],-1))])]),_:2,__:[8]},1032,["to"])]))),128))]),v(D,{total:_.value,"current-page":a.value,"onUpdate:currentPage":e[0]||(e[0]=l=>a.value=l),"page-size":d.value,"onUpdate:pageSize":e[1]||(e[1]=l=>d.value=l),onChange:f,"page-sizes":[8,16,24,32]},null,8,["total","current-page","page-size"])]))])])])])}}}),R=V(J,[["__scopeId","data-v-78560292"]]);export{R as default};
|
1
boyuehasfj-html/assets/Pagination-CPYGqmDh.js
Normal file
1
boyuehasfj-html/assets/Pagination-CPYGqmDh.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as z,r as N,w,s as r,c as o,e as l,l as m,n as d,F as k,h as y,t as b,j as f,u as M,v as $,k as u,_ as A}from"./index-p3-iJV3R.js";const E={class:"pagination-container"},F={class:"pagination"},L=["disabled"],D={key:0,class:"pagination-item"},j={key:1,class:"pagination-item ellipsis"},q=["onClick"],I={key:2,class:"pagination-item ellipsis"},R={key:3,class:"pagination-item"},T=["disabled"],U={class:"pagination-info"},G={class:"total"},H=["value"],J=z({__name:"Pagination",props:{total:{type:Number,required:!0},currentPage:{type:Number,default:1},pageSize:{type:Number,default:10},pageSizes:{type:Array,default:()=>[10,20,50,100]},maxVisibleButtons:{type:Number,default:5}},emits:["update:currentPage","update:pageSize","change"],setup(s,{emit:x}){const n=s,g=x,P=N(n.pageSize);w(P,i=>{g("update:pageSize",i),g("change",1,i)});const a=r(()=>n.total<=0?0:Math.ceil(n.total/n.pageSize)),v=r(()=>{if(a.value<=n.maxVisibleButtons)return Array.from({length:a.value},(h,p)=>p+1);const i=Math.floor(n.maxVisibleButtons/2);let e=n.currentPage-i,t=n.currentPage+i;return e<2&&(e=2,t=Math.min(a.value-1,e+n.maxVisibleButtons-3)),t>=a.value&&(t=a.value-1,e=Math.max(2,t-n.maxVisibleButtons+3)),Array.from({length:t-e+1},(h,p)=>e+p)}),S=r(()=>a.value>0),B=r(()=>a.value>1),C=r(()=>v.value[0]>2),V=r(()=>v.value[v.value.length-1]<a.value-1),c=i=>{i<1||i>a.value||i===n.currentPage||(g("update:currentPage",i),g("change",i,n.pageSize))};return(i,e)=>(u(),o("div",E,[l("ul",F,[l("li",{class:d(["pagination-item",{disabled:s.currentPage===1}])},[l("button",{onClick:e[0]||(e[0]=t=>c(s.currentPage-1)),disabled:s.currentPage===1}," 上一页 ",8,L)],2),S.value?(u(),o("li",D,[l("button",{onClick:e[1]||(e[1]=t=>c(1)),class:d({active:s.currentPage===1})},"1",2)])):m("",!0),C.value?(u(),o("li",j,"...")):m("",!0),(u(!0),o(k,null,y(v.value,t=>(u(),o("li",{key:t,class:"pagination-item"},[l("button",{onClick:h=>c(t),class:d({active:s.currentPage===t})},b(t),11,q)]))),128)),V.value?(u(),o("li",I,"...")):m("",!0),B.value?(u(),o("li",R,[l("button",{onClick:e[2]||(e[2]=t=>c(a.value)),class:d({active:s.currentPage===a.value})},b(a.value),3)])):m("",!0),l("li",{class:d(["pagination-item",{disabled:s.currentPage===a.value||a.value===0}])},[l("button",{onClick:e[3]||(e[3]=t=>c(s.currentPage+1)),disabled:s.currentPage===a.value||a.value===0}," 下一页 ",8,T)],2)]),l("div",U,[e[5]||(e[5]=f(" 共 ")),l("span",G,b(s.total),1),e[6]||(e[6]=f(" 条数据,每页显示 ")),M(l("select",{"onUpdate:modelValue":e[4]||(e[4]=t=>P.value=t),class:"page-size-select"},[(u(!0),o(k,null,y(s.pageSizes,t=>(u(),o("option",{key:t,value:t},b(t),9,H))),128))],512),[[$,P.value]]),e[7]||(e[7]=f(" 条 "))])]))}}),O=A(J,[["__scopeId","data-v-be628029"]]);export{O as P};
|
1
boyuehasfj-html/assets/Pagination-wcAoWHYR.css
Normal file
1
boyuehasfj-html/assets/Pagination-wcAoWHYR.css
Normal file
@ -0,0 +1 @@
|
||||
.pagination-container[data-v-be628029]{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;margin:2rem 0}.pagination[data-v-be628029]{display:flex;list-style:none;padding:0;margin:0;flex-wrap:wrap;gap:.5rem}.pagination-item[data-v-be628029]{margin:0}.pagination-item button[data-v-be628029]{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;font-size:14px;background-color:#fff;border:1px solid #d9d9d9;color:var(--color-text);cursor:pointer;border-radius:4px;transition:all .3s}.pagination-item button[data-v-be628029]:hover{border-color:var(--color-primary);color:var(--color-primary)}.pagination-item button.active[data-v-be628029]{background-color:var(--color-primary);border-color:var(--color-primary);color:#fff}.pagination-item.disabled button[data-v-be628029]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.pagination-item.ellipsis[data-v-be628029]{display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 8px;font-size:14px;color:#00000073}.pagination-info[data-v-be628029]{color:var(--color-text-light);font-size:14px}.total[data-v-be628029]{color:var(--color-primary);font-weight:700}.page-size-select[data-v-be628029]{margin:0 .5rem;padding:.2rem .5rem;border:1px solid #d9d9d9;border-radius:4px;background-color:#fff;cursor:pointer}@media (max-width: 576px){.pagination-container[data-v-be628029]{flex-direction:column;align-items:center}.pagination[data-v-be628029]{margin-bottom:1rem}}
|
1
boyuehasfj-html/assets/QRCodesView-DdqwMmpH.js
Normal file
1
boyuehasfj-html/assets/QRCodesView-DdqwMmpH.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as $,r as o,w as I,o as _,c as i,b as y,e as a,T,n as b,j as v,t as m,F as C,h as Q,m as U,p as q,q as z,Q as B,k as n,_ as P}from"./index-p3-iJV3R.js";import{P as R}from"./Pagination-CPYGqmDh.js";const V={class:"qrcodes-container"},x={class:"container"},N={class:"content-wrapper"},S={class:"type-tabs"},D={class:"qrcode-section"},F={class:"section-header"},j={key:0,class:"loading-container"},E={key:1,class:"error-container"},L={key:2,class:"empty-container"},M={class:"qrcode-grid"},A=$({__name:"QRCodesView",setup(G){const t=o("law"),u=o(6),r=o(1),w=o(0),d=o([]),p=o(!1),c=o(""),g=async()=>{try{p.value=!0,c.value="";const l=await U(t.value,r.value,u.value);if(l.code===200){const e=l.rows||[];d.value=e.map(s=>({id:s.id||s.formatId,formatId:s.formatId,title:s.title,url:k(s.formatId,t.value),pageType:s.pageType||t.value})),w.value=l.total||e.length}else c.value=l.msg||"获取数据失败",d.value=[]}catch(l){console.error("获取二维码数据失败:",l),c.value="获取数据失败,请稍后重试",d.value=[]}finally{p.value=!1}},k=(l,e)=>{let s="";return e==="law"?s=`/show.html?Id=${l}`:e==="case"?s=`/showcase.html?Id=${l}`:e==="form"?s=`/table.html?Id=${l}`:s=`/show.html?Id=${l}`,`${q()}${s}`},h=(l,e)=>{console.log(`页码切换到: 第${l}页, 每页${e}条`),r.value=l,u.value=e},f=l=>{l!==t.value&&(t.value=l,r.value=1)};return I([r,u,t],()=>{g()}),_(()=>{g()}),(l,e)=>(n(),i("div",V,[y(T),e[11]||(e[11]=a("div",{class:"page-header"},[a("div",{class:"container"},[a("h1",null,"二维码入口"),a("p",{class:"subtitle"},"扫描二维码快速访问各类资源")])],-1)),a("div",x,[a("div",N,[a("div",S,[a("button",{class:b(["type-tab",{active:t.value==="law"}]),onClick:e[0]||(e[0]=s=>f("law"))},e[5]||(e[5]=[a("i",{class:"tab-icon law-icon"},null,-1),v(" 法律规定 ")]),2),a("button",{class:b(["type-tab",{active:t.value==="case"}]),onClick:e[1]||(e[1]=s=>f("case"))},e[6]||(e[6]=[a("i",{class:"tab-icon case-icon"},null,-1),v(" 典型案例 ")]),2),a("button",{class:b(["type-tab",{active:t.value==="form"}]),onClick:e[2]||(e[2]=s=>f("form"))},e[7]||(e[7]=[a("i",{class:"tab-icon form-icon"},null,-1),v(" 表单下载 ")]),2)]),a("div",D,[a("div",F,[a("h2",null,m(t.value==="law"?"法律规定":t.value==="case"?"典型案例":"表单下载"),1)]),p.value?(n(),i("div",j,e[8]||(e[8]=[a("div",{class:"spinner"},null,-1),a("p",null,"加载中...",-1)]))):c.value?(n(),i("div",E,[e[10]||(e[10]=a("h3",null,"获取数据失败",-1)),a("p",null,m(c.value),1),a("button",{onClick:g,class:"retry-button"},e[9]||(e[9]=[a("i",{class:"retry-icon"},null,-1),v(" 重试 ")]))])):d.value.length===0?(n(),i("div",L,[a("p",null,"暂无"+m(t.value==="law"?"法律规定":t.value==="case"?"典型案例":"表单下载")+"相关二维码",1)])):(n(),i(C,{key:3},[a("div",M,[(n(!0),i(C,null,Q(d.value,s=>(n(),z(B,{key:s.id,url:s.url,title:s.title,id:s.formatId},null,8,["url","title","id"]))),128))]),y(R,{total:w.value,"current-page":r.value,"onUpdate:currentPage":e[3]||(e[3]=s=>r.value=s),"page-size":u.value,"onUpdate:pageSize":e[4]||(e[4]=s=>u.value=s),onChange:h,"page-sizes":[6,12,24,36]},null,8,["total","current-page","page-size"])],64))])])])]))}}),O=P(A,[["__scopeId","data-v-272e4945"]]);export{O as default};
|
1
boyuehasfj-html/assets/QRCodesView-Y6kV9Tb7.css
Normal file
1
boyuehasfj-html/assets/QRCodesView-Y6kV9Tb7.css
Normal file
File diff suppressed because one or more lines are too long
2
boyuehasfj-html/assets/SearchResultsView-CB_YbNqO.js
Normal file
2
boyuehasfj-html/assets/SearchResultsView-CB_YbNqO.js
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html/assets/SearchResultsView-DwFS11Nn.css
Normal file
1
boyuehasfj-html/assets/SearchResultsView-DwFS11Nn.css
Normal file
File diff suppressed because one or more lines are too long
1
boyuehasfj-html/assets/index-KI5d1KgQ.css
Normal file
1
boyuehasfj-html/assets/index-KI5d1KgQ.css
Normal file
File diff suppressed because one or more lines are too long
38
boyuehasfj-html/assets/index-p3-iJV3R.js
Normal file
38
boyuehasfj-html/assets/index-p3-iJV3R.js
Normal file
File diff suppressed because one or more lines are too long
BIN
boyuehasfj-html/favicon.ico
Normal file
BIN
boyuehasfj-html/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
14
boyuehasfj-html/index.html
Normal file
14
boyuehasfj-html/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>淮安市司法局民营企业全流程法律风险百问百答</title>
|
||||
<script type="module" crossorigin src="/assets/index-p3-iJV3R.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-KI5d1KgQ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html/show-fallback.html
Normal file
79
boyuehasfj-html/show-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载法律规定详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/law/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到法律规定列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjlaw`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html/showcase-fallback.html
Normal file
79
boyuehasfj-html/showcase-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载典型案例详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/case/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到案例列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjcase`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
59
boyuehasfj-html/static-fallback.html
Normal file
59
boyuehasfj-html/static-fallback.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p>正在重定向到正确的页面...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取当前URL路径
|
||||
const path = window.location.pathname;
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// 根据路径重定向到前端路由
|
||||
if (path === '/hasfjlaw') {
|
||||
window.location.href = baseUrl + '/hasfjlaw';
|
||||
} else if (path === '/hasfjcase') {
|
||||
window.location.href = baseUrl + '/hasfjcase';
|
||||
} else if (path === '/hasfjform') {
|
||||
window.location.href = baseUrl + '/hasfjform';
|
||||
} else {
|
||||
// 默认重定向到首页
|
||||
window.location.href = baseUrl;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
boyuehasfj-html/table-fallback.html
Normal file
79
boyuehasfj-html/table-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载表单下载详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/form/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到表单列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjform`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
22
eslint.config.ts
Normal file
22
eslint.config.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
skipFormatting,
|
||||
)
|
15
hasfj.conf
Normal file
15
hasfj.conf
Normal file
@ -0,0 +1,15 @@
|
||||
location ^~ /hasfj {
|
||||
proxy_pass http://222.184.49.22:9799;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
proxy_ssl_name $proxy_host;
|
||||
}
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>淮安市司法局民营企业全流程法律风险百问百答</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "boyue-vue3-html",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"pinia": "^3.0.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-vue": "~10.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^5.4.19",
|
||||
"vite-plugin-vue-devtools": "^7.7.2",
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
3521
pnpm-lock.yaml
generated
Normal file
3521
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
79
public/show-fallback.html
Normal file
79
public/show-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载法律规定详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/law/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到法律规定列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjlaw`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
public/showcase-fallback.html
Normal file
79
public/showcase-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载典型案例详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/case/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到案例列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjcase`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
59
public/static-fallback.html
Normal file
59
public/static-fallback.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p>正在重定向到正确的页面...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取当前URL路径
|
||||
const path = window.location.pathname;
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// 根据路径重定向到前端路由
|
||||
if (path === '/hasfjlaw') {
|
||||
window.location.href = baseUrl + '/hasfjlaw';
|
||||
} else if (path === '/hasfjcase') {
|
||||
window.location.href = baseUrl + '/hasfjcase';
|
||||
} else if (path === '/hasfjform') {
|
||||
window.location.href = baseUrl + '/hasfjform';
|
||||
} else {
|
||||
// 默认重定向到首页
|
||||
window.location.href = baseUrl;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
79
public/table-fallback.html
Normal file
79
public/table-fallback.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>重定向中...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 90%;
|
||||
width: 450px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #007bff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<p class="message">正在加载表单下载详情...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取URL中的ID参数
|
||||
function getQueryParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
|
||||
// 重定向到前端应用
|
||||
function redirectToApp() {
|
||||
const id = getQueryParam('Id');
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (id) {
|
||||
// 使用vue router处理详情页
|
||||
window.location.href = `${baseUrl}/#/detail/form/${id}`;
|
||||
} else {
|
||||
// 如果没有ID参数,重定向到表单列表页
|
||||
window.location.href = `${baseUrl}/#/hasfjform`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后立即重定向
|
||||
window.onload = function() {
|
||||
// 稍微延迟以确保旋转加载效果显示
|
||||
setTimeout(redirectToApp, 800);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
158
src/App.vue
Normal file
158
src/App.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<keep-alive>
|
||||
<RouterView />
|
||||
</keep-alive>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-primary: #007bff;
|
||||
--color-primary-dark: #0062cc;
|
||||
--color-primary-light: #e6f2ff;
|
||||
--color-success: #28a745;
|
||||
--color-success-dark: #1e7e34;
|
||||
--color-danger: #dc3545;
|
||||
--color-danger-dark: #bd2130;
|
||||
--color-background: #f8f9fa;
|
||||
--color-background-alt: #f0f2f5;
|
||||
--color-text: #333333;
|
||||
--color-text-light: #666666;
|
||||
--color-text-muted: #999999;
|
||||
--color-border: #e1e4e8;
|
||||
--color-border-light: #eaecef;
|
||||
--font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
--container-width-xl: 100%;
|
||||
--container-width-lg: 100%;
|
||||
--container-width-md: 100%;
|
||||
--container-width-sm: 100%;
|
||||
--border-radius: 0;
|
||||
--transition-fast: 0.2s;
|
||||
--transition-normal: 0.3s;
|
||||
--transition-slow: 0.5s;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-size: 16px;
|
||||
scroll-behavior: smooth;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
line-height: 1.6;
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-background);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
transition: color var(--transition-normal);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 响应式布局媒体查询 */
|
||||
@media (max-width: 1200px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
86
src/assets/base.css
Normal file
86
src/assets/base.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 276 B |
35
src/assets/main.css
Normal file
35
src/assets/main.css
Normal file
@ -0,0 +1,35 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
41
src/components/HelloWorld.vue
Normal file
41
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
49
src/components/LoadingSpinner.vue
Normal file
49
src/components/LoadingSpinner.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
<p v-if="message" class="loading-message">{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
message: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-left-color: #1677ff;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
margin-top: 1rem;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
235
src/components/Pagination.vue
Normal file
235
src/components/Pagination.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="pagination-container">
|
||||
<ul class="pagination">
|
||||
<li class="pagination-item" :class="{ disabled: currentPage === 1 }">
|
||||
<button @click="changePage(currentPage - 1)" :disabled="currentPage === 1">
|
||||
上一页
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- 首页 -->
|
||||
<li class="pagination-item" v-if="showFirst">
|
||||
<button @click="changePage(1)" :class="{ active: currentPage === 1 }">1</button>
|
||||
</li>
|
||||
|
||||
<!-- 左省略号 -->
|
||||
<li class="pagination-item ellipsis" v-if="showLeftEllipsis">...</li>
|
||||
|
||||
<!-- 页码 -->
|
||||
<li v-for="page in visiblePageNumbers" :key="page" class="pagination-item">
|
||||
<button @click="changePage(page)" :class="{ active: currentPage === page }">
|
||||
{{ page }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- 右省略号 -->
|
||||
<li class="pagination-item ellipsis" v-if="showRightEllipsis">...</li>
|
||||
|
||||
<!-- 尾页 -->
|
||||
<li class="pagination-item" v-if="showLast">
|
||||
<button @click="changePage(totalPages)" :class="{ active: currentPage === totalPages }">
|
||||
{{ totalPages }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="pagination-item" :class="{ disabled: currentPage === totalPages || totalPages === 0 }">
|
||||
<button @click="changePage(currentPage + 1)" :disabled="currentPage === totalPages || totalPages === 0">
|
||||
下一页
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="pagination-info">
|
||||
共 <span class="total">{{ total }}</span> 条数据,每页显示
|
||||
<select v-model="pageSizeModel" class="page-size-select">
|
||||
<option v-for="size in pageSizes" :key="size" :value="size">{{ size }}</option>
|
||||
</select> 条
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as () => number[],
|
||||
default: () => [10, 20, 50, 100]
|
||||
},
|
||||
maxVisibleButtons: {
|
||||
type: Number,
|
||||
default: 5
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:currentPage', 'update:pageSize', 'change']);
|
||||
|
||||
// 创建双向绑定值
|
||||
const pageSizeModel = ref(props.pageSize);
|
||||
|
||||
// 监听页面大小变化
|
||||
watch(pageSizeModel, (newSize) => {
|
||||
emit('update:pageSize', newSize);
|
||||
emit('change', 1, newSize); // 修改每页数量后,回到第1页
|
||||
});
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return props.total <= 0 ? 0 : Math.ceil(props.total / props.pageSize);
|
||||
});
|
||||
|
||||
// 计算显示哪些页码
|
||||
const visiblePageNumbers = computed(() => {
|
||||
if (totalPages.value <= props.maxVisibleButtons) {
|
||||
// 如果总页数小于最大可见按钮数,则全部显示
|
||||
return Array.from({ length: totalPages.value }, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
// 计算中间部分要显示的页码
|
||||
const halfButtons = Math.floor(props.maxVisibleButtons / 2);
|
||||
let startPage = props.currentPage - halfButtons;
|
||||
let endPage = props.currentPage + halfButtons;
|
||||
|
||||
if (startPage < 2) {
|
||||
startPage = 2;
|
||||
endPage = Math.min(totalPages.value - 1, startPage + props.maxVisibleButtons - 3);
|
||||
}
|
||||
|
||||
if (endPage >= totalPages.value) {
|
||||
endPage = totalPages.value - 1;
|
||||
startPage = Math.max(2, endPage - props.maxVisibleButtons + 3);
|
||||
}
|
||||
|
||||
return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
|
||||
});
|
||||
|
||||
// 是否显示首页和尾页
|
||||
const showFirst = computed(() => totalPages.value > 0);
|
||||
const showLast = computed(() => totalPages.value > 1);
|
||||
|
||||
// 是否显示省略号
|
||||
const showLeftEllipsis = computed(() => visiblePageNumbers.value[0] > 2);
|
||||
const showRightEllipsis = computed(() => {
|
||||
return visiblePageNumbers.value[visiblePageNumbers.value.length - 1] < totalPages.value - 1;
|
||||
});
|
||||
|
||||
// 页码变化处理
|
||||
const changePage = (page: number) => {
|
||||
if (page < 1 || page > totalPages.value || page === props.currentPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('update:currentPage', page);
|
||||
emit('change', page, props.pageSize);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.pagination-item {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pagination-item button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
background-color: white;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-item button:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.pagination-item button.active {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pagination-item.disabled button {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
background-color: #f5f5f5;
|
||||
border-color: #d9d9d9;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-item.ellipsis {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: var(--color-text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total {
|
||||
color: var(--color-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.page-size-select {
|
||||
margin: 0 0.5rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.pagination-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
433
src/components/QRCodeDisplay.vue
Normal file
433
src/components/QRCodeDisplay.vue
Normal file
@ -0,0 +1,433 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const qrcodeEl = ref<HTMLElement | null>(null);
|
||||
const qrcodeImgUrl = ref('');
|
||||
const isLoading = ref(true);
|
||||
|
||||
// 获取显示友好的URL
|
||||
const getDisplayUrl = (url: string): string => {
|
||||
try {
|
||||
// 从URL中提取完整URL,不再截断
|
||||
const urlObj = new URL(url);
|
||||
return url;
|
||||
} catch (e) {
|
||||
// 如果URL格式不正确,则显示完整URL
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// 使用 toDataURL 方法生成二维码数据 URL
|
||||
qrcodeImgUrl.value = await QRCode.toDataURL(props.url, {
|
||||
width: 180,
|
||||
margin: 1,
|
||||
errorCorrectionLevel: 'H',
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error);
|
||||
// 如果生成失败,使用一个默认的二维码图片作为替代
|
||||
qrcodeImgUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(props.url)}`;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
const onDownload = () => {
|
||||
try {
|
||||
if (!qrcodeImgUrl.value) {
|
||||
throw new Error('二维码图片不存在');
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a');
|
||||
link.download = `${props.title}-${props.id}.png`;
|
||||
link.href = qrcodeImgUrl.value;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.error('下载二维码失败:', error);
|
||||
alert('下载二维码失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="qrcode-item">
|
||||
<div class="qrcode-wrapper">
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<div v-else class="qrcode">
|
||||
<img :src="qrcodeImgUrl" :alt="title" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="qrcode-info">
|
||||
<h4>{{ title }}</h4>
|
||||
<div class="qr-url-hint">{{ getDisplayUrl(url) }}</div>
|
||||
<div class="qrcode-controls">
|
||||
<div class="id-container">
|
||||
<span class="id-label">编号:</span>
|
||||
<span class="id-value" :title="id">{{ id }}</span>
|
||||
</div>
|
||||
<button @click="onDownload" class="btn-download" :title="`下载${title}二维码`">
|
||||
<span class="icon-download"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.qrcode {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qrcode img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.qrcode-item {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.4s ease;
|
||||
max-width: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 1px solid #f0f0f0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qrcode-item:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
border-color: rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.qrcode-item:hover::before {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.qrcode-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
|
||||
transform: translateY(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.qrcode-item:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.qrcode-item::after {
|
||||
content: '扫码访问';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 20px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
z-index: 2;
|
||||
box-shadow: 0 3px 6px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.qrcode-item:hover::after {
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px dashed #eee;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--color-primary);
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.qrcode-info {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qrcode-info h4 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qrcode-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-light);
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-radius: 30px;
|
||||
margin-top: 0.8rem;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.qrcode-item:hover .qrcode-controls {
|
||||
background-color: #f0f8ff;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.qr-url-hint {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.5rem;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
text-align: left;
|
||||
max-height: 3.5rem;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.id-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 70%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.id-label {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
.id-value {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.btn-download {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
padding: 0.4rem;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: var(--color-primary);
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-download::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-primary);
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.btn-download:hover {
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-download:hover::before {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-download:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.icon-download {
|
||||
position: relative;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.icon-download::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 10px;
|
||||
height: 12px;
|
||||
border: 2px solid currentColor;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.icon-download::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 8px solid currentColor;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.qrcode-item {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.qrcode-info h4 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.qrcode-item {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.qrcode-item {
|
||||
max-width: 250px;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.qrcode-info h4 {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.qrcode-controls {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.qrcode-item {
|
||||
max-width: 200px;
|
||||
padding: 0.7rem;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
</style>
|
699
src/components/SearchBar.vue
Normal file
699
src/components/SearchBar.vue
Normal file
@ -0,0 +1,699 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { searchContent } from '../services/api';
|
||||
|
||||
interface SearchResult {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
pageType: string;
|
||||
content?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
isMobile: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isHomePage: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const searchQuery = ref('');
|
||||
const searchResults = ref<SearchResult[]>([]);
|
||||
const filteredResults = ref<SearchResult[]>([]);
|
||||
const showResults = ref(false);
|
||||
const isSearching = ref(false);
|
||||
const searchError = ref('');
|
||||
|
||||
// 执行搜索
|
||||
async function performSearch() {
|
||||
if (!searchQuery.value || searchQuery.value.trim() === '') {
|
||||
filteredResults.value = [];
|
||||
showResults.value = false;
|
||||
searchError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isSearching.value = true;
|
||||
searchError.value = '';
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 15000); // 增加超时时间到15秒
|
||||
|
||||
// 确保查询字符串不是纯数字,避免后端将其解析为ID
|
||||
const searchText = searchQuery.value.trim();
|
||||
|
||||
// 检查是否为特殊关键词,如"search"或"如何"等常见词汇
|
||||
const isSpecialKeyword = searchText === 'search' ||
|
||||
searchText.toLowerCase() === 'search' ||
|
||||
searchText === '如何' ||
|
||||
searchText.includes('?') ||
|
||||
searchText.includes('?');
|
||||
|
||||
if (isSpecialKeyword) {
|
||||
console.log('检测到特殊关键词: "' + searchText + '",将使用专用搜索API');
|
||||
}
|
||||
|
||||
// 检查是否为纯数字,如果是,在前端日志中记录警告
|
||||
const isNumeric = /^\d+$/.test(searchText);
|
||||
if (isNumeric) {
|
||||
console.log('警告: 搜索关键词是纯数字,将使用专用搜索API');
|
||||
}
|
||||
|
||||
console.log('开始搜索,关键词:', searchText,
|
||||
isNumeric ? '(纯数字)' : isSpecialKeyword ? '(特殊关键词)' : '');
|
||||
|
||||
try {
|
||||
// 使用导入的searchContent函数进行搜索,设置titleOnly为true,仅搜索标题
|
||||
const result = await searchContent(searchText, undefined, true);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (result && result.code === 200) {
|
||||
let searchData = [];
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
searchData = result.data;
|
||||
console.log('使用data字段数据:', searchData.length);
|
||||
} else if (result.rows && Array.isArray(result.rows)) {
|
||||
searchData = result.rows;
|
||||
console.log('使用rows字段数据:', searchData.length);
|
||||
} else if (result.data && !Array.isArray(result.data)) {
|
||||
searchData = [result.data];
|
||||
console.log('使用单个对象数据');
|
||||
} else if (result.rows && !Array.isArray(result.rows)) {
|
||||
searchData = [result.rows];
|
||||
console.log('使用单个对象数据');
|
||||
}
|
||||
|
||||
if (searchData.length === 0) {
|
||||
showResults.value = true;
|
||||
searchError.value = '未找到相关内容';
|
||||
isSearching.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
searchResults.value = searchData;
|
||||
filteredResults.value = searchData;
|
||||
console.log('处理后的搜索结果:', filteredResults.value.length);
|
||||
|
||||
// 如果在首页,只显示下拉结果,不自动跳转(除非手动点击搜索按钮)
|
||||
if (props.isHomePage) {
|
||||
showResults.value = true;
|
||||
isSearching.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 非首页的情况下,按照原来的逻辑处理
|
||||
if (searchData.length > 1) {
|
||||
showResults.value = false;
|
||||
navigateToSearchResults(searchText, searchData);
|
||||
return;
|
||||
} else if (searchData.length === 1) {
|
||||
// 只有一个结果,直接跳转到详情页
|
||||
showResults.value = false;
|
||||
goToResult(searchData[0]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
filteredResults.value = [];
|
||||
showResults.value = true;
|
||||
if (result && result.msg) {
|
||||
searchError.value = result.msg;
|
||||
} else {
|
||||
searchError.value = '搜索服务暂时不可用,请稍后重试';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索方法失败:', error);
|
||||
searchError.value = '搜索服务不可用,请稍后重试';
|
||||
}
|
||||
|
||||
isSearching.value = false;
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error);
|
||||
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
searchError.value = '搜索请求超时,请稍后重试';
|
||||
} else {
|
||||
searchError.value = '搜索服务暂时不可用,请稍后重试';
|
||||
}
|
||||
|
||||
isSearching.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到搜索结果列表页
|
||||
function navigateToSearchResults(keyword: string, results: SearchResult[]) {
|
||||
storeSearchResultsAndNavigate(keyword, results);
|
||||
}
|
||||
|
||||
// 搜索按钮点击
|
||||
function search() {
|
||||
// 直接执行搜索并跳转到结果页面
|
||||
if (searchQuery.value && searchQuery.value.trim() !== '') {
|
||||
// 执行搜索 - 对于搜索按钮点击,我们不需要再次执行搜索,因为onInput或onFocus已经触发了搜索
|
||||
// 但为了确保有最新结果,还是执行一次
|
||||
isSearching.value = true;
|
||||
|
||||
// 缓存当前的搜索关键词
|
||||
const keyword = searchQuery.value.trim();
|
||||
|
||||
// 如果已经有搜索结果,直接使用
|
||||
if (filteredResults.value.length > 0) {
|
||||
// 将关键词和结果缓存到sessionStorage
|
||||
storeSearchResultsAndNavigate(keyword, filteredResults.value);
|
||||
} else {
|
||||
// 如果没有结果,执行搜索后再跳转,设置titleOnly为true,仅搜索标题
|
||||
searchContent(keyword, undefined, true).then(result => {
|
||||
if (result && result.code === 200) {
|
||||
let searchData = [];
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
searchData = result.data;
|
||||
} else if (result.rows && Array.isArray(result.rows)) {
|
||||
searchData = result.rows;
|
||||
} else if (result.data && !Array.isArray(result.data)) {
|
||||
searchData = [result.data];
|
||||
} else if (result.rows && !Array.isArray(result.rows)) {
|
||||
searchData = [result.rows];
|
||||
}
|
||||
|
||||
// 如果只有一个结果,直接跳转到详情页
|
||||
if (searchData.length === 1) {
|
||||
goToResult(searchData[0]);
|
||||
} else {
|
||||
// 否则,跳转到搜索结果页面
|
||||
storeSearchResultsAndNavigate(keyword, searchData);
|
||||
}
|
||||
} else {
|
||||
// 即使搜索失败,也跳转到搜索结果页面
|
||||
router.push(`/search-results?keyword=${encodeURIComponent(keyword)}`);
|
||||
}
|
||||
|
||||
isSearching.value = false;
|
||||
}).catch(error => {
|
||||
console.error('搜索失败:', error);
|
||||
// 即使搜索失败,也跳转到搜索结果页面
|
||||
router.push(`/search-results?keyword=${encodeURIComponent(keyword)}`);
|
||||
isSearching.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 存储搜索结果并导航到搜索结果页
|
||||
function storeSearchResultsAndNavigate(keyword: string, results: SearchResult[]) {
|
||||
// 按照pageType对结果进行分类
|
||||
const categorizedResults = {
|
||||
law: results.filter(item => item.pageType === 'law'),
|
||||
case: results.filter(item => item.pageType === 'case'),
|
||||
form: results.filter(item => item.pageType === 'form'),
|
||||
other: results.filter(item => !['law', 'case', 'form'].includes(item.pageType))
|
||||
};
|
||||
|
||||
// 将结果存储到sessionStorage中,以便在结果页面中获取
|
||||
sessionStorage.setItem('searchKeyword', keyword);
|
||||
sessionStorage.setItem('searchResults', JSON.stringify(results));
|
||||
sessionStorage.setItem('categorizedResults', JSON.stringify(categorizedResults));
|
||||
|
||||
// 跳转到搜索结果页面
|
||||
router.push(`/search-results?keyword=${encodeURIComponent(keyword)}`);
|
||||
}
|
||||
|
||||
// 导航到结果页面
|
||||
function goToResult(result: SearchResult) {
|
||||
let url = '/';
|
||||
|
||||
switch(result.pageType) {
|
||||
case 'law':
|
||||
url = `/show.html?Id=${result.formatId}`;
|
||||
break;
|
||||
case 'case':
|
||||
url = `/showcase.html?Id=${result.formatId}`;
|
||||
break;
|
||||
case 'form':
|
||||
url = `/table.html?Id=${result.formatId}`;
|
||||
break;
|
||||
}
|
||||
|
||||
searchQuery.value = '';
|
||||
showResults.value = false;
|
||||
searchError.value = '';
|
||||
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
// 输入框内容变化时搜索
|
||||
function onInput() {
|
||||
// 不再在输入时执行搜索,只在点击搜索按钮时搜索
|
||||
if (searchQuery.value.length === 0) {
|
||||
filteredResults.value = [];
|
||||
showResults.value = false;
|
||||
searchError.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 处理输入框聚焦
|
||||
function onFocus() {
|
||||
// 不再在聚焦时执行搜索,只在点击搜索按钮时搜索
|
||||
if (props.isHomePage && props.isMobile) {
|
||||
setTimeout(getSearchPosition, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理输入框失焦
|
||||
function onBlur() {
|
||||
setTimeout(() => {
|
||||
showResults.value = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 处理按键事件
|
||||
function onKeyUp(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
// 在移动端,手动强制DOM元素失去焦点
|
||||
function blurInput() {
|
||||
const inputElement = document.querySelector('.search-input') as HTMLInputElement | null;
|
||||
if (inputElement) {
|
||||
inputElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取搜索框位置,用于正确定位搜索结果
|
||||
function getSearchPosition() {
|
||||
const searchBar = document.querySelector('.search-bar');
|
||||
if (searchBar) {
|
||||
const rect = searchBar.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
|
||||
// 重试搜索
|
||||
function retrySearch() {
|
||||
searchError.value = '';
|
||||
performSearch();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const resultsContainer = document.querySelector('.search-results');
|
||||
if (resultsContainer) {
|
||||
resultsContainer.addEventListener('touchend', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container" :class="{ 'mobile': isMobile, 'home-search': isHomePage }">
|
||||
<form class="search-bar" @submit.prevent="search">
|
||||
<input
|
||||
type="search"
|
||||
v-model="searchQuery"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keyup="onKeyUp"
|
||||
placeholder="搜索法律规定、典型案例或表单..."
|
||||
class="search-input"
|
||||
inputmode="search"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="search-button"
|
||||
@click="search"
|
||||
:disabled="isSearching"
|
||||
>
|
||||
<span class="search-icon" v-if="!isSearching"></span>
|
||||
<span class="loading-icon" v-else></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div v-if="showResults && filteredResults.length > 0" class="search-results"
|
||||
:class="{ 'mobile-results': isMobile, 'home-page-results': isHomePage }">
|
||||
<div class="search-results-content">
|
||||
<ul>
|
||||
<li
|
||||
v-for="result in filteredResults"
|
||||
:key="result.id"
|
||||
@mousedown.prevent="goToResult(result)"
|
||||
@touchend.prevent="goToResult(result); blurInput()"
|
||||
class="search-result-item"
|
||||
>
|
||||
<div class="result-content">
|
||||
<span class="result-title">{{ result.title }}</span>
|
||||
<span class="result-type">
|
||||
{{ result.pageType === 'law' ? '法律规定' :
|
||||
result.pageType === 'case' ? '典型案例' : '表单下载' }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="showResults && searchQuery && (filteredResults.length === 0 || searchError)" class="search-results"
|
||||
:class="{ 'mobile-results': isMobile, 'home-page-results': isHomePage }">
|
||||
<div class="search-results-content">
|
||||
<div class="no-results">
|
||||
<p>{{ searchError || '未找到相关内容' }}</p>
|
||||
<button v-if="searchError" @click="retrySearch" class="retry-button">重试</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e1e4e8;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-bar:focus-within {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0.8rem 1rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text);
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
background-color: var(--color-primary);
|
||||
border: none;
|
||||
padding: 0.8rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.3s;
|
||||
min-width: 45px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.search-icon::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 2px solid white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.search-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
background-color: white;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border-radius: 0 0 4px 4px;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-top: none;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mobile-results {
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
max-height: 250px;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 4px;
|
||||
z-index: 1200;
|
||||
}
|
||||
|
||||
.search-results ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 0.8rem 1rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.search-result-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.search-result-item:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.result-type {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
margin-top: 0.5rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.retry-button:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.search-container.mobile {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.search-container.mobile .search-input {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-container.mobile .search-bar {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #d0d0d0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-container.mobile .search-button {
|
||||
padding: 0.9rem;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.search-container.mobile .search-input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-container {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d0d0d0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: 1rem;
|
||||
padding: 0.9rem 1rem;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 0.9rem;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 1rem 1.2rem;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.result-type {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.search-result-item + .search-result-item {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border-radius: 0 0 6px 6px;
|
||||
border: 1px solid #d0d0d0;
|
||||
border-top: none;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
z-index: 1500;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.search-results-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
/* 首页搜索特有样式 */
|
||||
.search-container.home-search {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-container.home-search .search-bar {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.search-container.home-search .search-input {
|
||||
font-size: 1.1rem;
|
||||
padding: 0.9rem 1rem;
|
||||
}
|
||||
|
||||
.search-container.home-search .search-button {
|
||||
padding: 0.9rem;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.search-container.home-search .search-input::placeholder {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.search-container.home-search .search-results.home-page-results {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
|
||||
top: calc(100% + 2px);
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
/* 添加加载中图标的样式 */
|
||||
.loading-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: white;
|
||||
animation: spin 1s infinite linear;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
353
src/components/TheNavbar.vue
Normal file
353
src/components/TheNavbar.vue
Normal file
@ -0,0 +1,353 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
|
||||
const isMenuOpen = ref(false);
|
||||
const isScrolled = ref(false);
|
||||
const window = globalThis.window;
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
if (isMenuOpen.value) {
|
||||
// 只在移动端锁定滚动
|
||||
if (window.innerWidth <= 768) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
if (isMenuOpen.value) {
|
||||
isMenuOpen.value = false;
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 导航栏滚动效果
|
||||
const handleScroll = () => {
|
||||
isScrolled.value = window.scrollY > 60;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
window.addEventListener('resize', closeMenu);
|
||||
// 确保PC端可以正常滚动
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.removeEventListener('resize', closeMenu);
|
||||
document.body.style.overflow = ''; // 清除可能的滚动锁定
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar" :class="{ 'scrolled': isScrolled }">
|
||||
<div class="container">
|
||||
<div class="navbar-content">
|
||||
<router-link to="/" class="navbar-brand" @click="closeMenu">
|
||||
淮安市司法局百问百答
|
||||
</router-link>
|
||||
|
||||
<div class="search-wrapper" :class="{ 'mobile-search': window.innerWidth <= 768 }">
|
||||
<SearchBar :isMobile="window.innerWidth <= 768" />
|
||||
</div>
|
||||
|
||||
<button class="menu-toggle" @click="toggleMenu" aria-label="菜单">
|
||||
<span class="bar" :class="{ 'open': isMenuOpen }"></span>
|
||||
<span class="bar" :class="{ 'open': isMenuOpen }"></span>
|
||||
<span class="bar" :class="{ 'open': isMenuOpen }"></span>
|
||||
</button>
|
||||
|
||||
<div class="navbar-collapse" :class="{ 'show': isMenuOpen }">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<router-link to="/" class="nav-link" @click="closeMenu">首页</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link to="/hasfjlaw" class="nav-link" @click="closeMenu">法律规定</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link to="/hasfjcase" class="nav-link" @click="closeMenu">典型案例</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link to="/hasfjform" class="nav-link" @click="closeMenu">表单下载</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.navbar {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
z-index: 1001;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-brand::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
background-color: var(--color-primary);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.navbar-brand:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.navbar-brand:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
margin: 0 1rem;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.mobile-search-wrapper {
|
||||
width: 100%;
|
||||
max-width: 90%;
|
||||
margin: 0 auto 1.5rem;
|
||||
padding: 0 1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-left: 2.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
font-size: 1.1rem;
|
||||
transition: all 0.3s;
|
||||
display: block;
|
||||
padding: 0.8rem 0;
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-link:hover, .router-link-active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: var(--color-primary);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.nav-link:hover::after,
|
||||
.router-link-active::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
margin: 5px auto;
|
||||
background-color: var(--color-text);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.close-menu-btn {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 1.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
z-index: 1002;
|
||||
display: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 1.8rem;
|
||||
display: block;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar-brand {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
padding: 1rem 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: block;
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
order: 2;
|
||||
max-width: 100%;
|
||||
flex: 1 0 100%;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.mobile-search {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.bar.open:nth-child(1) {
|
||||
transform: translateY(8px) rotate(45deg);
|
||||
}
|
||||
|
||||
.bar.open:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.bar.open:nth-child(3) {
|
||||
transform: translateY(-8px) rotate(-45deg);
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
z-index: 1000;
|
||||
padding-top: 5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.navbar-collapse.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 1.2rem;
|
||||
font-size: 1.3rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.nav-item:last-child .nav-link {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.navbar-content {
|
||||
padding: 0.8rem 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
94
src/components/TheWelcome.vue
Normal file
94
src/components/TheWelcome.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
|
||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
||||
and
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||
/
|
||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in
|
||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||
>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
||||
(our official Discord server), or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also follow the official
|
||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||
Bluesky account or the
|
||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
X account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
87
src/components/WelcomeItem.vue
Normal file
87
src/components/WelcomeItem.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
7
src/components/icons/IconCommunity.vue
Normal file
7
src/components/icons/IconCommunity.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/components/icons/IconDocumentation.vue
Normal file
7
src/components/icons/IconDocumentation.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/components/icons/IconEcosystem.vue
Normal file
7
src/components/icons/IconEcosystem.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/components/icons/IconSupport.vue
Normal file
7
src/components/icons/IconSupport.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
19
src/components/icons/IconTooling.vue
Normal file
19
src/components/icons/IconTooling.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
14
src/main.ts
Normal file
14
src/main.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
66
src/router/index.ts
Normal file
66
src/router/index.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import LawView from '../views/LawView.vue'
|
||||
import CaseView from '../views/CaseView.vue'
|
||||
import FormView from '../views/FormView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
// 法律规定页面 - 对应 /show.html?Id=xxx
|
||||
{
|
||||
path: '/show.html',
|
||||
name: 'law',
|
||||
component: LawView,
|
||||
},
|
||||
// 典型案例页面 - 对应 /showcase.html?Id=xxx
|
||||
{
|
||||
path: '/showcase.html',
|
||||
name: 'case',
|
||||
component: CaseView,
|
||||
},
|
||||
// 表单下载页面 - 对应 /table.html?Id=xxx
|
||||
{
|
||||
path: '/table.html',
|
||||
name: 'form',
|
||||
component: FormView,
|
||||
},
|
||||
// 法律规定列表页
|
||||
{
|
||||
path: '/hasfjlaw',
|
||||
name: 'lawList',
|
||||
component: () => import('../views/LawListView.vue'),
|
||||
},
|
||||
// 典型案例列表页
|
||||
{
|
||||
path: '/hasfjcase',
|
||||
name: 'caseList',
|
||||
component: () => import('../views/CaseListView.vue'),
|
||||
},
|
||||
// 表单下载列表页
|
||||
{
|
||||
path: '/hasfjform',
|
||||
name: 'formList',
|
||||
component: () => import('../views/FormListView.vue'),
|
||||
},
|
||||
// 二维码入口页面
|
||||
{
|
||||
path: '/qrcodes',
|
||||
name: 'qrcodeList',
|
||||
component: () => import('../views/QRCodesView.vue'),
|
||||
},
|
||||
// 搜索结果页面
|
||||
{
|
||||
path: '/search-results',
|
||||
name: 'searchResults',
|
||||
component: () => import('../views/SearchResultsView.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
1040
src/services/api.ts
Normal file
1040
src/services/api.ts
Normal file
File diff suppressed because it is too large
Load Diff
12
src/stores/counter.ts
Normal file
12
src/stores/counter.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
453
src/views/CaseListView.vue
Normal file
453
src/views/CaseListView.vue
Normal file
@ -0,0 +1,453 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, onActivated } from 'vue'
|
||||
import { getPageList } from '../services/api'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
content?: string;
|
||||
pageType?: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
updateTime?: string;
|
||||
status?: number;
|
||||
sortOrder?: number;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
keywords?: string;
|
||||
description?: string;
|
||||
attachmentUrl?: string;
|
||||
}
|
||||
|
||||
const caseList = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(12); // 每页显示12条数据
|
||||
|
||||
const fetchCases = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const result = await getPageList('case', currentPage.value, pageSize.value);
|
||||
|
||||
if (result.code === 200) {
|
||||
caseList.value = result.rows || [];
|
||||
total.value = result.total || caseList.value.length;
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (caseList.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchCases();
|
||||
}
|
||||
} else {
|
||||
error.value = result.msg || '获取数据失败';
|
||||
caseList.value = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
caseList.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchCases();
|
||||
});
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCases();
|
||||
});
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('典型案例列表页面被激活,重新获取数据');
|
||||
fetchCases();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="case-list">
|
||||
<TheNavbar />
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>典型案例</h1>
|
||||
<p class="subtitle">查看劳动纠纷的典型处理案例</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="cases-section">
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchCases" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="caseList.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有典型案例相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="case-grid">
|
||||
<div v-for="item in caseList" :key="item.formatId" class="case-card">
|
||||
<router-link :to="`/showcase.html?Id=${item.formatId}`" class="case-link">
|
||||
<div class="case-icon"></div>
|
||||
<div class="case-content">
|
||||
<h3 class="case-title">{{ item.title }}</h3>
|
||||
<div class="case-meta">
|
||||
<span class="case-date">{{ formatDate(item.createTime) }}</span>
|
||||
<!-- <span class="case-views">浏览: {{ item.viewCount }}</span> -->
|
||||
</div>
|
||||
<div class="case-footer">
|
||||
<span class="view-more">查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.case-list {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(135deg, #0072ff 0%, #00c6ff 100%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IGlkPSJwYXR0ZXJuLWJhY2tncm91bmQiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InRyYW5zcGFyZW50Ij48L3JlY3Q+PHBhdGggZD0iTSAwIDAgTCAxMCAwIiBzdHJva2U9IiNmZmZmZmYxMCIgc3Ryb2tlLXdpZHRoPSIxIj48L3BhdGg+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI3BhdHRlcm4pIj48L3JlY3Q+PC9zdmc+');
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-header .container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 700px;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cases-section {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px 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); }
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.7rem 1.8rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.25);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.case-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.case-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
border: 1px solid #f0f0f0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.case-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.case-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
padding: 1.2rem;
|
||||
}
|
||||
|
||||
.case-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: rgba(0, 123, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
margin-bottom: 1.2rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z'/%3E%3C/svg%3E");
|
||||
background-size: 24px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.case-card:hover .case-icon {
|
||||
background-color: var(--color-primary);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.case-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.case-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.case-meta {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.case-date, .case-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.case-date::before, .case-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.case-date::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.684 11.523v-2.3h2.261v-.61H4.684V6.801h2.464v-.61H4v5.332h.684zm3.296 0h.676V8.98c0-.554.227-1.007.953-1.007.125 0 .258.004.329.015v-.613a1.806 1.806 0 0 0-.254-.02c-.582 0-.891.32-1.012.567h-.02v-.504H7.98v4.105zm2.805-5.093c0 .238.192.425.43.425a.428.428 0 1 0 0-.855.426.426 0 0 0-.43.43zm.094 5.093h.672V7.418h-.672v4.105z'/%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.case-views::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z'/%3E%3Cpath d='M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.case-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.view-more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.view-more::after {
|
||||
content: '→';
|
||||
margin-left: 0.3rem;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.case-card:hover .view-more::after {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.case-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cases-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.case-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cases-section {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.case-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
449
src/views/CaseView.vue
Normal file
449
src/views/CaseView.vue
Normal file
@ -0,0 +1,449 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getPageDetail, updateViewCount } 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)
|
||||
|
||||
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('case', formatId);
|
||||
console.log('典型案例详情请求结果:', result);
|
||||
|
||||
if (result && result.code === 200 && result.data) {
|
||||
page.value = result.data;
|
||||
console.log('成功获取典型案例详情:', page.value);
|
||||
|
||||
// 更新浏览量
|
||||
try {
|
||||
const viewResult = await updateViewCount('case', 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 !== 'case') {
|
||||
console.error(`错误: 请求的是典型案例(case),但返回的是${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>';
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
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 formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="case-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="case-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="page.content"></div>
|
||||
|
||||
<div class="footer">
|
||||
<router-link to="/hasfjcase" class="btn-more">查看更多典型案例</router-link>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.case-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;
|
||||
}
|
||||
|
||||
.case-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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
.content .footer {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.content-card {
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content .body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
493
src/views/FormListView.vue
Normal file
493
src/views/FormListView.vue
Normal file
@ -0,0 +1,493 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, onActivated } from 'vue'
|
||||
import { getPageList } from '../services/api'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
multiAttachments?: string;
|
||||
attachmentUrl?: string;
|
||||
}
|
||||
|
||||
const formList = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(15);
|
||||
|
||||
const fetchForms = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const result = await getPageList('form', currentPage.value, pageSize.value);
|
||||
|
||||
if (result.code === 200) {
|
||||
formList.value = result.rows || [];
|
||||
total.value = result.total || formList.value.length;
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (formList.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchForms();
|
||||
}
|
||||
} else {
|
||||
error.value = result.msg || '获取数据失败';
|
||||
formList.value = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
formList.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchForms();
|
||||
});
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchForms();
|
||||
});
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('表单下载列表页面被激活,重新获取数据');
|
||||
fetchForms();
|
||||
});
|
||||
|
||||
// 判断是否有附件
|
||||
function hasAttachments(item: PageItem): boolean {
|
||||
// 首先检查是否有直接附件链接
|
||||
if (item.attachmentUrl) return true;
|
||||
|
||||
// 再检查多附件数据
|
||||
if (!item.multiAttachments) return false;
|
||||
|
||||
try {
|
||||
const attachments = JSON.parse(item.multiAttachments);
|
||||
return Array.isArray(attachments) && attachments.length > 0;
|
||||
} catch (e) {
|
||||
console.error('解析附件列表失败:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-list">
|
||||
<TheNavbar />
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>表单下载</h1>
|
||||
<p class="subtitle">下载各类表单模板,便捷办理相关业务</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="forms-section">
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchForms" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="formList.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有表单下载相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="form-grid">
|
||||
<div v-for="item in formList" :key="item.formatId" class="form-card">
|
||||
<router-link :to="`/table.html?Id=${item.formatId}`" class="form-link">
|
||||
<div class="form-icon" :class="{'has-download': hasAttachments(item)}"></div>
|
||||
<div class="form-content">
|
||||
<h3 class="form-title">{{ item.title }}</h3>
|
||||
<div class="form-meta">
|
||||
<span class="form-date">{{ formatDate(item.createTime) }}</span>
|
||||
<!-- <span class="form-views">浏览: {{ item.viewCount }}</span> -->
|
||||
<span v-if="hasAttachments(item)" class="form-download-tag">可下载</span>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<span class="view-more">查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-list {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(135deg, #0072ff 0%, #00c6ff 100%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IGlkPSJwYXR0ZXJuLWJhY2tncm91bmQiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InRyYW5zcGFyZW50Ij48L3JlY3Q+PHBhdGggZD0iTSAwIDAgTCAxMCAwIiBzdHJva2U9IiNmZmZmZmYxMCIgc3Ryb2tlLXdpZHRoPSIxIj48L3BhdGg+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI3BhdHRlcm4pIj48L3JlY3Q+PC9zdmc+');
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-header .container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 700px;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.forms-section {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px 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); }
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.7rem 1.8rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.25);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
border: 1px solid #f0f0f0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.form-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
padding: 1.2rem;
|
||||
}
|
||||
|
||||
.form-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: rgba(0, 123, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
margin-bottom: 1.2rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z'/%3E%3Cpath d='M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z'/%3E%3C/svg%3E");
|
||||
background-size: 24px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.form-icon.has-download {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-card:hover .form-icon {
|
||||
background-color: var(--color-primary);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z'/%3E%3Cpath d='M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-card:hover .form-icon.has-download {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.form-meta {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-date, .form-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-date::before, .form-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.form-date::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.684 11.523v-2.3h2.261v-.61H4.684V6.801h2.464v-.61H4v5.332h.684zm3.296 0h.676V8.98c0-.554.227-1.007.953-1.007.125 0 .258.004.329.015v-.613a1.806 1.806 0 0 0-.254-.02c-.582 0-.891.32-1.012.567h-.02v-.504H7.98v4.105zm2.805-5.093c0 .238.192.425.43.425a.428.428 0 1 0 0-.855.426.426 0 0 0-.43.43zm.094 5.093h.672V7.418h-.672v4.105z'/%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-views::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z'/%3E%3Cpath d='M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-download-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #198754;
|
||||
font-size: 0.85rem;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.form-download-tag::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.8;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23198754' viewBox='0 0 16 16'%3E%3Cpath d='M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.view-more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.view-more::after {
|
||||
content: '→';
|
||||
margin-left: 0.3rem;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.form-card:hover .view-more::after {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.forms-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.forms-section {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
858
src/views/FormView.vue
Normal file
858
src/views/FormView.vue
Normal file
@ -0,0 +1,858 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getPageDetail, updateViewCount } 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 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;
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
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}`;
|
||||
}
|
||||
|
||||
// 添加一个直接文件下载处理函数,处理点击下载事件
|
||||
function handleDownload(url: string, filename: string) {
|
||||
const fullUrl = getAttachmentUrl(url);
|
||||
|
||||
// 显示下载中状态
|
||||
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');
|
||||
});
|
||||
}
|
||||
|
||||
// 后台管理中的附件删除处理函数
|
||||
|
||||
// 1. 首先在FileUpload组件中添加更健壮的文件路径处理函数
|
||||
function normalizeFilePath(url:any) {
|
||||
if (!url) return '';
|
||||
|
||||
// 去除基础URL前缀
|
||||
let path = url;
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
if (path.startsWith(baseUrl)) {
|
||||
path = path.substring(baseUrl.length);
|
||||
}
|
||||
|
||||
// 规范化路径
|
||||
path = path.replace(/\\/g, '/');
|
||||
|
||||
// 处理/profile/前缀
|
||||
if (path.startsWith('/profile/')) {
|
||||
path = path.substring('/profile/'.length);
|
||||
}
|
||||
|
||||
// 确保没有多余的斜杠
|
||||
while (path.includes('//')) {
|
||||
path = path.replace('//', '/');
|
||||
}
|
||||
|
||||
// 去除开头的斜杠
|
||||
if (path.startsWith('/')) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// 2. 提取文件ID以便更精确地删除文件
|
||||
function extractFileId(url) {
|
||||
if (!url) return '';
|
||||
|
||||
// 尝试匹配文件格式 [年月日时分秒]_[随机字符串].[扩展名]
|
||||
const match = url.match(/(\d{8}|\d{14})_[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/);
|
||||
if (match && match[0]) {
|
||||
return match[0];
|
||||
}
|
||||
|
||||
// 如果无法提取特定格式,则返回文件名
|
||||
const fileName = url.split('/').pop();
|
||||
return fileName || '';
|
||||
}
|
||||
|
||||
// 3. 优化删除文件的函数,使用多种策略尝试删除文件
|
||||
function handleDelete(index) {
|
||||
if (index < 0 || index >= fileList.value.length) return;
|
||||
|
||||
const file = fileList.value[index];
|
||||
if (!file || !file.url) return;
|
||||
|
||||
proxy.$modal.confirm('确定要删除该文件吗?').then(() => {
|
||||
proxy.$modal.loading("删除文件中,请稍候...");
|
||||
|
||||
// 获取所有可能的文件路径表示形式
|
||||
const normalizedPath = normalizeFilePath(file.url);
|
||||
const fileId = extractFileId(file.url);
|
||||
|
||||
console.log("删除文件信息:");
|
||||
console.log("- 原始URL:", file.url);
|
||||
console.log("- 规范化路径:", normalizedPath);
|
||||
console.log("- 文件ID:", fileId);
|
||||
|
||||
// 尝试策略1: 使用文件ID删除
|
||||
deleteFileUsingId(fileId, index, () => {
|
||||
// 尝试策略2: 使用规范化路径删除
|
||||
deleteFileUsingPath(normalizedPath, index, () => {
|
||||
// 尝试策略3: 使用原始URL删除
|
||||
deleteFileUsingOriginalUrl(file.url, index, () => {
|
||||
// 所有策略都失败时,直接从前端移除
|
||||
console.warn("所有删除策略均失败,仅从前端移除文件");
|
||||
removeFileFromList(index);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 使用文件ID尝试删除
|
||||
function deleteFileUsingId(fileId, index, onFail) {
|
||||
console.log("尝试通过文件ID删除:", fileId);
|
||||
proxy.$http.post('/profile/delete-by-id', {
|
||||
fileId: fileId
|
||||
}).then(res => {
|
||||
if (res.data && res.data.code === 200) {
|
||||
removeFileFromList(index);
|
||||
proxy.$modal.msgSuccess("文件删除成功");
|
||||
} else {
|
||||
console.warn("通过ID删除失败:", res.data?.msg);
|
||||
onFail && onFail();
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("通过ID删除请求失败:", err);
|
||||
onFail && onFail();
|
||||
});
|
||||
}
|
||||
|
||||
// 5. 使用规范化路径尝试删除
|
||||
function deleteFileUsingPath(path, index, onFail) {
|
||||
console.log("尝试通过规范化路径删除:", path);
|
||||
proxy.$http.post('/common/deleteFile', {
|
||||
filePath: path
|
||||
}).then(res => {
|
||||
if (res.data && res.data.code === 200) {
|
||||
removeFileFromList(index);
|
||||
proxy.$modal.msgSuccess("文件删除成功");
|
||||
} else {
|
||||
console.warn("通过规范化路径删除失败:", res.data?.msg);
|
||||
onFail && onFail();
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("通过规范化路径删除请求失败:", err);
|
||||
onFail && onFail();
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 使用原始URL尝试删除
|
||||
function deleteFileUsingOriginalUrl(url, index, onFail) {
|
||||
console.log("尝试通过原始URL删除:", url);
|
||||
proxy.$http.get('/common/deleteFile', {
|
||||
params: { filePath: url }
|
||||
}).then(res => {
|
||||
if (res.data && res.data.code === 200) {
|
||||
removeFileFromList(index);
|
||||
proxy.$modal.msgSuccess("文件删除成功");
|
||||
} else {
|
||||
console.warn("通过原始URL删除失败:", res.data?.msg);
|
||||
onFail && onFail();
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("通过原始URL删除请求失败:", err);
|
||||
onFail && onFail();
|
||||
});
|
||||
}
|
||||
|
||||
// 7. 从前端列表移除文件
|
||||
function removeFileFromList(index) {
|
||||
fileList.value.splice(index, 1);
|
||||
emit("update:modelValue", listToString(fileList.value));
|
||||
proxy.$modal.closeLoading();
|
||||
}
|
||||
</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="page.content"></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>
|
||||
</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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.content-card {
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content .body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
800
src/views/HomeView.vue
Normal file
800
src/views/HomeView.vue
Normal file
@ -0,0 +1,800 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { getPageList, getBaseUrl } from '../services/api';
|
||||
import QRCodeDisplay from '../components/QRCodeDisplay.vue';
|
||||
import TheNavbar from '../components/TheNavbar.vue';
|
||||
import SearchBar from '../components/SearchBar.vue';
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
pageType: string;
|
||||
pageUrl: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
status: number;
|
||||
sortOrder: number;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
keywords?: string;
|
||||
description?: string;
|
||||
attachmentUrl?: string;
|
||||
}
|
||||
|
||||
// 移除默认数据,只使用后端提供的真实数据
|
||||
const lawList = ref<PageItem[]>([]);
|
||||
const caseList = ref<PageItem[]>([]);
|
||||
const formList = ref<PageItem[]>([]);
|
||||
const loading = ref({
|
||||
law: true,
|
||||
case: true,
|
||||
form: true
|
||||
});
|
||||
|
||||
// 根据不同类型构建不同的URL
|
||||
const buildQRCodeUrl = (id: string, type: string): string => {
|
||||
const baseUrl = getBaseUrl();
|
||||
let path = '';
|
||||
|
||||
if (type === 'law') {
|
||||
path = `/show.html?Id=${id}`;
|
||||
} else if (type === 'case') {
|
||||
path = `/showcase.html?Id=${id}`;
|
||||
} else if (type === 'form') {
|
||||
path = `/table.html?Id=${id}`;
|
||||
} else {
|
||||
path = `/show.html?Id=${id}`;
|
||||
}
|
||||
|
||||
return `${baseUrl}${path}`;
|
||||
};
|
||||
|
||||
// 预设的二维码链接
|
||||
const qrCodeLinks = {
|
||||
law: [
|
||||
{ id: '000001', title: '法律规定1', url: buildQRCodeUrl('000001', 'law') },
|
||||
{ id: '000002', title: '法律规定2', url: buildQRCodeUrl('000002', 'law') },
|
||||
{ id: '000003', title: '法律规定3', url: buildQRCodeUrl('000003', 'law') },
|
||||
],
|
||||
case: [
|
||||
{ id: '000001', title: '典型案例1', url: buildQRCodeUrl('000001', 'case') },
|
||||
{ id: '000002', title: '典型案例2', url: buildQRCodeUrl('000002', 'case') },
|
||||
{ id: '000003', title: '典型案例3', url: buildQRCodeUrl('000003', 'case') },
|
||||
],
|
||||
form: [
|
||||
{ id: '000001', title: '表单下载1', url: buildQRCodeUrl('000001', 'form') },
|
||||
{ id: '000002', title: '表单下载2', url: buildQRCodeUrl('000002', 'form') },
|
||||
{ id: '000003', title: '表单下载3', url: buildQRCodeUrl('000003', 'form') },
|
||||
]
|
||||
};
|
||||
|
||||
// 格式化日期,处理无效日期情况
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '--/--';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
// 检查日期是否有效
|
||||
if (isNaN(date.getTime())) {
|
||||
return '--/--';
|
||||
}
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`;
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '--/--';
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 获取法律规定列表
|
||||
try {
|
||||
console.log("开始请求法律规定列表...");
|
||||
const result = await getPageList('law', 1, 6);
|
||||
console.log("法律规定列表请求结果:", result);
|
||||
|
||||
// 详细检查返回数据
|
||||
if (!result) {
|
||||
console.error("法律规定请求结果为空");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("法律规定返回数据类型:", typeof result);
|
||||
console.log("法律规定返回数据结构:", Object.keys(result));
|
||||
|
||||
if (result && result.code === 200) {
|
||||
if (Array.isArray(result.rows) && result.rows.length > 0) {
|
||||
lawList.value = result.rows;
|
||||
console.log("成功获取法律规定数据:", lawList.value);
|
||||
} else {
|
||||
console.warn("法律规定数据为空或格式不正确:", result);
|
||||
console.warn("rows是否为数组:", Array.isArray(result.rows));
|
||||
console.warn("rows长度:", result.rows ? result.rows.length : 0);
|
||||
|
||||
// 尝试从其他字段获取数据
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
lawList.value = result.data;
|
||||
console.log("从data字段获取法律规定数据:", lawList.value);
|
||||
} else if (result.list && Array.isArray(result.list)) {
|
||||
lawList.value = result.list;
|
||||
console.log("从list字段获取法律规定数据:", lawList.value);
|
||||
} else if (Array.isArray(result)) {
|
||||
lawList.value = result;
|
||||
console.log("直接使用结果数组作为法律规定数据:", lawList.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("请求法律规定列表失败:", result?.msg || "未知错误");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取法律规定列表失败:', error);
|
||||
} finally {
|
||||
loading.value.law = false;
|
||||
}
|
||||
|
||||
// 获取典型案例列表
|
||||
try {
|
||||
console.log("开始请求典型案例列表...");
|
||||
const result = await getPageList('case', 1, 6);
|
||||
console.log("典型案例列表请求结果:", result);
|
||||
|
||||
// 详细检查返回数据
|
||||
if (!result) {
|
||||
console.error("典型案例请求结果为空");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("典型案例返回数据类型:", typeof result);
|
||||
console.log("典型案例返回数据结构:", Object.keys(result));
|
||||
|
||||
if (result && result.code === 200) {
|
||||
if (Array.isArray(result.rows) && result.rows.length > 0) {
|
||||
caseList.value = result.rows;
|
||||
console.log("成功获取典型案例数据:", caseList.value);
|
||||
} else {
|
||||
console.warn("典型案例数据为空或格式不正确:", result);
|
||||
console.warn("rows是否为数组:", Array.isArray(result.rows));
|
||||
console.warn("rows长度:", result.rows ? result.rows.length : 0);
|
||||
|
||||
// 尝试从其他字段获取数据
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
caseList.value = result.data;
|
||||
console.log("从data字段获取典型案例数据:", caseList.value);
|
||||
} else if (result.list && Array.isArray(result.list)) {
|
||||
caseList.value = result.list;
|
||||
console.log("从list字段获取典型案例数据:", caseList.value);
|
||||
} else if (Array.isArray(result)) {
|
||||
caseList.value = result;
|
||||
console.log("直接使用结果数组作为典型案例数据:", caseList.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("请求典型案例列表失败:", result?.msg || "未知错误");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取典型案例列表失败:', error);
|
||||
} finally {
|
||||
loading.value.case = false;
|
||||
}
|
||||
|
||||
// 获取表单下载列表
|
||||
try {
|
||||
console.log("开始请求表单下载列表...");
|
||||
const result = await getPageList('form', 1, 6);
|
||||
console.log("表单下载列表请求结果:", result);
|
||||
|
||||
// 详细检查返回数据
|
||||
if (!result) {
|
||||
console.error("表单下载请求结果为空");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("表单下载返回数据类型:", typeof result);
|
||||
console.log("表单下载返回数据结构:", Object.keys(result));
|
||||
|
||||
if (result && result.code === 200) {
|
||||
if (Array.isArray(result.rows) && result.rows.length > 0) {
|
||||
formList.value = result.rows;
|
||||
console.log("成功获取表单下载数据:", formList.value);
|
||||
} else {
|
||||
console.warn("表单下载数据为空或格式不正确:", result);
|
||||
console.warn("rows是否为数组:", Array.isArray(result.rows));
|
||||
console.warn("rows长度:", result.rows ? result.rows.length : 0);
|
||||
|
||||
// 尝试从其他字段获取数据
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
formList.value = result.data;
|
||||
console.log("从data字段获取表单下载数据:", formList.value);
|
||||
} else if (result.list && Array.isArray(result.list)) {
|
||||
formList.value = result.list;
|
||||
console.log("从list字段获取表单下载数据:", formList.value);
|
||||
} else if (Array.isArray(result)) {
|
||||
formList.value = result;
|
||||
console.log("直接使用结果数组作为表单下载数据:", formList.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("请求表单下载列表失败:", result?.msg || "未知错误");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取表单下载列表失败:', error);
|
||||
} finally {
|
||||
loading.value.form = false;
|
||||
}
|
||||
|
||||
// 更新二维码链接,使用实际数据
|
||||
try {
|
||||
if (lawList.value.length > 0) {
|
||||
qrCodeLinks.law = lawList.value.slice(0, 3).map(item => ({
|
||||
id: item.formatId,
|
||||
title: item.title,
|
||||
url: buildQRCodeUrl(item.formatId, 'law')
|
||||
}));
|
||||
}
|
||||
|
||||
if (caseList.value.length > 0) {
|
||||
qrCodeLinks.case = caseList.value.slice(0, 3).map(item => ({
|
||||
id: item.formatId,
|
||||
title: item.title,
|
||||
url: buildQRCodeUrl(item.formatId, 'case')
|
||||
}));
|
||||
}
|
||||
|
||||
if (formList.value.length > 0) {
|
||||
qrCodeLinks.form = formList.value.slice(0, 3).map(item => ({
|
||||
id: item.formatId,
|
||||
title: item.title,
|
||||
url: buildQRCodeUrl(item.formatId, 'form')
|
||||
}));
|
||||
}
|
||||
console.log("二维码链接已更新:", qrCodeLinks);
|
||||
} catch (error) {
|
||||
console.error('更新二维码链接失败:', error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<TheNavbar />
|
||||
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<h1>淮安市司法局百问百答</h1>
|
||||
<p class="subtitle">法律法规、典型案例、表单下载</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container main-container">
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="content-row">
|
||||
<!-- 法律规定区块 -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<h2><i class="card-icon law-icon"></i>法律规定</h2>
|
||||
<router-link to="/hasfjlaw" class="view-more">查看更多</router-link>
|
||||
</div>
|
||||
<div v-if="loading.law" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="lawList.length === 0" class="empty">暂无法律规定</div>
|
||||
<ul v-else class="content-list">
|
||||
<li v-for="item in lawList.slice(0, 6)" :key="item.formatId">
|
||||
<router-link :to="`/show.html?Id=${item.formatId}`" class="list-item-link">
|
||||
<span class="item-dot"></span>
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
<span class="date">{{ formatDate(item.createTime) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 典型案例区块 -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<h2><i class="card-icon case-icon"></i>典型案例</h2>
|
||||
<router-link to="/hasfjcase" class="view-more">查看更多</router-link>
|
||||
</div>
|
||||
<div v-if="loading.case" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="caseList.length === 0" class="empty">暂无典型案例</div>
|
||||
<ul v-else class="content-list">
|
||||
<li v-for="item in caseList.slice(0, 6)" :key="item.formatId">
|
||||
<router-link :to="`/showcase.html?Id=${item.formatId}`" class="list-item-link">
|
||||
<span class="item-dot"></span>
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
<span class="date">{{ formatDate(item.createTime) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 表单下载区块 -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<h2><i class="card-icon form-icon"></i>表单下载</h2>
|
||||
<router-link to="/hasfjform" class="view-more">查看更多</router-link>
|
||||
</div>
|
||||
<div v-if="loading.form" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="formList.length === 0" class="empty">暂无表单</div>
|
||||
<ul v-else class="content-list">
|
||||
<li v-for="item in formList.slice(0, 6)" :key="item.formatId">
|
||||
<router-link :to="`/table.html?Id=${item.formatId}`" class="list-item-link">
|
||||
<span class="item-dot"></span>
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
<span class="date">{{ formatDate(item.createTime) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div class="qrcode-section">
|
||||
<div class="section-header">
|
||||
<h2><i class="section-icon qrcode-icon"></i>二维码入口</h2>
|
||||
<router-link to="/qrcodes" class="view-more">查看更多</router-link>
|
||||
</div>
|
||||
<div class="qrcode-list">
|
||||
<!-- 法律规定二维码 -->
|
||||
<div class="qrcode-group">
|
||||
<h3>法律规定</h3>
|
||||
<div class="qrcode-container">
|
||||
<QRCodeDisplay
|
||||
v-for="item in qrCodeLinks.law"
|
||||
:key="item.id"
|
||||
:url="item.url"
|
||||
:title="item.title"
|
||||
:id="item.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 典型案例二维码 -->
|
||||
<div class="qrcode-group">
|
||||
<h3>典型案例</h3>
|
||||
<div class="qrcode-container">
|
||||
<QRCodeDisplay
|
||||
v-for="item in qrCodeLinks.case"
|
||||
:key="item.id"
|
||||
:url="item.url"
|
||||
:title="item.title"
|
||||
:id="item.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单下载二维码 -->
|
||||
<div class="qrcode-group">
|
||||
<h3>表单下载</h3>
|
||||
<div class="qrcode-container">
|
||||
<QRCodeDisplay
|
||||
v-for="item in qrCodeLinks.form"
|
||||
:key="item.id"
|
||||
:url="item.url"
|
||||
:title="item.title"
|
||||
:id="item.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 博越科技. 版权所有.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html, body {
|
||||
overflow-y: auto !important;
|
||||
height: auto !important;
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(135deg, #0072ff 0%, #00c6ff 100%);
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IGlkPSJwYXR0ZXJuLWJhY2tncm91bmQiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InRyYW5zcGFyZW50Ij48L3JlY3Q+PHBhdGggZD0iTSAwIDAgTCAxMCAwIiBzdHJva2U9IiNmZmZmZmYxMCIgc3Ryb2tlLXdpZHRoPSIxIj48L3BhdGg+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI3BhdHRlcm4pIj48L3JlY3Q+PC9zdmc+');
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header .container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1200px;
|
||||
padding: 0 2rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.6rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.9;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.content-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.law-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zM4.5 7.5a.5.5 0 0 1 0 1H3.5a.5.5 0 0 1 0-1h1zm2 0a.5.5 0 0 1 0 1h-1a.5.5 0 0 1 0-1h1zm1 0a.5.5 0 0 1 0 1V8h1v.5a.5.5 0 0 1 0 1V8h1v.5a.5.5 0 1 1 0 1V8a.5.5 0 0 1-.5-.5H9v.5a.5.5 0 0 1-1 0V8h-1v.5zM8 9a.5.5 0 0 1 0 1H4.5a.5.5 0 0 1 0-1zm2.5 0a.5.5 0 0 1 0 1h-1a.5.5 0 0 1 0-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.case-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1z'/%3E%3Cpath d='M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2m0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1M3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.section-icon, .qrcode-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.qrcode-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M2 2h2v2H2V2Z'/%3E%3Cpath d='M6 0v6H0V0h6ZM5 1H1v4h4V1ZM4 12H2v2h2v-2Z'/%3E%3Cpath d='M6 10v6H0v-6h6Zm-5 1v4h4v-4H1Zm11-9h2v2h-2V2Z'/%3E%3Cpath d='M10 0v6h6V0h-6Zm5 1v4h-4V1h4ZM8 1V0h1v2H8v2H7V1h1Zm0 5V4h1v2H8ZM6 8V7h1V6h1v2h1V7h5v1h-4v1H7V8H6Zm0 0v1H2V8H1v1H0V7h3v1h3Zm10 1h-1V7h1v2Zm-1 0h-1v2h2v-1h-1V9Zm-4 0h2v1h-1v1h-1V9Zm2 3v-1h-1v1h-1v1H9v1h3v-2h1Zm0 0h3v1h-2v1h-1v-2Zm-4-1v1h1v-2H7v1h2Z'/%3E%3Cpath d='M7 12h1v3h4v1H7v-4Zm9 2v2h-3v-1h2v-1h1Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.view-more {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: transform 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.view-more::after {
|
||||
content: '→';
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.view-more:hover {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.loading, .empty {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--color-text-light);
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 3px solid rgba(0, 123, 255, 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); }
|
||||
}
|
||||
|
||||
.content-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-list li {
|
||||
padding: 0.7rem 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.content-list li:hover {
|
||||
background-color: rgba(0, 123, 255, 0.05);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.content-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item-link {
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding-left: 15px;
|
||||
font-size: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-dot {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.list-item-link:hover {
|
||||
color: var(--color-primary);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.date {
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
margin: 0;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.8rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.qrcode-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.qrcode-group {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.qrcode-group h3 {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text);
|
||||
font-size: 1.4rem;
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.qrcode-group h3::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 20px;
|
||||
background-color: var(--color-primary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: auto;
|
||||
background-color: #2c3e50;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 移动端首页搜索框 */
|
||||
.mobile-home-search {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
@media (max-width: 992px) {
|
||||
.content-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qrcode-group {
|
||||
width: 100%;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card-header h2,
|
||||
.section-header h2 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.qrcode-group h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
460
src/views/LawListView.vue
Normal file
460
src/views/LawListView.vue
Normal file
@ -0,0 +1,460 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, onActivated } from 'vue'
|
||||
import { getPageList } from '../services/api'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
pageType: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
status: number;
|
||||
sortOrder: number;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
keywords?: string;
|
||||
description?: string;
|
||||
attachmentUrl?: string;
|
||||
}
|
||||
|
||||
interface PageData {
|
||||
code: number;
|
||||
rows: PageItem[];
|
||||
total: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
const laws = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(15);
|
||||
|
||||
const fetchLaws = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await getPageList('law', currentPage.value, pageSize.value);
|
||||
|
||||
if (response.code === 200) {
|
||||
laws.value = response.rows || [];
|
||||
total.value = response.total || laws.value.length;
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (laws.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchLaws();
|
||||
}
|
||||
} else {
|
||||
error.value = response.msg || '获取数据失败';
|
||||
laws.value = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取法律规定列表失败:', err);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
laws.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchLaws();
|
||||
});
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchLaws();
|
||||
});
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('法律规定列表页面被激活,重新获取数据');
|
||||
fetchLaws();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="law-list-container">
|
||||
<TheNavbar />
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>法律规定</h1>
|
||||
<p class="subtitle">查看并了解最新的法律法规内容</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="law-list">
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>获取数据失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchLaws" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="laws.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有法律规定相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="law-grid">
|
||||
<div v-for="law in laws" :key="law.id" class="law-card">
|
||||
<router-link :to="`/show.html?Id=${law.formatId}`" class="law-link">
|
||||
<div class="law-icon"></div>
|
||||
<div class="law-content">
|
||||
<h3 class="law-title">{{ law.title }}</h3>
|
||||
<div class="law-meta">
|
||||
<span class="law-date">{{ formatDate(law.createTime) }}</span>
|
||||
<!-- <span class="law-views">浏览: {{ law.viewCount }}</span> -->
|
||||
</div>
|
||||
<div class="law-footer">
|
||||
<span class="view-more">查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.law-list-container {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(135deg, #0072ff 0%, #00c6ff 100%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IGlkPSJwYXR0ZXJuLWJhY2tncm91bmQiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InRyYW5zcGFyZW50Ij48L3JlY3Q+PHBhdGggZD0iTSAwIDAgTCAxMCAwIiBzdHJva2U9IiNmZmZmZmYxMCIgc3Ryb2tlLXdpZHRoPSIxIj48L3BhdGg+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI3BhdHRlcm4pIj48L3JlY3Q+PC9zdmc+');
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-header .container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 700px;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.law-list {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px 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); }
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.7rem 1.8rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.25);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.law-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.law-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
border: 1px solid #f0f0f0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.law-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.law-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
padding: 1.2rem;
|
||||
}
|
||||
|
||||
.law-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: rgba(0, 123, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
margin-bottom: 1.2rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%230072ff' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z'/%3E%3C/svg%3E");
|
||||
background-size: 24px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.law-card:hover .law-icon {
|
||||
background-color: var(--color-primary);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.law-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.law-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.law-meta {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.law-date, .law-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.law-date::before, .law-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.law-date::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.684 11.523v-2.3h2.261v-.61H4.684V6.801h2.464v-.61H4v5.332h.684zm3.296 0h.676V8.98c0-.554.227-1.007.953-1.007.125 0 .258.004.329.015v-.613a1.806 1.806 0 0 0-.254-.02c-.582 0-.891.32-1.012.567h-.02v-.504H7.98v4.105zm2.805-5.093c0 .238.192.425.43.425a.428.428 0 1 0 0-.855.426.426 0 0 0-.43.43zm.094 5.093h.672V7.418h-.672v4.105z'/%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.law-views::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z'/%3E%3Cpath d='M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.law-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.view-more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.view-more::after {
|
||||
content: '→';
|
||||
margin-left: 0.3rem;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.law-card:hover .view-more::after {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.law-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.law-list {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.law-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.law-list {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.law-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
449
src/views/LawView.vue
Normal file
449
src/views/LawView.vue
Normal file
@ -0,0 +1,449 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getPageDetail, updateViewCount } 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)
|
||||
|
||||
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('law', formatId);
|
||||
console.log('法律规定详情请求结果:', result);
|
||||
|
||||
if (result && result.code === 200 && result.data) {
|
||||
page.value = result.data;
|
||||
console.log('成功获取法律规定详情:', page.value);
|
||||
|
||||
// 更新浏览量
|
||||
try {
|
||||
const viewResult = await updateViewCount('law', 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 !== 'law') {
|
||||
console.error(`错误: 请求的是法律规定(law),但返回的是${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>';
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
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 formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="law-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="law-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="page.content"></div>
|
||||
|
||||
<div class="footer">
|
||||
<router-link to="/hasfjlaw" class="btn-more">查看更多法律规定</router-link>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.law-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;
|
||||
}
|
||||
|
||||
.law-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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
.content .footer {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.content-card {
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content .body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
552
src/views/QRCodesView.vue
Normal file
552
src/views/QRCodesView.vue
Normal file
@ -0,0 +1,552 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import TheNavbar from '../components/TheNavbar.vue';
|
||||
import QRCodeDisplay from '../components/QRCodeDisplay.vue';
|
||||
import Pagination from '../components/Pagination.vue';
|
||||
import { getQRCodes, getBaseUrl } from '../services/api';
|
||||
|
||||
interface QRCodeItem {
|
||||
id: string;
|
||||
formatId: string;
|
||||
title: string;
|
||||
url: string;
|
||||
pageType: string;
|
||||
}
|
||||
|
||||
// 分页相关状态
|
||||
const currentType = ref('law'); // 当前显示的类型:law、case、form
|
||||
const pageSize = ref(6); // 每页显示的数量
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const total = ref(0); // 数据总数
|
||||
const qrCodeItems = ref<QRCodeItem[]>([]); // 二维码数据
|
||||
const loading = ref(false); // 加载状态
|
||||
const error = ref(''); // 错误信息
|
||||
|
||||
// 获取二维码数据
|
||||
const fetchQRCodes = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await getQRCodes(currentType.value, currentPage.value, pageSize.value);
|
||||
|
||||
if (response.code === 200) {
|
||||
// 处理响应数据
|
||||
const items = response.rows || [];
|
||||
|
||||
// 转换数据格式,构建完整URL
|
||||
qrCodeItems.value = items.map((item: any) => ({
|
||||
id: item.id || item.formatId,
|
||||
formatId: item.formatId,
|
||||
title: item.title,
|
||||
url: buildItemUrl(item.formatId, currentType.value),
|
||||
pageType: item.pageType || currentType.value
|
||||
}));
|
||||
|
||||
total.value = response.total || items.length;
|
||||
} else {
|
||||
error.value = response.msg || '获取数据失败';
|
||||
qrCodeItems.value = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取二维码数据失败:', err);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
qrCodeItems.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 构建不同类型的URL
|
||||
const buildItemUrl = (formatId: string, type: string): string => {
|
||||
// 根据不同类型构建不同的URL,使用动态获取的站点URL
|
||||
let path = '';
|
||||
if (type === 'law') {
|
||||
path = `/show.html?Id=${formatId}`;
|
||||
} else if (type === 'case') {
|
||||
path = `/showcase.html?Id=${formatId}`;
|
||||
} else if (type === 'form') {
|
||||
path = `/table.html?Id=${formatId}`;
|
||||
} else {
|
||||
path = `/show.html?Id=${formatId}`;
|
||||
}
|
||||
|
||||
// 组合完整URL
|
||||
const baseUrl = getBaseUrl();
|
||||
return `${baseUrl}${path}`;
|
||||
};
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
console.log(`页码切换到: 第${page}页, 每页${size}条`);
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
// 分页变化后重新获取数据 - 不需要显式调用,由watch监听处理
|
||||
};
|
||||
|
||||
// 切换类型
|
||||
const switchType = (type: string) => {
|
||||
if (type !== currentType.value) {
|
||||
currentType.value = type;
|
||||
currentPage.value = 1; // 切换类型时重置到第一页
|
||||
}
|
||||
};
|
||||
|
||||
// 监听页码或类型变化,重新获取数据
|
||||
watch([currentPage, pageSize, currentType], () => {
|
||||
fetchQRCodes();
|
||||
});
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
fetchQRCodes();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="qrcodes-container">
|
||||
<TheNavbar />
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>二维码入口</h1>
|
||||
<p class="subtitle">扫描二维码快速访问各类资源</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<!-- 类型切换按钮组 -->
|
||||
<div class="type-tabs">
|
||||
<button
|
||||
class="type-tab"
|
||||
:class="{ active: currentType === 'law' }"
|
||||
@click="switchType('law')"
|
||||
>
|
||||
<i class="tab-icon law-icon"></i>
|
||||
法律规定
|
||||
</button>
|
||||
<button
|
||||
class="type-tab"
|
||||
:class="{ active: currentType === 'case' }"
|
||||
@click="switchType('case')"
|
||||
>
|
||||
<i class="tab-icon case-icon"></i>
|
||||
典型案例
|
||||
</button>
|
||||
<button
|
||||
class="type-tab"
|
||||
:class="{ active: currentType === 'form' }"
|
||||
@click="switchType('form')"
|
||||
>
|
||||
<i class="tab-icon form-icon"></i>
|
||||
表单下载
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 二维码展示区域 -->
|
||||
<div class="qrcode-section">
|
||||
<div class="section-header">
|
||||
<h2>{{
|
||||
currentType === 'law' ? '法律规定' :
|
||||
currentType === 'case' ? '典型案例' : '表单下载'
|
||||
}}</h2>
|
||||
</div>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-else-if="error" class="error-container">
|
||||
<h3>获取数据失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchQRCodes" class="retry-button">
|
||||
<i class="retry-icon"></i>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<div v-else-if="qrCodeItems.length === 0" class="empty-container">
|
||||
<p>暂无{{ currentType === 'law' ? '法律规定' : currentType === 'case' ? '典型案例' : '表单下载' }}相关二维码</p>
|
||||
</div>
|
||||
|
||||
<!-- 数据展示 -->
|
||||
<template v-else>
|
||||
<div class="qrcode-grid">
|
||||
<QRCodeDisplay
|
||||
v-for="item in qrCodeItems"
|
||||
:key="item.id"
|
||||
:url="item.url"
|
||||
:title="item.title"
|
||||
:id="item.formatId"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[6, 12, 24, 36]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.qrcodes-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-background);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(135deg, #0072ff 0%, #00c6ff 100%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IGlkPSJwYXR0ZXJuLWJhY2tncm91bmQiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InRyYW5zcGFyZW50Ij48L3JlY3Q+PHBhdGggZD0iTSAwIDAgTCAxMCAwIiBzdHJva2U9IiNmZmZmZmYxMCIgc3Ryb2tlLXdpZHRoPSIxIj48L3BhdGg+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI3BhdHRlcm4pIj48L3JlY3Q+PC9zdmc+');
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-header .container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1200px;
|
||||
padding: 0 2rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 700px;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type-tabs {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
padding: 0.8rem 1.8rem;
|
||||
border: none;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: var(--color-primary);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 140px;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.type-tab::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
background-color: var(--color-primary);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.type-tab:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type-tab.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 114, 255, 0.4);
|
||||
transform: translateY(-2px);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.type-tab.active::after {
|
||||
width: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.type-tab:hover:not(.active) {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.type-tab:hover .tab-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.law-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15h9.286zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1zM.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8H.8z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.case-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.form-icon {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1H5z'/%3E%3Cpath d='M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.type-tab.active .law-icon,
|
||||
.type-tab.active .case-icon,
|
||||
.type-tab.active .form-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
margin-bottom: 3rem;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qrcode-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background: linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 1.8rem;
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-header h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 24px;
|
||||
background-color: var(--color-primary);
|
||||
margin-right: 12px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.qrcode-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem 0.5rem;
|
||||
}
|
||||
|
||||
.loading-container, .error-container, .empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px 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-container h3 {
|
||||
color: var(--color-danger);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-container p {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.7rem 1.8rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.retry-button:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.retry-button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.retry-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z'/%3E%3Cpath d='M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z'/%3E%3C/svg%3E");
|
||||
animation: spin 1s linear infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.retry-button:hover .retry-icon {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-container p {
|
||||
color: var(--color-text-light);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.qrcode-grid {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.type-tabs {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.qrcode-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
783
src/views/SearchResultsView.vue
Normal file
783
src/views/SearchResultsView.vue
Normal file
@ -0,0 +1,783 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watchEffect, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import SearchBar from '@/components/SearchBar.vue';
|
||||
|
||||
interface SearchResult {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
pageType: string;
|
||||
content?: string;
|
||||
description?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
interface CategorizedResults {
|
||||
law: SearchResult[];
|
||||
case: SearchResult[];
|
||||
form: SearchResult[];
|
||||
other: SearchResult[];
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const keyword = ref('');
|
||||
const searchResults = ref<SearchResult[]>([]);
|
||||
const categorizedResults = ref<CategorizedResults>({
|
||||
law: [],
|
||||
case: [],
|
||||
form: [],
|
||||
other: []
|
||||
});
|
||||
|
||||
const isLoading = ref(true);
|
||||
const errorMessage = ref('');
|
||||
|
||||
// 检查是否有结果
|
||||
const hasResults = computed(() => {
|
||||
return searchResults.value.length > 0;
|
||||
});
|
||||
|
||||
// 检查各类别是否有结果
|
||||
const hasLawResults = computed(() => categorizedResults.value.law.length > 0);
|
||||
const hasCaseResults = computed(() => categorizedResults.value.case.length > 0);
|
||||
const hasFormResults = computed(() => categorizedResults.value.form.length > 0);
|
||||
const hasOtherResults = computed(() => categorizedResults.value.other.length > 0);
|
||||
|
||||
// 获取结果总数
|
||||
const totalResults = computed(() => searchResults.value.length);
|
||||
|
||||
// 当前选中的分类
|
||||
const activeCategory = ref('all');
|
||||
|
||||
// 监听路由变化,从sessionStorage获取搜索结果
|
||||
watchEffect(() => {
|
||||
const queryKeyword = route.query.keyword as string;
|
||||
if (queryKeyword) {
|
||||
keyword.value = decodeURIComponent(queryKeyword);
|
||||
loadSearchResults();
|
||||
}
|
||||
});
|
||||
|
||||
// 加载搜索结果
|
||||
function loadSearchResults() {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
try {
|
||||
// 从sessionStorage获取搜索结果
|
||||
const storedKeyword = sessionStorage.getItem('searchKeyword');
|
||||
const storedResults = sessionStorage.getItem('searchResults');
|
||||
const storedCategorizedResults = sessionStorage.getItem('categorizedResults');
|
||||
|
||||
if (storedKeyword === keyword.value && storedResults) {
|
||||
// 使用存储的结果
|
||||
searchResults.value = JSON.parse(storedResults);
|
||||
|
||||
if (storedCategorizedResults) {
|
||||
categorizedResults.value = JSON.parse(storedCategorizedResults);
|
||||
} else {
|
||||
// 如果没有存储分类结果,手动分类
|
||||
categorizeResults();
|
||||
}
|
||||
|
||||
console.log('从sessionStorage加载搜索结果:', searchResults.value.length);
|
||||
} else {
|
||||
// 如果没有存储结果或关键词不匹配,重新搜索
|
||||
console.log('未找到存储的搜索结果或关键词不匹配,重新搜索:', keyword.value);
|
||||
performSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索结果失败:', error);
|
||||
errorMessage.value = '加载搜索结果失败';
|
||||
// 尝试重新搜索
|
||||
performSearch();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
async function performSearch() {
|
||||
if (!keyword.value || keyword.value.trim() === '') {
|
||||
errorMessage.value = '请输入搜索关键词';
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
try {
|
||||
console.log('执行搜索:', keyword.value);
|
||||
|
||||
// 导入searchContent函数
|
||||
const { searchContent } = await import('@/services/api');
|
||||
|
||||
// 调用搜索API,设置titleOnly为true,仅搜索标题
|
||||
const response = await searchContent(keyword.value, undefined, true);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
// 处理搜索结果
|
||||
const results = response.data || [];
|
||||
searchResults.value = Array.isArray(results) ? results : [];
|
||||
|
||||
// 分类结果
|
||||
categorizeResults();
|
||||
|
||||
// 存储到sessionStorage
|
||||
sessionStorage.setItem('searchKeyword', keyword.value);
|
||||
sessionStorage.setItem('searchResults', JSON.stringify(searchResults.value));
|
||||
sessionStorage.setItem('categorizedResults', JSON.stringify(categorizedResults.value));
|
||||
|
||||
console.log('搜索成功,结果数量:', searchResults.value.length);
|
||||
} else {
|
||||
console.error('搜索失败:', response);
|
||||
errorMessage.value = response?.msg || '搜索失败,请稍后重试';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索出错:', error);
|
||||
errorMessage.value = '搜索服务暂时不可用,请稍后重试';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 手动分类结果
|
||||
function categorizeResults() {
|
||||
categorizedResults.value = {
|
||||
law: searchResults.value.filter(item => item.pageType === 'law'),
|
||||
case: searchResults.value.filter(item => item.pageType === 'case'),
|
||||
form: searchResults.value.filter(item => item.pageType === 'form'),
|
||||
other: searchResults.value.filter(item => !['law', 'case', 'form'].includes(item.pageType))
|
||||
};
|
||||
}
|
||||
|
||||
// 设置活动分类
|
||||
function setActiveCategory(category: string) {
|
||||
activeCategory.value = category;
|
||||
|
||||
// 滚动到对应的区域
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(`${category}-section`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateString?: string): string {
|
||||
if (!dateString) return '';
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到详情页
|
||||
function goToDetail(item: SearchResult) {
|
||||
let url = '/';
|
||||
|
||||
switch(item.pageType) {
|
||||
case 'law':
|
||||
url = `/show.html?Id=${item.formatId}`;
|
||||
break;
|
||||
case 'case':
|
||||
url = `/showcase.html?Id=${item.formatId}`;
|
||||
break;
|
||||
case 'form':
|
||||
url = `/table.html?Id=${item.formatId}`;
|
||||
break;
|
||||
default:
|
||||
// 如果类型未知,尝试使用ID
|
||||
url = `/show.html?Id=${item.formatId || item.id}`;
|
||||
}
|
||||
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
// 监听滚动,高亮当前可见区域
|
||||
function highlightVisibleSections() {
|
||||
const sections = document.querySelectorAll('.result-section');
|
||||
const navItems = document.querySelectorAll('.category-list li');
|
||||
|
||||
if (sections.length === 0 || navItems.length === 0) return;
|
||||
|
||||
const viewportHeight = window.innerHeight;
|
||||
let closestSection: Element | null = null;
|
||||
let closestDistance = Infinity;
|
||||
|
||||
sections.forEach((section) => {
|
||||
const rect = section.getBoundingClientRect();
|
||||
const distance = Math.abs(rect.top);
|
||||
|
||||
if (distance < closestDistance && rect.top <= viewportHeight / 2) {
|
||||
closestDistance = distance;
|
||||
closestSection = section;
|
||||
}
|
||||
});
|
||||
|
||||
if (closestSection && closestSection.id) {
|
||||
const id = closestSection.id;
|
||||
const category = id.replace('-section', '');
|
||||
|
||||
navItems.forEach((item) => {
|
||||
const link = item.querySelector('a');
|
||||
if (link && link.getAttribute('href') === `#${id}`) {
|
||||
item.classList.add('active');
|
||||
} else {
|
||||
item.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 更新活动分类
|
||||
if (activeCategory.value !== category) {
|
||||
activeCategory.value = category;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchResults();
|
||||
|
||||
// 添加滚动监听
|
||||
window.addEventListener('scroll', highlightVisibleSections);
|
||||
});
|
||||
|
||||
// 组件卸载时移除滚动监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', highlightVisibleSections);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-results-page">
|
||||
<div class="search-results-container">
|
||||
<div class="search-header">
|
||||
<div class="header-content">
|
||||
<h1>搜索结果</h1>
|
||||
<div class="search-bar-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-content">
|
||||
<div class="search-sidebar">
|
||||
<div class="search-info">
|
||||
<template v-if="hasResults">
|
||||
<p>关键词 "<span class="keyword-highlight">{{ keyword }}</span>" 的搜索结果(共 <span class="result-count">{{ totalResults }}</span> 条)</p>
|
||||
</template>
|
||||
<template v-else-if="errorMessage">
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
</template>
|
||||
<template v-else-if="isLoading">
|
||||
<p>正在加载搜索结果...</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>未找到与 "<span class="keyword-highlight">{{ keyword }}</span>" 相关的内容</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="search-categories" v-if="hasResults">
|
||||
<h3>搜索结果分类</h3>
|
||||
<ul class="category-list">
|
||||
<li v-if="hasLawResults" :class="{ active: true }">
|
||||
<a href="#law-section">法律规定 ({{ categorizedResults.law.length }})</a>
|
||||
</li>
|
||||
<li v-if="hasCaseResults" :class="{ active: true }">
|
||||
<a href="#case-section">典型案例 ({{ categorizedResults.case.length }})</a>
|
||||
</li>
|
||||
<li v-if="hasFormResults" :class="{ active: true }">
|
||||
<a href="#form-section">表单下载 ({{ categorizedResults.form.length }})</a>
|
||||
</li>
|
||||
<li v-if="hasOtherResults" :class="{ active: true }">
|
||||
<a href="#other-section">其他内容 ({{ categorizedResults.other.length }})</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-main-content">
|
||||
<div v-if="isLoading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在加载搜索结果...</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="results-sections">
|
||||
<!-- 法律规定结果 -->
|
||||
<section v-if="hasLawResults" id="law-section" class="result-section law-section">
|
||||
<h2>法律规定 <span class="result-count">({{ categorizedResults.law.length }})</span></h2>
|
||||
<div class="result-list">
|
||||
<div v-for="item in categorizedResults.law" :key="item.id" class="result-item" @click="goToDetail(item)">
|
||||
<div class="result-content">
|
||||
<h3 class="result-title">{{ item.title }}</h3>
|
||||
<p v-if="item.description" class="result-description">{{ item.description }}</p>
|
||||
<div class="result-meta">
|
||||
<span v-if="item.createTime" class="result-date">{{ formatDate(item.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 典型案例结果 -->
|
||||
<section v-if="hasCaseResults" id="case-section" class="result-section case-section">
|
||||
<h2>典型案例 <span class="result-count">({{ categorizedResults.case.length }})</span></h2>
|
||||
<div class="result-list">
|
||||
<div v-for="item in categorizedResults.case" :key="item.id" class="result-item" @click="goToDetail(item)">
|
||||
<div class="result-content">
|
||||
<h3 class="result-title">{{ item.title }}</h3>
|
||||
<p v-if="item.description" class="result-description">{{ item.description }}</p>
|
||||
<div class="result-meta">
|
||||
<span v-if="item.createTime" class="result-date">{{ formatDate(item.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 表单下载结果 -->
|
||||
<section v-if="hasFormResults" id="form-section" class="result-section form-section">
|
||||
<h2>表单下载 <span class="result-count">({{ categorizedResults.form.length }})</span></h2>
|
||||
<div class="result-list">
|
||||
<div v-for="item in categorizedResults.form" :key="item.id" class="result-item" @click="goToDetail(item)">
|
||||
<div class="result-content">
|
||||
<h3 class="result-title">{{ item.title }}</h3>
|
||||
<p v-if="item.description" class="result-description">{{ item.description }}</p>
|
||||
<div class="result-meta">
|
||||
<span v-if="item.createTime" class="result-date">{{ formatDate(item.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 其他结果 -->
|
||||
<section v-if="hasOtherResults" id="other-section" class="result-section other-section">
|
||||
<h2>其他内容 <span class="result-count">({{ categorizedResults.other.length }})</span></h2>
|
||||
<div class="result-list">
|
||||
<div v-for="item in categorizedResults.other" :key="item.id" class="result-item" @click="goToDetail(item)">
|
||||
<div class="result-content">
|
||||
<h3 class="result-title">{{ item.title }}</h3>
|
||||
<p v-if="item.description" class="result-description">{{ item.description }}</p>
|
||||
<div class="result-meta">
|
||||
<span v-if="item.createTime" class="result-date">{{ formatDate(item.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<div v-if="!hasResults && !isLoading && !errorMessage" class="no-results">
|
||||
<p class="no-results-title">抱歉,未找到与 "<span class="keyword-highlight">{{ keyword }}</span>" 相关的内容</p>
|
||||
<div class="no-results-suggestions">
|
||||
<p>建议:</p>
|
||||
<ul>
|
||||
<li>请检查关键词拼写是否正确</li>
|
||||
<li>尝试使用其他关键词</li>
|
||||
<li>尝试使用更通用的关键词</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="try-again-section">
|
||||
<button class="try-again-button" @click="performSearch">重新搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-results-page {
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.search-results-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.search-header h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--color-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-bar-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.search-content {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.search-sidebar {
|
||||
width: 280px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-main-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-info {
|
||||
background-color: #fff;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 20px;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-categories {
|
||||
background-color: #fff;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.search-categories h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.category-list li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.category-list a {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.category-list li.active a {
|
||||
background-color: #f0f7ff;
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
border-left: 3px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.category-list a:hover {
|
||||
background-color: #f0f7ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.keyword-highlight {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-count {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d9534f;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 50px 0;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--color-primary);
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.results-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
background-color: #fff;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.result-section:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result-section h2 {
|
||||
background-color: #f8f8f8;
|
||||
padding: 15px 20px;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 17px;
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-description {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.result-meta {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.result-date {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 40px 30px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.no-results-title {
|
||||
font-size: 18px;
|
||||
color: #555;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-results-suggestions {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.no-results p {
|
||||
margin: 10px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-results ul {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin: 10px auto;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.no-results li {
|
||||
margin: 8px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.try-again-section {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.try-again-button {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 10px 20px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.try-again-button:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* 针对不同类型的特殊样式 */
|
||||
.law-section h2 {
|
||||
border-left: 4px solid #2c3e50;
|
||||
}
|
||||
|
||||
.case-section h2 {
|
||||
border-left: 4px solid #e74c3c;
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.other-section h2 {
|
||||
border-left: 4px solid #f39c12;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.search-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-sidebar {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-categories {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.category-list li {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-results-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.search-info {
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.search-categories {
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.result-section h2 {
|
||||
font-size: 16px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.no-results-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.category-list li {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.category-list a {
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
624
src/views/TableDetailView.vue
Normal file
624
src/views/TableDetailView.vue
Normal file
@ -0,0 +1,624 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getPageDetail, updateViewCount } from '../services/api';
|
||||
import TheNavbar from '../components/TheNavbar.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const pageData = ref<any>(null);
|
||||
const isLoading = ref(true);
|
||||
const loadingError = ref('');
|
||||
|
||||
// 计算附件列表
|
||||
const attachmentList = computed(() => {
|
||||
if (!pageData.value || !pageData.value.multiAttachments) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(pageData.value.multiAttachments);
|
||||
// 过滤无效的附件项
|
||||
return Array.isArray(parsed) ? parsed.filter(item => item && (item.url || item.name)) : [];
|
||||
} catch (e) {
|
||||
console.error('解析附件列表失败:', e);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const pageId = route.query.Id as string;
|
||||
|
||||
if (!pageId) {
|
||||
loadingError.value = '页面标识不存在';
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('开始获取表单详情,formatId:', pageId);
|
||||
const result = await getPageDetail('form', pageId);
|
||||
console.log('表单详情请求结果:', result);
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
pageData.value = result.data;
|
||||
|
||||
// 更新浏览量
|
||||
try {
|
||||
await updateViewCount('form', pageId);
|
||||
// 更新成功后增加本地显示的浏览量
|
||||
if (pageData.value.viewCount !== undefined) {
|
||||
pageData.value.viewCount = Number(pageData.value.viewCount) + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新浏览量失败:', e);
|
||||
}
|
||||
|
||||
// 处理可能的中文乱码问题
|
||||
if (pageData.value.title && /\\u|%/.test(pageData.value.title)) {
|
||||
try {
|
||||
pageData.value.title = decodeURIComponent(pageData.value.title);
|
||||
} catch (e) {
|
||||
console.error('标题解码失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理附件URL的乱码问题
|
||||
if (pageData.value.attachmentUrl && /\\u|%/.test(pageData.value.attachmentUrl)) {
|
||||
try {
|
||||
pageData.value.attachmentUrl = decodeURIComponent(pageData.value.attachmentUrl);
|
||||
} catch (e) {
|
||||
console.error('附件URL解码失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理多附件数据
|
||||
if (pageData.value.multiAttachments) {
|
||||
try {
|
||||
// 处理可能的多附件数据乱码
|
||||
let attachmentsStr = pageData.value.multiAttachments;
|
||||
if (/\\u|%/.test(attachmentsStr)) {
|
||||
try {
|
||||
attachmentsStr = decodeURIComponent(attachmentsStr);
|
||||
pageData.value.multiAttachments = attachmentsStr;
|
||||
} catch (e) {
|
||||
console.error('多附件数据解码失败:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('处理多附件数据失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
document.title = `${pageData.value.title} - 表单下载`;
|
||||
} else {
|
||||
loadingError.value = result.msg || '加载表单数据失败';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取表单数据错误:', error);
|
||||
loadingError.value = '获取表单数据时发生错误';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取附件完整URL
|
||||
function getAttachmentUrl(url: string): string {
|
||||
if (!url) return '#';
|
||||
// 如果已经是完整URL则直接返回
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
// 否则拼接API基础路径
|
||||
return `/hasfj${url.startsWith('/') ? url : '/' + url}`;
|
||||
}
|
||||
|
||||
// 下载单个附件
|
||||
function downloadAttachment(url: string, filename?: string) {
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
console.log('准备下载附件:', url);
|
||||
const fullUrl = getAttachmentUrl(url);
|
||||
console.log('完整下载URL:', fullUrl);
|
||||
|
||||
// 创建一个隐藏的a标签来触发下载
|
||||
const link = document.createElement('a');
|
||||
link.href = fullUrl;
|
||||
link.target = '_blank';
|
||||
link.download = filename || url.split('/').pop() || '下载文件';
|
||||
|
||||
// 添加到DOM,触发点击,然后移除
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (e) {
|
||||
console.error('下载附件失败:', e);
|
||||
alert('下载失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 从URL中获取文件名
|
||||
function getFileName(url: string): string {
|
||||
if (!url) return '未知文件';
|
||||
return url.split('/').pop() || '未知文件';
|
||||
}
|
||||
|
||||
// 获取附件图标类
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table-detail-container">
|
||||
<TheNavbar />
|
||||
|
||||
<div v-if="isLoading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="loadingError" class="error">
|
||||
<div class="container">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>{{ loadingError }}</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="pageData" class="content">
|
||||
<div class="container">
|
||||
<div class="content-card">
|
||||
<div class="header">
|
||||
<div class="form-category">表单下载</div>
|
||||
<h2>{{ pageData.title }}</h2>
|
||||
<div class="meta">
|
||||
<span><i class="icon-time"></i>{{ formatDate(pageData.createTime) }}</span>
|
||||
<span><i class="icon-eye"></i>浏览次数: {{ pageData.viewCount }}</span>
|
||||
<span v-if="pageData.author"><i class="icon-user"></i>作者: {{ pageData.author }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body" v-html="pageData.content"></div>
|
||||
|
||||
<!-- 单个附件下载 -->
|
||||
<div v-if="pageData.attachmentUrl" class="attachment-section">
|
||||
<button class="download-btn" @click="downloadAttachment(pageData.attachmentUrl)">
|
||||
<span class="download-icon"></span>
|
||||
下载表单
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 多附件下载列表 -->
|
||||
<div v-if="attachmentList.length > 0" class="attachments">
|
||||
<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>
|
||||
<button @click="downloadAttachment(item.url, item.name)" class="download-btn-small">
|
||||
下载
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="error">
|
||||
<div class="container">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>表单数据不存在</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.table-detail-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);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.attachment-section {
|
||||
margin: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.7rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background-color: #0069d9;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='7 10 12 15 17 10'/%3E%3Cline x1='12' y1='15' x2='12' y2='3'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-right: 12px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
flex: 1;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.download-btn-small {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
.download-btn-small:hover {
|
||||
background-color: #0069d9;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.content-card {
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content .body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
12
tsconfig.app.json
Normal file
12
tsconfig.app.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
124
vite.config.ts
Normal file
124
vite.config.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
// 自定义中间件,处理路由问题
|
||||
{
|
||||
name: 'handle-spa-fallback',
|
||||
configureServer(server) {
|
||||
// 返回中间件处理函数
|
||||
return () => {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
// 如果是前端路由路径,直接返回index.html
|
||||
if (req.url === '/hasfjlaw' || req.url === '/hasfjcase' || req.url === '/hasfjform' || req.url === '/qrcodes') {
|
||||
const indexHtml = fs.readFileSync(
|
||||
path.resolve(__dirname, 'index.html'),
|
||||
'utf-8'
|
||||
)
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.end(indexHtml)
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 80, // 指定端口为80
|
||||
host: '0.0.0.0', // 允许外部访问
|
||||
proxy: {
|
||||
// 配置跨域
|
||||
'/api': {
|
||||
// target: 'http://127.0.0.1:9799', // 后端服务地址
|
||||
target: 'http://222.184.49.22:9799', // 后端服务地址
|
||||
changeOrigin: true, // 支持跨域
|
||||
rewrite: (path) => path.replace(/^\/api/, ''), // 移除/api前缀
|
||||
configure: (proxy, options) => {
|
||||
// 添加代理错误处理
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('API代理错误:', err);
|
||||
|
||||
// 返回友好的错误响应
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// 添加司法局后台管理接口代理
|
||||
'/hasfj': {
|
||||
// target: 'http://127.0.0.1:9799', // 后端服务地址
|
||||
target: 'http://222.184.49.22:9799', // 司法局后端服务
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path, // 保持路径不变
|
||||
configure: (proxy, options) => {
|
||||
// 添加代理错误处理
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('后台管理接口代理错误:', err);
|
||||
|
||||
// 返回友好的错误响应
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// 添加文件下载代理,专门处理静态资源文件请求
|
||||
'/profile': {
|
||||
target: 'http://222.184.49.22:9799', // 后端静态资源服务地址
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path, // 保持路径不变
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('文件下载代理错误:', err);
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '文件下载失败,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user