diff --git a/.env.example b/.env.example
index 01824b2..59a11b7 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,7 @@
# Tenant / headers
# - TenantId header will be sent on every request.
# - Authorization header is taken from client storage (AccessToken) and forwarded by the Nuxt proxy.
-NUXT_PUBLIC_TENANT_ID=10584
+NUXT_PUBLIC_TENANT_ID=10588
# Upstream APIs (required)
NUXT_PUBLIC_SERVER_API_BASE=https://server.websoft.top/api
diff --git a/app/app.vue b/app/app.vue
index 12685a0..1f83ab1 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -9,8 +9,13 @@
diff --git a/app/assets/css/tailwind.css b/app/assets/css/tailwind.css
index b5c61c9..28c377c 100644
--- a/app/assets/css/tailwind.css
+++ b/app/assets/css/tailwind.css
@@ -1,3 +1,62 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+@layer base {
+ :root {
+ --gov-blue: #1e6fb5;
+ --gov-teal: #2ba69a;
+
+ --text-primary: #333333;
+ --text-secondary: #4b5563;
+ --text-tertiary: #6b7280;
+
+ --bg-page: #f7f9fc;
+ --bg-surface: #ffffff;
+ --border-subtle: rgba(0, 0, 0, 0.08);
+ }
+
+ html {
+ color: var(--text-primary);
+ background: var(--bg-page);
+ font-size: 16px;
+ line-height: 1.6;
+ text-size-adjust: 100%;
+ font-family: "Source Han Sans SC", "Noto Sans SC", "Microsoft YaHei", "PingFang SC", system-ui,
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
+ "Apple Color Emoji", "Segoe UI Emoji";
+ }
+
+ body {
+ margin: 0;
+ color: var(--text-primary);
+ background: var(--bg-page);
+ }
+
+ a {
+ color: var(--gov-blue);
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+ }
+
+ a:hover {
+ color: #185d96;
+ }
+
+ :focus-visible {
+ outline: 2px solid var(--gov-teal);
+ outline-offset: 2px;
+ }
+
+ #main-content {
+ scroll-margin-top: 120px;
+ }
+
+ html[data-contrast='high'] {
+ --text-primary: #0f172a;
+ --text-secondary: #111827;
+ --text-tertiary: #111827;
+ --bg-page: #ffffff;
+ --border-subtle: rgba(0, 0, 0, 0.2);
+ }
+}
diff --git a/app/components/Breadcrumbs.vue b/app/components/Breadcrumbs.vue
new file mode 100644
index 0000000..647177e
--- /dev/null
+++ b/app/components/Breadcrumbs.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
diff --git a/app/components/Carousel.vue b/app/components/Carousel.vue
index 7a34254..873e665 100644
--- a/app/components/Carousel.vue
+++ b/app/components/Carousel.vue
@@ -24,6 +24,13 @@
aria-label="carousel-slide"
>
+
+
+
{{ it.title }}
+
{{ it.desc }}
+
{{ it.ctaLabel }}
+
+
+
+
+
{{ it.title }}
+
{{ it.desc }}
+
{{ it.ctaLabel }}
+
+
-
+
+
![]()
+
+
+
{{ it.title }}
+
{{ it.desc }}
+
{{ it.ctaLabel }}
+
+
+
@@ -71,6 +94,9 @@ type CarouselItem = {
src: string
href?: string
alt?: string
+ title?: string
+ desc?: string
+ ctaLabel?: string
}
const props = withDefaults(
@@ -123,7 +149,9 @@ function prev() {
background: #fff;
position: relative;
overflow: hidden;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
+ border-radius: 14px;
+ border: 1px solid var(--border-subtle);
+ box-shadow: 0 14px 30px rgba(15, 23, 42, 0.08);
}
.carousel {
@@ -137,6 +165,7 @@ function prev() {
.carousel-link {
display: block;
height: 100%;
+ position: relative;
}
.carousel-img {
@@ -146,6 +175,45 @@ function prev() {
display: block;
}
+.carousel-overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: flex-end;
+ padding: 18px;
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.08), rgba(15, 23, 42, 0.74));
+}
+
+.carousel-overlay-inner {
+ max-width: 860px;
+}
+
+.carousel-title {
+ color: rgba(255, 255, 255, 0.98);
+ font-size: 28px;
+ line-height: 1.25;
+ font-weight: 900;
+ letter-spacing: 0.02em;
+}
+
+.carousel-desc {
+ margin-top: 10px;
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 14px;
+ line-height: 1.8;
+}
+
+.carousel-cta {
+ display: inline-flex;
+ margin-top: 12px;
+ padding: 8px 12px;
+ border-radius: 10px;
+ background: rgba(30, 111, 181, 0.95);
+ color: rgba(255, 255, 255, 0.98);
+ font-weight: 700;
+ font-size: 13px;
+}
+
.carousel :deep(.slick-list),
.carousel :deep(.slick-track),
.carousel :deep(.slick-slide),
diff --git a/app/components/SectionStub.vue b/app/components/SectionStub.vue
new file mode 100644
index 0000000..8a48463
--- /dev/null
+++ b/app/components/SectionStub.vue
@@ -0,0 +1,46 @@
+
+
+
+ {{ title }}
+
+
+ {{ description }}
+
+
+
+
+
+
+ {{ it.label }}
+
+
+ {{ it.desc }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/SiteFooter.vue b/app/components/SiteFooter.vue
index 9cc608a..6ce477a 100644
--- a/app/components/SiteFooter.vue
+++ b/app/components/SiteFooter.vue
@@ -3,35 +3,54 @@
- 关注我们
-
-
-
- 小程序
-
- 获取最新动态
-
+
联系我们
+
+
咨询服务:服务简介
+
建议反馈:建言献策
+
联系方式:联系我们
快速入口
- 经营范围
- 联系我们
+ 政策要闻
+ 数据服务
+ 专家申请
+ 资料下载
+ 站内搜索
+ 站点地图
备案信息
- {{ icpText }}
+
+
{{ icpText }}
+
{{ policeText }}
+
{{ siteCodeText }}
+
+
+
© {{ year }} {{ siteName }}. All rights reserved.
+
+ 隐私政策
+ 使用条款
+ 免责声明
+
Powered by
String((siteInfo.value as any)?.data?.websiteName || '桂乐淘'))
+const siteName = computed(() => String((siteInfo.value as any)?.data?.websiteName || '广西决策咨询网'))
const { data: icpField } = useAsyncData('cms-website-field-icpNo', async () => {
try {
@@ -62,6 +81,22 @@ const { data: icpField } = useAsyncData('cms-website-field-icpNo', async () => {
}
})
+const { data: policeField } = useAsyncData('cms-website-field-policeNo', async () => {
+ try {
+ return await getCmsWebsiteFieldByCode('policeNo')
+ } catch {
+ return null
+ }
+})
+
+const { data: siteCodeField } = useAsyncData('cms-website-field-siteCode', async () => {
+ try {
+ return await getCmsWebsiteFieldByCode('siteCode')
+ } catch {
+ return null
+ }
+})
+
const icpNo = computed(() => {
const v = icpField.value?.value ?? icpField.value?.defaultValue
if (typeof v === 'string' && v.trim()) return v.trim()
@@ -69,13 +104,36 @@ const icpNo = computed(() => {
return typeof fallback === 'string' ? fallback.trim() : ''
})
-const icpText = computed(() => (icpNo.value ? `备案号:${icpNo.value}` : '备案号:'))
+const policeNo = computed(() => {
+ const v = policeField.value?.value ?? policeField.value?.defaultValue
+ if (typeof v === 'string' && v.trim()) return v.trim()
+ const fallback = (siteInfo.value as any)?.data?.policeNo
+ return typeof fallback === 'string' ? fallback.trim() : ''
+})
+
+const siteCode = computed(() => {
+ const v = siteCodeField.value?.value ?? siteCodeField.value?.defaultValue
+ if (typeof v === 'string' && v.trim()) return v.trim()
+ const fallback = (siteInfo.value as any)?.data?.siteCode
+ return typeof fallback === 'string' ? fallback.trim() : ''
+})
+
+const icpText = computed(() => (icpNo.value ? `桂 ICP 备:${icpNo.value}` : '桂 ICP 备:'))
+const policeText = computed(() => (policeNo.value ? `桂公网安备:${policeNo.value}` : '桂公网安备:'))
+const siteCodeText = computed(() => (siteCode.value ? `网站标识码:${siteCode.value}` : '网站标识码:'))
const year = new Date().getFullYear()
+
+const friendLinks = [
+ { name: '广西壮族自治区人民政府', url: 'https://www.gxzf.gov.cn' },
+ { name: '国务院发展研究中心', url: 'https://www.drc.gov.cn' },
+ { name: '中国社会科学院', url: 'https://www.cass.cn' },
+ { name: '广西社科联', url: 'http://www.gxskl.gov.cn' }
+]
diff --git a/app/components/SiteHeader.vue b/app/components/SiteHeader.vue
index c83f6bb..fb22dd1 100644
--- a/app/components/SiteHeader.vue
+++ b/app/components/SiteHeader.vue
@@ -1,50 +1,71 @@