boyuehasfj-vue3-html

This commit is contained in:
luoyu 2025-06-02 21:36:36 +08:00
commit 320265cc07
95 changed files with 14277 additions and 0 deletions

9
.editorconfig Normal file
View 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

BIN
.env Normal file

Binary file not shown.

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}

39
README.md Normal file
View 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
View 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;
}

View 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};

File diff suppressed because one or more lines are too long

View 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};

File diff suppressed because one or more lines are too long

View 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};

File diff suppressed because one or more lines are too long

View 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};

View 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}}

View 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};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View 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>

View 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>

View 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>

View 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>

View 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

Binary file not shown.

View 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};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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};

File diff suppressed because one or more lines are too long

View 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};

View 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};

View 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}}

View 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};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
boyuehasfj-html/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View 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>

View 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>

View 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>

View 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>

View 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>

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

22
eslint.config.ts Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

79
public/show-fallback.html Normal file
View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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
View 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;
}
}

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve 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>

View 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>

View 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>

View 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 {
// URLURL
const urlObj = new URL(url);
return url;
} catch (e) {
// URLURL
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>

View 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 {
// 使searchContenttitleOnlytrue
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() !== '') {
// - onInputonFocus
//
isSearching.value = true;
//
const keyword = searchQuery.value.trim();
// 使
if (filteredResults.value.length > 0) {
// sessionStorage
storeSearchResultsAndNavigate(keyword, filteredResults.value);
} else {
// titleOnlytrue
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>

View 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>

View 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>
Vues
<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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

12
src/stores/counter.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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'); // lawcaseform
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>

View 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');
// APItitleOnlytrue
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>

View 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
View 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
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json Normal file
View 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
View 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
}));
}
});
}
}
}
}
})