feat(home): 重构首页界面并移除文章相关页面
- 添加公司信息配置文件,包含项目名称、地址、经营范围等 - 实现404页面路由,显示页面建设中提示和导航按钮 - 在首页集成公司信息展示,包括经营范围和资质信息 - 移除文章列表页、文章详情页、栏目页和单页内容相关功能 - 更新Ant Design主题配色为绿色主色调 - 简化首页布局,突出业务板块和服务导向设计 - 删除部署方案和开通流程等临时页面内容
This commit is contained in:
19
app/app.vue
19
app/app.vue
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
<a-config-provider :theme="theme">
|
||||
<NuxtLayout>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
const theme = {
|
||||
token: {
|
||||
// Ant Design Vue primary color -> green
|
||||
colorPrimary: '#16a34a'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
<a-col :xs="24" :md="8">
|
||||
<div class="text-base font-semibold text-white">快速入口</div>
|
||||
<div class="mt-4 grid gap-2 text-sm text-gray-400">
|
||||
<NuxtLink class="hover:text-white" to="/platform">平台能力</NuxtLink>
|
||||
<NuxtLink class="hover:text-white" to="/products">产品矩阵</NuxtLink>
|
||||
<NuxtLink class="hover:text-white" to="/market">模板/插件市场</NuxtLink>
|
||||
<NuxtLink class="hover:text-white" to="/deploy">部署方案</NuxtLink>
|
||||
<NuxtLink class="hover:text-white" to="/products">经营范围</NuxtLink>
|
||||
<NuxtLink class="hover:text-white" to="/contact">联系我们</NuxtLink>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
@@ -30,8 +28,21 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="mt-10 border-t border-white/10 pt-6 text-xs text-gray-500">
|
||||
© {{ year }} {{ siteName }}. All rights reserved.
|
||||
<div
|
||||
class="mt-10 flex flex-col gap-2 border-t border-white/10 pt-6 text-xs text-gray-500 md:flex-row md:items-center md:justify-between"
|
||||
>
|
||||
<div>© {{ year }} {{ siteName }}. All rights reserved.</div>
|
||||
<div class="tools flex items-center opacity-80 hover:opacity-90 text-gray-100 text-xs">
|
||||
Powered by
|
||||
<a
|
||||
rel="nofollow"
|
||||
href="https://site.websoft.top"
|
||||
target="_blank"
|
||||
class="text-white visited:text-white hover:text-gray-200 ml-1"
|
||||
>
|
||||
云·企业官网
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-footer>
|
||||
@@ -53,4 +64,3 @@ const year = new Date().getFullYear()
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -7,38 +7,9 @@
|
||||
</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<a-input-search
|
||||
v-model:value="keywords"
|
||||
class="topbar-search"
|
||||
placeholder="请输入关键字"
|
||||
:allow-clear="true"
|
||||
@search="onSearch"
|
||||
/>
|
||||
|
||||
<div class="hidden md:flex items-center gap-3">
|
||||
<template v-if="!isAuthed">
|
||||
<a-button size="small" type="primary" @click="navigateTo('/login')">登录</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-dropdown :trigger="['hover']" placement="bottomRight">
|
||||
<a-space>
|
||||
<a-avatar :src="userAvatar" :size="28">
|
||||
<template v-if="!userAvatar" #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<span class="topbar-user">{{ userName }}</span>
|
||||
</a-space>
|
||||
<template #overlay>
|
||||
<a-menu @click="onUserMenuClick">
|
||||
<a-menu-item key="console">管理中心</a-menu-item>
|
||||
<a-menu-item key="profile">个人资料</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout">退出登录</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<div class="hidden md:flex items-center gap-2">
|
||||
<a-button size="small" @click="navigateTo('/products')">经营范围</a-button>
|
||||
<a-button size="small" type="primary" @click="navigateTo('/contact')">联系我们</a-button>
|
||||
</div>
|
||||
|
||||
<a-button class="md:hidden" size="small" @click="open = true">菜单</a-button>
|
||||
@@ -137,30 +108,21 @@
|
||||
</a-menu>
|
||||
|
||||
<div class="mt-4">
|
||||
<a-button v-if="!isAuthed" block type="primary" @click="onNav('/login')">登录</a-button>
|
||||
<template v-else>
|
||||
<a-button block @click="goConsoleCenter">管理中心</a-button>
|
||||
<a-button block @click="goDeveloperCenter">开发者中心</a-button>
|
||||
<a-button block @click="onNav('/profile')">个人资料</a-button>
|
||||
<a-button block danger class="mt-2" @click="logout">退出登录</a-button>
|
||||
</template>
|
||||
<a-space direction="vertical" class="w-full">
|
||||
<a-button type="primary" block @click="onNav('/contact')">联系我们</a-button>
|
||||
<a-button block @click="onNav('/products')">经营范围</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mainNav } from '@/config/nav'
|
||||
import { getUserInfo } from '@/api/layout'
|
||||
import type { User } from '@/api/system/user/model'
|
||||
import type { CmsNavigation } from '@/api/cms/cmsNavigation/model'
|
||||
import { getToken, removeToken } from '@/utils/token-util'
|
||||
import { clearAuthz, hasRole, setAuthzFromUser } from '@/utils/permission'
|
||||
import { UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { COMPANY } from '@/config/company'
|
||||
|
||||
const route = useRoute()
|
||||
const open = ref(false)
|
||||
const keywords = ref('')
|
||||
|
||||
type HeaderNavItem = {
|
||||
key: string
|
||||
@@ -218,10 +180,8 @@ const siteName = computed(() => {
|
||||
})
|
||||
|
||||
const showBrandbar = computed(() => {
|
||||
const p = route.path || '/'
|
||||
if (p === '/') return true
|
||||
// 文章列表、单页详情、文章详情都显示 brandbar
|
||||
return p === '/articles' || p.startsWith('/article/') || p.startsWith('/page/') || p.startsWith('/item/')
|
||||
// Corporate site: keep brand bar consistent across pages.
|
||||
return true
|
||||
})
|
||||
|
||||
const logoUrl = computed(() => {
|
||||
@@ -241,10 +201,10 @@ const siteSlogan = computed(() => {
|
||||
pickString(data?.setting, 'slogan') ||
|
||||
pickString(data?.setting, 'subtitle') ||
|
||||
pickString(data?.config, 'slogan')
|
||||
return slogan || 'XINGYUSI BANKRUPTCY TRANSACTION SERVICE PLATFORM'
|
||||
return slogan || `${COMPANY.projectName} · 品质服务与合规经营`
|
||||
})
|
||||
|
||||
const missionText = computed(() => '致力于企业纾困和破产事务服务')
|
||||
const missionText = computed(() => '生物基材料研发 · 技术服务 · 食品与农产品流通')
|
||||
const valuesText = computed(() => '真诚 · 奉献 · 规范 · 聚力')
|
||||
|
||||
function normalizePath(path: unknown) {
|
||||
@@ -290,6 +250,7 @@ function normalizeNavTree(list: CmsNavigation[]): HeaderNavItem[] {
|
||||
const navItems = computed<HeaderNavItem[]>(() => {
|
||||
const apiNavs = siteData.value?.topNavs
|
||||
if (Array.isArray(apiNavs) && apiNavs.length) {
|
||||
// Prefer navigation from getShopInfo (CMS-managed).
|
||||
return normalizeNavTree(apiNavs as CmsNavigation[])
|
||||
}
|
||||
// Fallback when CMS has not configured topNavs.
|
||||
@@ -313,28 +274,6 @@ function onNavClick(item: HeaderNavItem) {
|
||||
if (item.to) navigateTo(item.to)
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
if (!keywords.value.trim()) return
|
||||
navigateTo({ path: '/articles', query: { keywords: keywords.value.trim() } })
|
||||
}
|
||||
|
||||
const token = ref('')
|
||||
const user = ref<User | null>(null)
|
||||
const isAuthed = computed(() => !!token.value)
|
||||
const userName = computed(() => String(user.value?.nickname || user.value?.username || '已登录'))
|
||||
const userAvatar = computed(() => {
|
||||
const candidate =
|
||||
user.value?.avatarUrl ||
|
||||
user.value?.avatar ||
|
||||
user.value?.merchantAvatar ||
|
||||
user.value?.logo ||
|
||||
''
|
||||
if (typeof candidate !== 'string') return ''
|
||||
const normalized = candidate.trim()
|
||||
if (!normalized || normalized === 'null' || normalized === 'undefined') return ''
|
||||
return normalized
|
||||
})
|
||||
|
||||
function onNav(to: string) {
|
||||
open.value = false
|
||||
navigateTo(to)
|
||||
@@ -346,69 +285,6 @@ const todayText = computed(() => {
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}年${pad(d.getMonth() + 1)}月${pad(d.getDate())}日 星期${week}`
|
||||
})
|
||||
|
||||
async function refreshAuth() {
|
||||
token.value = getToken()
|
||||
if (!token.value) {
|
||||
user.value = null
|
||||
clearAuthz()
|
||||
return
|
||||
}
|
||||
try {
|
||||
user.value = await getUserInfo()
|
||||
setAuthzFromUser(user.value)
|
||||
} catch {
|
||||
// token may be expired; keep authed UI but without profile info
|
||||
clearAuthz()
|
||||
}
|
||||
}
|
||||
|
||||
function goConsoleCenter() {
|
||||
if (!isAuthed.value) return navigateTo('/login')
|
||||
open.value = false
|
||||
navigateTo('/console')
|
||||
}
|
||||
|
||||
function goDeveloperCenter() {
|
||||
if (!isAuthed.value) return navigateTo('/login')
|
||||
open.value = false
|
||||
if (!hasRole('developer')) return message.error('您还不是开发者')
|
||||
navigateTo('/developer')
|
||||
}
|
||||
|
||||
function logout() {
|
||||
removeToken()
|
||||
try {
|
||||
localStorage.removeItem('TenantId')
|
||||
localStorage.removeItem('UserId')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
clearAuthz()
|
||||
user.value = null
|
||||
token.value = ''
|
||||
open.value = false
|
||||
navigateTo('/')
|
||||
}
|
||||
|
||||
function onUserMenuClick(info: { key: string }) {
|
||||
if (info.key === 'console') return goConsoleCenter()
|
||||
if (info.key === 'developer') return goDeveloperCenter()
|
||||
if (info.key === 'profile') return navigateTo('/profile')
|
||||
if (info.key === 'logout') return logout()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshAuth()
|
||||
|
||||
window.addEventListener('auth-token-changed', refreshAuth)
|
||||
window.addEventListener('storage', refreshAuth)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('auth-token-changed', refreshAuth)
|
||||
window.removeEventListener('storage', refreshAuth)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -434,14 +310,6 @@ onUnmounted(() => {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.topbar-search {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.topbar-user {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.brandbar {
|
||||
background:
|
||||
linear-gradient(0deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.88)),
|
||||
@@ -468,7 +336,7 @@ onUnmounted(() => {
|
||||
font-size: 28px;
|
||||
line-height: 1.1;
|
||||
font-weight: 800;
|
||||
color: #b91c1c;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.brand-sub {
|
||||
@@ -491,7 +359,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #c30000;
|
||||
background: #16a34a;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
11
app/config/company.ts
Normal file
11
app/config/company.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const COMPANY = {
|
||||
projectName: '桂乐淘',
|
||||
address: '南宁市江南区国凯大道东13号神冠胶原智库项目加工厂房',
|
||||
scope: {
|
||||
general:
|
||||
'生物基材料技术研发;技术服务、技术开发、技术咨询、技术交流、技术转让、技术推广;食品销售(仅销售预包装食品);保健食品(预包装)销售;鲜肉零售;新鲜水果批发;特殊医学用途配方食品销售;水产品零售;鲜肉批发;鲜蛋零售;食品互联网销售(仅销售预包装食品);食用农产品零售;新鲜水果零售;新鲜蔬菜零售;鲜蛋批发(除依法须经批准的项目外,凭营业执照依法自主开展经营活动)。',
|
||||
licensed:
|
||||
'食品销售;酒类经营;食品互联网销售;食品经营管理(依法须经批准的项目,经相关部门批准后方可开展经营活动,具体经营项目以相关部门批准文件或许可证件为准)。'
|
||||
},
|
||||
tags: ['生物基材料', '技术服务', '食品/农产品流通', '合规经营']
|
||||
} as const
|
||||
@@ -6,10 +6,6 @@ export type NavItem = {
|
||||
|
||||
export const mainNav: NavItem[] = [
|
||||
{key: 'home', label: '首页', to: '/'},
|
||||
{key: 'products', label: '产品矩阵', to: '/products'},
|
||||
{key: 'platform', label: '平台能力', to: '/platform'},
|
||||
{key: 'market', label: '模板/插件市场', to: '/market'},
|
||||
{key: 'deploy', label: '部署方案', to: '/deploy'},
|
||||
{key: 'flow', label: '开通流程', to: '/flow'},
|
||||
{key: 'products', label: '经营范围', to: '/products'},
|
||||
{key: 'contact', label: '联系我们', to: '/contact'}
|
||||
]
|
||||
|
||||
33
app/pages/[...slug].vue
Normal file
33
app/pages/[...slug].vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-md px-4 py-16">
|
||||
<a-result status="404" title="页面建设中" sub-title="该页面暂未开放,建议返回首页或联系我们。">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="navigateTo('/')">返回首页</a-button>
|
||||
<a-button @click="navigateTo('/products')">经营范围</a-button>
|
||||
<a-button @click="navigateTo('/contact')">联系我们</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
|
||||
<a-card class="mt-6" size="small">
|
||||
<div class="text-sm text-gray-500">
|
||||
当前路径:<span class="font-mono">{{ path }}</span>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
const route = useRoute()
|
||||
const path = computed(() => route.fullPath || '/')
|
||||
|
||||
usePageSeo({
|
||||
title: '页面未开放',
|
||||
description: '该页面暂未开放,可返回首页或进入联系我们/经营范围。',
|
||||
path: route.path
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
<template>
|
||||
<main class="min-h-screen bg-gray-50 px-4 py-8">
|
||||
<div class="mx-auto max-w-screen-lg space-y-6">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<NuxtLink to="/">首页</NuxtLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>栏目</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>{{ navTitle }}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
|
||||
<a-card class="shadow-sm">
|
||||
<template #title>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xl font-semibold text-gray-900">{{ navTitle }}</div>
|
||||
<div v-if="navDescription" class="text-sm text-gray-500">{{ navDescription }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-image
|
||||
v-if="navBanner"
|
||||
:src="navBanner"
|
||||
:preview="false"
|
||||
class="mb-5 w-full"
|
||||
/>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<a-input
|
||||
v-model:value="keywords"
|
||||
placeholder="搜索本栏目文章"
|
||||
class="w-72"
|
||||
allow-clear
|
||||
@press-enter="doSearch"
|
||||
/>
|
||||
<a-button type="primary" :loading="pending" @click="doSearch">搜索</a-button>
|
||||
<a-button :disabled="pending" @click="refresh">刷新</a-button>
|
||||
</div>
|
||||
|
||||
<a-alert
|
||||
v-if="loadError"
|
||||
class="mt-4"
|
||||
show-icon
|
||||
type="error"
|
||||
:message="String(loadError)"
|
||||
/>
|
||||
|
||||
<a-list
|
||||
class="mt-4"
|
||||
:data-source="list"
|
||||
:loading="pending"
|
||||
item-layout="vertical"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<NuxtLink
|
||||
v-if="item?.articleId"
|
||||
class="text-blue-600 hover:underline"
|
||||
:to="`/item/${item.articleId}`"
|
||||
>
|
||||
{{ item.title }}
|
||||
</NuxtLink>
|
||||
<span v-else>{{ item?.title }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500">
|
||||
<span v-if="item?.createTime">发布时间:{{ item.createTime }}</span>
|
||||
<span v-if="item?.author">作者:{{ item.author }}</span>
|
||||
<span v-if="item?.source">来源:{{ item.source }}</span>
|
||||
<span v-if="typeof item?.actualViews === 'number'">阅读:{{ item.actualViews }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<div :class="item?.image ? 'col-span-12 md:col-span-9' : 'col-span-12'">
|
||||
<div v-if="item?.overview" class="text-gray-700">
|
||||
{{ item.overview }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item?.image" class="col-span-12 md:col-span-3">
|
||||
<a-image :src="item.image" :preview="false" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
|
||||
<div class="mt-4 flex items-center justify-end">
|
||||
<a-pagination
|
||||
:current="page"
|
||||
:page-size="limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
:page-size-options="['10', '20', '50', '100']"
|
||||
@change="onPageChange"
|
||||
@show-size-change="onPageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { pageCmsArticle } from '@/api/cms/cmsArticle'
|
||||
import { getCmsNavigation } from '@/api/cms/cmsNavigation'
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const navigationId = computed(() => {
|
||||
const raw = route.params.id
|
||||
const val = Array.isArray(raw) ? raw[0] : raw
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : NaN
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const limit = ref(10)
|
||||
const keywords = ref('')
|
||||
|
||||
watch(
|
||||
() => route.query.keywords,
|
||||
(val) => {
|
||||
const next = Array.isArray(val) ? val[0] : val
|
||||
if (typeof next === 'string') keywords.value = next.trim()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const {
|
||||
data,
|
||||
pending,
|
||||
error: loadError,
|
||||
refresh
|
||||
} = useAsyncData(
|
||||
() => `cms-navigation-article-page-${navigationId.value}-${page.value}-${limit.value}-${keywords.value}`,
|
||||
async () => {
|
||||
if (!Number.isFinite(navigationId.value)) throw new Error('无效的栏目ID')
|
||||
|
||||
const [nav, articles] = await Promise.all([
|
||||
getCmsNavigation(navigationId.value),
|
||||
pageCmsArticle({
|
||||
page: page.value,
|
||||
limit: limit.value,
|
||||
keywords: keywords.value || undefined,
|
||||
status: 0,
|
||||
// Some backends use categoryId, some use navigationId; send both.
|
||||
categoryId: navigationId.value,
|
||||
navigationId: navigationId.value
|
||||
})
|
||||
])
|
||||
|
||||
return { nav, articles }
|
||||
},
|
||||
{ watch: [navigationId] }
|
||||
)
|
||||
|
||||
const navTitle = computed(() => data.value?.nav?.title?.trim() || `栏目 ${navigationId.value}`)
|
||||
const navDescription = computed(() => data.value?.nav?.comments?.trim() || '')
|
||||
const navBanner = computed(() => (typeof data.value?.nav?.banner === 'string' ? data.value?.nav?.banner.trim() : ''))
|
||||
|
||||
const list = computed(() => data.value?.articles?.list ?? [])
|
||||
const total = computed(() => data.value?.articles?.count ?? 0)
|
||||
|
||||
function doSearch() {
|
||||
page.value = 1
|
||||
refresh()
|
||||
}
|
||||
|
||||
function onPageChange(nextPage: number) {
|
||||
page.value = nextPage
|
||||
refresh()
|
||||
}
|
||||
|
||||
function onPageSizeChange(_current: number, nextSize: number) {
|
||||
limit.value = nextSize
|
||||
page.value = 1
|
||||
refresh()
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (!Number.isFinite(navigationId.value)) return
|
||||
usePageSeo({
|
||||
title: navTitle.value,
|
||||
description: navDescription.value || navTitle.value,
|
||||
path: route.fullPath
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
<template>
|
||||
<main class="min-h-screen bg-gray-50 p-8">
|
||||
<div class="mx-auto max-w-5xl space-y-6">
|
||||
<a-card title="文章列表 (pageCmsArticle)" class="shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<a-input-password
|
||||
v-model:value="token"
|
||||
placeholder="Authorization (AccessToken)"
|
||||
class="w-96"
|
||||
/>
|
||||
<a-button :disabled="pending" @click="applyToken">设置Token</a-button>
|
||||
<a-button :disabled="pending" danger @click="clearToken">清除Token</a-button>
|
||||
<a-input
|
||||
v-model:value="keywords"
|
||||
placeholder="关键词 keywords"
|
||||
class="w-72"
|
||||
@press-enter="doSearch"
|
||||
/>
|
||||
<a-button type="primary" :loading="pending" @click="doSearch">查询</a-button>
|
||||
<a-button :disabled="pending" @click="refresh">刷新</a-button>
|
||||
<div class="text-sm text-gray-500">
|
||||
TenantId: {{ tenantId }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-alert
|
||||
v-if="error"
|
||||
class="mt-4"
|
||||
show-icon
|
||||
type="error"
|
||||
:message="String(error)"
|
||||
/>
|
||||
|
||||
<a-table
|
||||
class="mt-4"
|
||||
:data-source="list"
|
||||
:loading="pending"
|
||||
:pagination="false"
|
||||
row-key="articleId"
|
||||
size="middle"
|
||||
>
|
||||
<a-table-column title="ID" data-index="articleId" width="90" />
|
||||
<a-table-column title="标题" data-index="title">
|
||||
<template #default="{ record }">
|
||||
<NuxtLink
|
||||
v-if="record?.articleId"
|
||||
class="text-blue-600 hover:underline"
|
||||
:to="`/item/${record.articleId}`"
|
||||
>
|
||||
{{ record.title }}
|
||||
</NuxtLink>
|
||||
<span v-else>{{ record.title }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="编号" data-index="code" width="220" />
|
||||
<a-table-column title="栏目" data-index="categoryName" width="160" />
|
||||
<a-table-column title="创建时间" data-index="createTime" width="180" />
|
||||
<a-table-column title="操作" key="action" width="120">
|
||||
<template #default="{ record }">
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
:disabled="!record?.articleId"
|
||||
@click="record?.articleId && navigateTo(`/item/${record.articleId}`)"
|
||||
>
|
||||
查看
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
|
||||
<div class="mt-4 flex items-center justify-end">
|
||||
<a-pagination
|
||||
:current="page"
|
||||
:page-size="limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
:page-size-options="['10', '20', '50', '100']"
|
||||
@change="onPageChange"
|
||||
@show-size-change="onPageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { pageCmsArticle } from '@/api/cms/cmsArticle/index'
|
||||
import { getToken, removeToken, setToken } from '@/utils/token-util'
|
||||
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
const tenantId = computed(() => String(config.public.tenantId))
|
||||
|
||||
const page = ref(1)
|
||||
const limit = ref(10)
|
||||
const keywords = ref('')
|
||||
const token = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
token.value = getToken()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.query.keywords,
|
||||
(val) => {
|
||||
const next = Array.isArray(val) ? val[0] : val
|
||||
if (typeof next === 'string' && next.trim() && next.trim() !== keywords.value.trim()) {
|
||||
keywords.value = next.trim()
|
||||
page.value = 1
|
||||
refresh()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const { data, pending, error, refresh } = useAsyncData(
|
||||
'cms-article-page',
|
||||
() =>
|
||||
pageCmsArticle({
|
||||
page: page.value,
|
||||
limit: limit.value,
|
||||
keywords: keywords.value || undefined
|
||||
}),
|
||||
{ server: false }
|
||||
)
|
||||
|
||||
const list = computed(() => data.value?.list ?? [])
|
||||
const total = computed(() => data.value?.count ?? 0)
|
||||
|
||||
function applyToken() {
|
||||
setToken(token.value, true)
|
||||
refresh()
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
removeToken()
|
||||
token.value = ''
|
||||
refresh()
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
page.value = 1
|
||||
refresh()
|
||||
}
|
||||
|
||||
function onPageChange(nextPage: number) {
|
||||
page.value = nextPage
|
||||
refresh()
|
||||
}
|
||||
|
||||
function onPageSizeChange(_current: number, nextSize: number) {
|
||||
limit.value = nextSize
|
||||
page.value = 1
|
||||
refresh()
|
||||
}
|
||||
</script>
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">部署方案</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
支持 SaaS、私有化与混合部署。针对安全合规/数据隔离/运维可控等需求,提供交付物清单与验收流程。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-alert
|
||||
class="mb-6"
|
||||
type="info"
|
||||
show-icon
|
||||
message="支持私有化部署:提供部署文档、初始化脚本、升级/回滚建议与验收清单。"
|
||||
/>
|
||||
|
||||
<a-table :columns="columns" :data-source="rows" :pagination="false" row-key="key" />
|
||||
|
||||
<a-row class="mt-10" :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="私有化交付清单(示例)">
|
||||
<a-list :data-source="deliverables" size="small" bordered>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="上线与升级策略">
|
||||
<a-collapse>
|
||||
<a-collapse-panel key="1" header="升级方式">
|
||||
<div class="text-gray-600">
|
||||
支持版本升级与兼容性说明;建议灰度升级并保留回滚方案。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header="数据安全">
|
||||
<div class="text-gray-600">
|
||||
提供租户隔离、权限审计、数据备份/恢复建议;可对接客户既有安全体系。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="3" header="高可用建议">
|
||||
<div class="text-gray-600">
|
||||
提供多实例部署、负载均衡与健康检查建议;根据业务量规划资源与扩容策略。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="mt-10">
|
||||
<a-space>
|
||||
<a-button @click="navigateTo('/flow')">查看开通流程</a-button>
|
||||
<a-button type="primary" @click="navigateTo('/contact')">获取部署方案</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
usePageSeo({
|
||||
title: '部署方案 - SaaS / 私有化 / 混合部署',
|
||||
description: '支持 SaaS、私有化与混合部署,提供交付物清单与验收流程,满足安全合规与运维可控需求。',
|
||||
path: '/deploy'
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '对比项', dataIndex: 'name' },
|
||||
{ title: 'SaaS', dataIndex: 'saas' },
|
||||
{ title: '私有化', dataIndex: 'private' },
|
||||
{ title: '混合', dataIndex: 'hybrid' }
|
||||
]
|
||||
|
||||
const rows = [
|
||||
{ key: 'k1', name: '交付速度', saas: '最快', private: '中', hybrid: '中' },
|
||||
{ key: 'k2', name: '数据与合规', saas: '标准', private: '最高可控', hybrid: '可定制' },
|
||||
{ key: 'k3', name: '运维成本', saas: '最低', private: '客户自运维', hybrid: '可分担' },
|
||||
{ key: 'k4', name: '扩展能力', saas: '强', private: '强', hybrid: '强' },
|
||||
{ key: 'k5', name: '适用场景', saas: '快速试用/中小团队', private: '政企/强合规', hybrid: '集团/多系统' }
|
||||
]
|
||||
|
||||
const deliverables = [
|
||||
'部署包/镜像(示例)',
|
||||
'部署与运维文档(示例)',
|
||||
'初始化脚本与默认配置(示例)',
|
||||
'验收清单与检查项(示例)',
|
||||
'升级/回滚建议(示例)'
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">业务流程(开通链路)</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
面向“产品售卖 + 自动交付”的业务模型:客户选择产品并支付后,平台自动创建租户、初始化模块与数据并完成交付上线。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-row :gutter="[24, 24]">
|
||||
<a-col :xs="24" :lg="12">
|
||||
<a-card title="对外流程(客户视角)">
|
||||
<a-steps direction="vertical" :current="-1">
|
||||
<a-step title="选择产品/套餐" description="选择产品矩阵中的产品与套餐,按需加购模板/插件" />
|
||||
<a-step title="支付下单" description="支付成功后触发开通编排任务" />
|
||||
<a-step title="收到交付入口" description="获取管理员账号、访问地址与基础指引" />
|
||||
<a-step title="开始配置与运营" description="基于模板与默认配置快速上线,按需启用插件" />
|
||||
</a-steps>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :lg="12">
|
||||
<a-card title="平台内部自动化(系统视角)">
|
||||
<a-timeline>
|
||||
<a-timeline-item>订单校验:产品/套餐/加购项与授权生成</a-timeline-item>
|
||||
<a-timeline-item>创建租户:租户信息、域名/应用信息、管理员生成</a-timeline-item>
|
||||
<a-timeline-item>模块装配:按所购产品组合模块并初始化菜单/权限</a-timeline-item>
|
||||
<a-timeline-item>数据初始化:基础字典、默认配置、可选演示数据</a-timeline-item>
|
||||
<a-timeline-item>交付通知:发送入口、账号、初始化结果与下一步指引</a-timeline-item>
|
||||
</a-timeline>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-card class="mt-10" title="常见问题">
|
||||
<a-collapse>
|
||||
<a-collapse-panel key="1" header="如何支持私有化部署?">
|
||||
<div class="text-gray-600">
|
||||
私有化交付可提供部署包/镜像、部署文档、初始化脚本与验收清单;按客户环境对接域名、存储、日志与监控体系。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header="模板/插件购买后如何生效?">
|
||||
<div class="text-gray-600">
|
||||
支付成功后生成授权,并在租户侧自动装配:模板应用到站点与配置;插件完成安装/启用与默认配置写入。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="3" header="初始化哪些数据?">
|
||||
<div class="text-gray-600">
|
||||
可按产品套餐选择:基础字典、默认配置、菜单与权限、可选演示数据/示例内容;便于“开通即验收”。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
|
||||
<div class="mt-10">
|
||||
<a-space>
|
||||
<a-button @click="navigateTo('/products')">选择产品</a-button>
|
||||
<a-button type="primary" @click="navigateTo('/contact')">咨询开通与交付</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
usePageSeo({
|
||||
title: '开通流程 - 选品支付 / 创建租户 / 初始化模块与数据',
|
||||
description:
|
||||
'客户选择产品并支付后,平台自动创建租户、装配模块、初始化菜单权限与基础数据,并完成交付上线;支持 SaaS 与私有化交付。',
|
||||
path: '/flow'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,37 +1,95 @@
|
||||
<template>
|
||||
<main class="home">
|
||||
<section class="mx-auto max-w-screen-xl px-4 py-6">
|
||||
<section class="mx-auto max-w-screen-xl px-4 py-8">
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 lg:col-span-7">
|
||||
<div class="panel">
|
||||
<a-image :src="featured.image" :preview="false" class="featured-image" />
|
||||
<div class="p-4">
|
||||
<div class="featured-title">{{ featured.title }}</div>
|
||||
<div class="featured-meta">{{ featured.date }}</div>
|
||||
<div class="panel hero">
|
||||
<div class="hero-inner">
|
||||
<!-- <div class="hero-badge">OFFICIAL</div>-->
|
||||
<!-- <div class="hero-title">{{ COMPANY.projectName }}</div>-->
|
||||
<!-- <div class="hero-sub">生物基材料技术研发 · 技术服务 · 食品与农产品流通</div>-->
|
||||
<!-- <div class="mt-6 flex flex-wrap gap-3">-->
|
||||
<!-- <a-button type="primary" size="large" @click="navigateTo('/contact')">-->
|
||||
<!-- <template #icon><PhoneOutlined /></template>-->
|
||||
<!-- 合作咨询-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <a-button size="large" @click="scrollToCompany">-->
|
||||
<!-- <template #icon><IdcardOutlined /></template>-->
|
||||
<!-- 工商信息-->
|
||||
<!-- </a-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-6 flex flex-wrap gap-2">-->
|
||||
<!-- <a-tag v-for="t in COMPANY.tags" :key="t" color="green">{{ t }}</a-tag>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 lg:col-span-5">
|
||||
<div class="panel notice">
|
||||
<div class="notice-head">
|
||||
<div class="notice-title">
|
||||
<NotificationOutlined />
|
||||
公告
|
||||
</div>
|
||||
<a class="notice-more" href="#" @click.prevent>更多</a>
|
||||
<div class="panel quick">
|
||||
<div class="login-hero quick-hero">
|
||||
<div class="login-hero-title">桂乐淘 · 官方网站</div>
|
||||
<div class="login-hero-sub">QUALITY · SERVICE · COMPLIANCE</div>
|
||||
</div>
|
||||
<div class="notice-list">
|
||||
<a
|
||||
v-for="n in notices"
|
||||
:key="n.title"
|
||||
class="notice-item"
|
||||
href="#"
|
||||
@click.prevent
|
||||
>
|
||||
<div class="notice-item-title">{{ n.title }}</div>
|
||||
<div class="notice-item-desc">{{ n.desc }}</div>
|
||||
</a>
|
||||
|
||||
<div class="p-4">
|
||||
<a-space direction="vertical" class="w-full" size="middle">
|
||||
<a-button type="primary" block size="large" @click="navigateTo('/products')">
|
||||
<template #icon><FileTextOutlined /></template>
|
||||
经营范围与资质信息
|
||||
</a-button>
|
||||
<a-button block size="large" @click="navigateTo('/contact')">
|
||||
<template #icon><PhoneOutlined /></template>
|
||||
合作咨询 / 联系我们
|
||||
</a-button>
|
||||
<div class="flex items-start justify-between gap-3 text-sm text-gray-500">
|
||||
<span class="leading-6">地址:{{ COMPANY.address }}</span>
|
||||
<a href="#" class="text-green-600" @click.prevent="scrollToCompany">查看</a>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 lg:col-span-5 hidden">
|
||||
<div id="company" class="panel company">
|
||||
<div class="panel-head">
|
||||
<div class="panel-title">
|
||||
<IdcardOutlined />
|
||||
工商信息
|
||||
</div>
|
||||
<a class="panel-more" href="#" @click.prevent="navigateTo('/products')">详情</a>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<a-descriptions bordered size="small" :column="1">
|
||||
<a-descriptions-item label="项目名称">{{ COMPANY.projectName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册地址">{{ COMPANY.address }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider class="!my-4" />
|
||||
|
||||
<a-collapse ghost>
|
||||
<a-collapse-panel key="general" header="一般项目(经营范围)">
|
||||
<a-list size="small" :data-source="generalScopeItems">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item class="scope-item">{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="licensed" header="许可项目">
|
||||
<a-list size="small" :data-source="licensedScopeItems">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item class="scope-item">{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
<a-alert
|
||||
class="mt-4"
|
||||
type="info"
|
||||
show-icon
|
||||
message="经营范围展示以工商登记为准(涉及许可项目请以许可文件为准)。"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,28 +100,28 @@
|
||||
<div class="panel">
|
||||
<div class="section-pill">
|
||||
<span class="pill-left">
|
||||
<FileTextOutlined />
|
||||
申报指南
|
||||
<AppstoreOutlined />
|
||||
业务板块
|
||||
</span>
|
||||
<span class="pill-right">Declaration Guide</span>
|
||||
<span class="pill-right">SERVICES</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<a-card
|
||||
v-for="g in guides"
|
||||
:key="g.title"
|
||||
class="guide-card"
|
||||
v-for="s in services"
|
||||
:key="s.title"
|
||||
class="guide-card service-card"
|
||||
:bordered="true"
|
||||
hoverable
|
||||
@click="navigateTo(g.to)"
|
||||
@click="navigateTo('/products')"
|
||||
>
|
||||
<a-space>
|
||||
<a-avatar :size="44" class="guide-icon">
|
||||
<component :is="g.icon" />
|
||||
<a-avatar :size="44" class="guide-icon service-icon">
|
||||
<component :is="s.icon" />
|
||||
</a-avatar>
|
||||
<div>
|
||||
<div class="guide-title">{{ g.title }}</div>
|
||||
<div class="guide-desc">{{ g.desc }}</div>
|
||||
<div class="guide-title">{{ s.title }}</div>
|
||||
<div class="guide-desc">{{ s.desc }}</div>
|
||||
</div>
|
||||
</a-space>
|
||||
</a-card>
|
||||
@@ -71,44 +129,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 lg:col-span-5">
|
||||
<div class="panel login">
|
||||
<div class="login-hero">
|
||||
<div class="login-hero-title">破产重整债权申报系统</div>
|
||||
<div class="login-hero-sub">SUBMIT REQUIREMENTS</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<a-space direction="vertical" class="w-full" size="middle">
|
||||
<a-button type="primary" block size="large" @click="navigateTo('/login')">
|
||||
<template #icon><LoginOutlined /></template>
|
||||
用户登录申报
|
||||
</a-button>
|
||||
<a-button block size="large" @click="navigateTo('/create-app')">
|
||||
<template #icon><UserAddOutlined /></template>
|
||||
新用户注册申报
|
||||
</a-button>
|
||||
<div class="flex items-center justify-between text-sm text-gray-500">
|
||||
<span>申报审核系统</span>
|
||||
<a href="#" class="text-red-600" @click.prevent>忘记密码?</a>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="banner">
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-10">
|
||||
<div class="banner-title">致力于专业破产事务服务</div>
|
||||
<div class="banner-title">以合规经营与品质服务为核心</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mx-auto max-w-screen-xl px-4 py-10">
|
||||
<div class="section-title">
|
||||
<div class="section-title-main">新闻资讯</div>
|
||||
<div class="section-title-sub">NEWS INFORMATION</div>
|
||||
<div class="section-title-main">资讯与公告</div>
|
||||
<div class="section-title-sub">NEWS & UPDATES</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-12 gap-6">
|
||||
@@ -135,23 +169,20 @@
|
||||
|
||||
<div class="mt-10">
|
||||
<div class="section-title">
|
||||
<div class="section-title-main">典型案例</div>
|
||||
<div class="section-title-sub">CLASSIC CASE</div>
|
||||
<div class="section-title-main">资质与合作</div>
|
||||
<div class="section-title-sub">COMPLIANCE</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-12 gap-6">
|
||||
<a-card
|
||||
v-for="c in cases"
|
||||
v-for="c in compliance"
|
||||
:key="c.title"
|
||||
hoverable
|
||||
class="case-card col-span-12 sm:col-span-6 lg:col-span-3"
|
||||
@click="navigateTo(c.to)"
|
||||
>
|
||||
<template #cover>
|
||||
<a-image :src="c.image" :preview="false" class="case-image" />
|
||||
</template>
|
||||
<a-typography-title :level="5" class="!mb-2 case-title">{{ c.title }}</a-typography-title>
|
||||
<div class="case-meta">{{ c.date }}</div>
|
||||
<div class="case-desc">{{ c.desc }}</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,110 +192,119 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
FileTextOutlined,
|
||||
LoginOutlined,
|
||||
NotificationOutlined,
|
||||
ProfileOutlined,
|
||||
IdcardOutlined,
|
||||
PhoneOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
UserAddOutlined
|
||||
ShopOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
import { COMPANY } from '@/config/company'
|
||||
|
||||
const featured = {
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1400&q=80',
|
||||
title: '行于思清算公司受指定担任广西民族包装有限公司管理人',
|
||||
date: '2023-10-11'
|
||||
usePageSeo({
|
||||
title: '首页',
|
||||
description:
|
||||
'桂乐淘:生物基材料技术研发、技术服务与食品/农产品流通服务。提供经营范围与资质信息查询及合作咨询入口。',
|
||||
path: '/'
|
||||
})
|
||||
|
||||
function splitScope(text: string) {
|
||||
return text
|
||||
.split(/[;;]+/g)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((s) => s.replace(/[。.]$/, '').trim())
|
||||
}
|
||||
|
||||
const notices = [
|
||||
{
|
||||
title: '关于公开招募破产清算管理主体框架及维修施工单位的公告',
|
||||
desc: '南宁市中级人民法院根据有关规定...'
|
||||
},
|
||||
{
|
||||
title: '钦州市王明年丰全仓储有限公司重整投资人招募公告',
|
||||
desc: '钦州市王明年丰全仓储有限公司...'
|
||||
},
|
||||
{
|
||||
title: '钦州市王明年丰全仓储有限公司 预重整债权申报公告',
|
||||
desc: '债权申报相关材料及申报途径...'
|
||||
},
|
||||
{
|
||||
title: '钦州市王明年丰全仓储有限公司 预重整债权申报公告(补充)',
|
||||
desc: '补充说明及常见问题...'
|
||||
}
|
||||
]
|
||||
const generalScopeItems = computed(() => splitScope(COMPANY.scope.general))
|
||||
const licensedScopeItems = computed(() => splitScope(COMPANY.scope.licensed))
|
||||
|
||||
const guides = [
|
||||
{ title: '线上实名认证指南', desc: '快速完成实名认证', to: '/flow', icon: ProfileOutlined },
|
||||
{ title: '债权人提交材料指南', desc: '材料清单与提交规范', to: '/articles', icon: FileTextOutlined },
|
||||
{ title: '债权人网上申报指南', desc: '在线申报流程说明', to: '/login', icon: SafetyCertificateOutlined },
|
||||
{ title: '公司自主清算债权登记材料范本', desc: '模板下载与填写说明', to: '/deploy', icon: FileTextOutlined }
|
||||
const services = [
|
||||
{
|
||||
title: '生物基材料技术研发',
|
||||
desc: '面向应用场景开展研发与技术支持',
|
||||
icon: SafetyCertificateOutlined
|
||||
},
|
||||
{
|
||||
title: '技术服务与技术推广',
|
||||
desc: '技术开发/咨询/交流/转让/推广',
|
||||
icon: AppstoreOutlined
|
||||
},
|
||||
{
|
||||
title: '食品销售(预包装)',
|
||||
desc: '含保健食品(预包装)销售等',
|
||||
icon: ShopOutlined
|
||||
},
|
||||
{
|
||||
title: '农产品与生鲜流通',
|
||||
desc: '鲜肉/水产/蔬果/蛋类等零售与批发',
|
||||
icon: FileTextOutlined
|
||||
}
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '新闻动态',
|
||||
title: '公司动态',
|
||||
items: [
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...'
|
||||
'桂乐淘官方网站上线公告',
|
||||
'业务范围与资质信息已更新',
|
||||
'合作咨询:欢迎留言,我们将尽快联系',
|
||||
'合规提示:涉及许可项目以许可文件为准',
|
||||
'更多动态敬请期待...'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '法律法规',
|
||||
title: '行业资讯',
|
||||
items: [
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...'
|
||||
'生物基材料与绿色低碳趋势速览',
|
||||
'食品流通与预包装食品合规要点',
|
||||
'冷链与生鲜品质管理建议',
|
||||
'更多资讯敬请期待...',
|
||||
'更多资讯敬请期待...'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '权威发布',
|
||||
title: '合规与公告',
|
||||
items: [
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...',
|
||||
'标题新闻标题新闻标题新闻标题新闻标题...'
|
||||
'一般项目:凭营业执照依法自主开展经营活动',
|
||||
'许可项目:依法须经批准的项目,经批准后方可开展',
|
||||
'具体经营项目以相关部门批准文件或许可证件为准',
|
||||
'更多公告敬请期待...',
|
||||
'更多公告敬请期待...'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const cases = [
|
||||
const compliance = [
|
||||
{
|
||||
title: '广西南院审理的印象刘三姐重整案...',
|
||||
date: '2023-10-17',
|
||||
to: '/articles',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?auto=format&fit=crop&w=900&q=80'
|
||||
title: '经营范围',
|
||||
desc: '一般项目/许可项目明细与说明',
|
||||
to: '/products'
|
||||
},
|
||||
{
|
||||
title: '广西南院审理的印象刘三姐重整案...',
|
||||
date: '2023-10-17',
|
||||
to: '/articles',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1520607162513-77705c0f0d4a?auto=format&fit=crop&w=900&q=80'
|
||||
title: '注册地址',
|
||||
desc: '南宁市江南区国凯大道东13号神冠胶原智库项目加工厂房',
|
||||
to: '/products'
|
||||
},
|
||||
{
|
||||
title: '广西南院审理的印象刘三姐重整案...',
|
||||
date: '2023-10-17',
|
||||
to: '/articles',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1521790797524-b2497295b8a0?auto=format&fit=crop&w=900&q=80'
|
||||
title: '合作咨询',
|
||||
desc: '提交需求与合作意向,我们尽快联系',
|
||||
to: '/contact'
|
||||
},
|
||||
{
|
||||
title: '广西南院审理的印象刘三姐重整案...',
|
||||
date: '2023-10-17',
|
||||
to: '/articles',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1554224155-6726b3ff858f?auto=format&fit=crop&w=900&q=80'
|
||||
title: '更多内容',
|
||||
desc: '后续可接入资讯、产品与资质展示',
|
||||
to: '/contact'
|
||||
}
|
||||
]
|
||||
|
||||
function scrollToCompany() {
|
||||
if (!import.meta.client) return
|
||||
const el = document.querySelector('#company')
|
||||
if (!el) return
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -280,76 +320,78 @@ const cases = [
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.featured-image :deep(img) {
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
object-fit: cover;
|
||||
.hero {
|
||||
min-height: 360px;
|
||||
background: url('https://oss.wsdns.cn/20260115/75690dea8f064ceda03246b198a7d710.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.featured-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
.hero-inner {
|
||||
height: 100%;
|
||||
padding: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.featured-meta {
|
||||
margin-top: 6px;
|
||||
.hero-badge {
|
||||
width: fit-content;
|
||||
padding: 4px 10px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(22, 163, 74, 0.12);
|
||||
color: rgba(21, 128, 61, 0.95);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.notice {
|
||||
.hero-title {
|
||||
margin-top: 14px;
|
||||
font-size: 42px;
|
||||
line-height: 1.1;
|
||||
font-weight: 900;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.company {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.notice-head {
|
||||
.panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px;
|
||||
background: linear-gradient(90deg, #c30000, #e11d48);
|
||||
background: linear-gradient(90deg, #16a34a, #22c55e);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
.panel-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.notice-more {
|
||||
.panel-more {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
padding: 10px 14px 14px;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notice-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.notice-item-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.notice-item-desc {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 1.4;
|
||||
.scope-item {
|
||||
padding: 6px 0;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.section-pill {
|
||||
@@ -358,7 +400,7 @@ const cases = [
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(90deg, #c30000, #e11d48);
|
||||
background: linear-gradient(90deg, #16a34a, #22c55e);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
}
|
||||
@@ -380,8 +422,13 @@ const cases = [
|
||||
}
|
||||
|
||||
.guide-icon {
|
||||
background: rgba(195, 0, 0, 0.1);
|
||||
color: #c30000;
|
||||
background: rgba(22, 163, 74, 0.12);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
background: rgba(22, 163, 74, 0.12);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.guide-title {
|
||||
@@ -400,7 +447,7 @@ const cases = [
|
||||
padding: 18px 16px;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.22), transparent 55%),
|
||||
linear-gradient(90deg, #c30000, #e11d48);
|
||||
linear-gradient(90deg, #16a34a, #22c55e);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -418,7 +465,7 @@ const cases = [
|
||||
|
||||
.banner {
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(195, 0, 0, 0.18), transparent 55%),
|
||||
radial-gradient(circle at 20% 30%, rgba(22, 163, 74, 0.18), transparent 55%),
|
||||
linear-gradient(180deg, #ffffff, #f8fafc);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
@@ -428,7 +475,7 @@ const cases = [
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #c30000;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@@ -463,7 +510,7 @@ const cases = [
|
||||
|
||||
.column-more {
|
||||
font-size: 12px;
|
||||
color: rgba(195, 0, 0, 0.95);
|
||||
color: rgba(21, 128, 61, 0.95);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -489,19 +536,23 @@ const cases = [
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.case-image :deep(img) {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.case-title {
|
||||
color: rgba(195, 0, 0, 0.95);
|
||||
color: rgba(21, 128, 61, 0.95);
|
||||
}
|
||||
|
||||
.case-meta {
|
||||
.case-desc {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hero-inner {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
<template>
|
||||
<main class="min-h-screen bg-gray-50 px-4 py-8">
|
||||
<div class="mx-auto max-w-screen-lg space-y-6">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<NuxtLink to="/">首页</NuxtLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>
|
||||
<NuxtLink to="/articles">文章</NuxtLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item v-if="article?.categoryId">
|
||||
<NuxtLink :to="`/article/${article.categoryId}`">
|
||||
{{ article.categoryName || '栏目' }}
|
||||
</NuxtLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>{{ articleTitle }}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
|
||||
<a-skeleton v-if="pending" active :paragraph="{ rows: 10 }" />
|
||||
|
||||
<a-result
|
||||
v-else-if="loadError"
|
||||
status="error"
|
||||
title="文章加载失败"
|
||||
:sub-title="String(loadError)"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<a-card class="shadow-sm">
|
||||
<template #title>
|
||||
<div class="space-y-2">
|
||||
<div class="text-2xl font-semibold text-gray-900">
|
||||
{{ articleTitle }}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500">
|
||||
<span v-if="article?.categoryName">栏目:{{ article.categoryName }}</span>
|
||||
<span v-if="article?.author">作者:{{ article.author }}</span>
|
||||
<span v-if="article?.createTime">发布时间:{{ article.createTime }}</span>
|
||||
<span v-if="article?.source">来源:{{ article.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-alert
|
||||
v-if="articleOverview"
|
||||
type="info"
|
||||
show-icon
|
||||
class="mb-5"
|
||||
:message="articleOverview"
|
||||
/>
|
||||
|
||||
<a-image
|
||||
v-if="articleCover"
|
||||
:src="articleCover"
|
||||
:preview="false"
|
||||
class="mb-5 w-full"
|
||||
/>
|
||||
|
||||
<RichText :content="articleContent" />
|
||||
</a-card>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCmsArticle } from '@/api/cms/cmsArticle'
|
||||
import { listCmsArticleContent } from '@/api/cms/cmsArticleContent'
|
||||
import RichText from '@/components/RichText.vue'
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const articleId = computed(() => {
|
||||
const raw = route.params.id
|
||||
const val = Array.isArray(raw) ? raw[0] : raw
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : NaN
|
||||
})
|
||||
|
||||
async function loadArticleDetail(id: number): Promise<{ article: CmsArticle; content: string }> {
|
||||
const article = await getCmsArticle(id)
|
||||
|
||||
// Prefer content field, fallback to detail/overview if API differs by model.
|
||||
let content = String(article.content || article.detail || '').trim()
|
||||
|
||||
if (!content) {
|
||||
try {
|
||||
const list = await listCmsArticleContent({ articleId: id, limit: 1 })
|
||||
const hit = Array.isArray(list) ? list[0] : null
|
||||
if (hit?.content) content = String(hit.content).trim()
|
||||
} catch {
|
||||
// ignore content enrichment failures; still render metadata
|
||||
}
|
||||
}
|
||||
|
||||
return { article, content }
|
||||
}
|
||||
|
||||
const { data, pending, error: loadError } = useAsyncData(
|
||||
() => `cms-article-item-${articleId.value}`,
|
||||
async () => {
|
||||
if (!Number.isFinite(articleId.value)) throw new Error('无效的文章ID')
|
||||
return loadArticleDetail(articleId.value)
|
||||
},
|
||||
{ watch: [articleId] }
|
||||
)
|
||||
|
||||
const article = computed(() => data.value?.article)
|
||||
const articleTitle = computed(() => article.value?.title?.trim() || `文章 ${articleId.value}`)
|
||||
const articleOverview = computed(() => article.value?.overview?.trim() || '')
|
||||
const articleCover = computed(() => (typeof article.value?.image === 'string' ? article.value?.image.trim() : ''))
|
||||
const articleContent = computed(() => data.value?.content || '')
|
||||
|
||||
watchEffect(() => {
|
||||
if (!Number.isFinite(articleId.value)) return
|
||||
usePageSeo({
|
||||
title: articleTitle.value,
|
||||
description: articleOverview.value || articleTitle.value,
|
||||
path: route.fullPath
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">模板 / 插件市场</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
用模板加速交付,用插件扩展能力。支持购买、授权、更新与版本管理,形成可持续的生态与增值体系。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="模板:标准化交付">
|
||||
<a-list :data-source="templates" size="small">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="插件:按需扩展与变现">
|
||||
<a-list :data-source="plugins" size="small">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row class="mt-10" :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="购买与授权流程">
|
||||
<a-steps direction="vertical" :current="-1" size="small">
|
||||
<a-step title="选择模板/插件" description="支持套餐内置 + 加购" />
|
||||
<a-step title="下单支付" description="支付成功生成授权与交付记录" />
|
||||
<a-step title="自动启用" description="为租户装配模板/插件与默认配置" />
|
||||
<a-step title="更新升级" description="支持版本更新、到期续费与兼容性提示" />
|
||||
</a-steps>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="生态合作(示例)">
|
||||
<a-collapse>
|
||||
<a-collapse-panel key="1" header="插件开发">
|
||||
<div class="text-gray-600">提供扩展点与规范,支持第三方开发并上架。</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header="模板共建">
|
||||
<div class="text-gray-600">按行业/场景沉淀模板资产,形成标准化交付能力。</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="3" header="授权与结算">
|
||||
<div class="text-gray-600">支持授权校验、版本管理与结算规则扩展。</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="mt-10">
|
||||
<a-space>
|
||||
<a-button @click="navigateTo('/products')">看产品矩阵</a-button>
|
||||
<a-button type="primary" @click="navigateTo('/contact')">咨询合作/上架</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
usePageSeo({
|
||||
title: '模板/插件市场 - 购买/授权/更新/生态合作',
|
||||
description: '模板加速交付,插件按需扩展;支持购买、授权、更新与版本管理,构建可持续生态与增值体系。',
|
||||
path: '/market'
|
||||
})
|
||||
|
||||
const templates = [
|
||||
'行业模板:官网/电商/门户/活动页等',
|
||||
'一键套用:默认页面 + 配置 + 初始化脚本',
|
||||
'多版本管理:预览、回滚、迁移与二次编辑'
|
||||
]
|
||||
|
||||
const plugins = [
|
||||
'能力插件:支付/会员/营销/工单/统计等',
|
||||
'授权与更新:版本升级、到期续费、兼容性提示',
|
||||
'生态变现:第三方能力沉淀为可售卖插件'
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
<template>
|
||||
<main class="min-h-screen bg-gray-50 px-4 py-8">
|
||||
<div class="mx-auto max-w-screen-xl space-y-6">
|
||||
<a-skeleton v-if="navPending" active :paragraph="{ rows: 8 }" />
|
||||
|
||||
<a-result
|
||||
v-else-if="navError"
|
||||
status="error"
|
||||
title="页面加载失败"
|
||||
:sub-title="String(navError)"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<a-card class="shadow-sm">
|
||||
<template #title>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-lg font-semibold text-gray-900">
|
||||
{{ parentTitle }}
|
||||
</div>
|
||||
<div v-if="parentDescription" class="text-sm text-gray-500">
|
||||
{{ parentDescription }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-image
|
||||
v-if="parentBanner"
|
||||
:src="parentBanner"
|
||||
:preview="false"
|
||||
class="mb-5 w-full"
|
||||
/>
|
||||
|
||||
<a-layout class="bg-transparent">
|
||||
<a-layout-sider
|
||||
v-if="children.length"
|
||||
theme="light"
|
||||
:width="240"
|
||||
class="rounded-xl border border-black/5 bg-white overflow-hidden"
|
||||
breakpoint="lg"
|
||||
collapsed-width="0"
|
||||
>
|
||||
<a-menu
|
||||
mode="inline"
|
||||
:selected-keys="selectedKeys"
|
||||
@click="onMenuClick"
|
||||
>
|
||||
<a-menu-item v-for="n in children" :key="String(n.navigationId)">
|
||||
{{ n.title || n.label || `栏目 ${n.navigationId}` }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
|
||||
<a-layout-content :class="children.length ? 'pl-0 lg:pl-6 pt-6 lg:pt-0' : ''">
|
||||
<a-skeleton v-if="contentPending" active :paragraph="{ rows: 10 }" />
|
||||
|
||||
<a-result
|
||||
v-else-if="contentError"
|
||||
status="error"
|
||||
title="内容加载失败"
|
||||
:sub-title="String(contentError)"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<div class="space-y-2">
|
||||
<div class="text-2xl font-semibold text-gray-900">
|
||||
{{ contentTitle }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<span v-if="articleMeta?.createTime">发布时间:{{ articleMeta.createTime }}</span>
|
||||
<span v-if="articleMeta?.author" class="ml-4">作者:{{ articleMeta.author }}</span>
|
||||
<span v-if="articleMeta?.source" class="ml-4">来源:{{ articleMeta.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-alert
|
||||
v-if="articleMeta?.overview"
|
||||
type="info"
|
||||
show-icon
|
||||
class="my-5"
|
||||
:message="String(articleMeta.overview)"
|
||||
/>
|
||||
|
||||
<a-image
|
||||
v-if="articleMeta?.image"
|
||||
:src="String(articleMeta.image)"
|
||||
:preview="false"
|
||||
class="mb-5 w-full"
|
||||
/>
|
||||
|
||||
<RichText :content="contentBody" />
|
||||
</template>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-card>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCmsArticle } from '@/api/cms/cmsArticle'
|
||||
import { listCmsArticleContent } from '@/api/cms/cmsArticleContent'
|
||||
import RichText from '@/components/RichText.vue'
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
import type { ApiEnvelope } from '~/types/api'
|
||||
import type { CmsNavigation } from '@/api/cms/cmsNavigation/model'
|
||||
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
|
||||
|
||||
const route = useRoute()
|
||||
const parentId = computed(() => {
|
||||
const raw = route.params.id
|
||||
const val = Array.isArray(raw) ? raw[0] : raw
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : NaN
|
||||
})
|
||||
|
||||
const selectedNavId = computed(() => {
|
||||
const raw = route.query.nav
|
||||
const val = Array.isArray(raw) ? raw[0] : raw
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : NaN
|
||||
})
|
||||
|
||||
function pickNavigationList(payload: unknown): CmsNavigation[] {
|
||||
if (Array.isArray(payload)) return payload as CmsNavigation[]
|
||||
const env = payload as ApiEnvelope<CmsNavigation[]>
|
||||
if (env && typeof env === 'object' && Array.isArray(env.data)) return env.data
|
||||
return []
|
||||
}
|
||||
|
||||
const { data: navData, pending: navPending, error: navError } = useAsyncData(
|
||||
() => `cms-navigation-single-${parentId.value}`,
|
||||
async () => {
|
||||
if (!Number.isFinite(parentId.value)) throw new Error('无效的页面ID')
|
||||
// Single-page type: fetch navigation list by parentId.
|
||||
const res = await $fetch<unknown>('/api/cms-navigation', {
|
||||
query: { parentId: String(parentId.value) }
|
||||
})
|
||||
const children = pickNavigationList(res)
|
||||
.filter((n) => (n.hide ?? 0) !== 1)
|
||||
.slice()
|
||||
.sort((a, b) => (a.sortNumber ?? 0) - (b.sortNumber ?? 0))
|
||||
return { children }
|
||||
},
|
||||
{ watch: [parentId] }
|
||||
)
|
||||
|
||||
const children = computed<CmsNavigation[]>(() => navData.value?.children ?? [])
|
||||
|
||||
const activeNav = computed<CmsNavigation | null>(() => {
|
||||
const list = children.value
|
||||
if (!list.length) return null
|
||||
if (Number.isFinite(selectedNavId.value)) {
|
||||
const hit = list.find((n) => n.navigationId === selectedNavId.value)
|
||||
if (hit) return hit
|
||||
}
|
||||
return list[0]
|
||||
})
|
||||
|
||||
// Keep URL stable so refresh/back works (default to first child).
|
||||
if (import.meta.client) {
|
||||
watchEffect(() => {
|
||||
if (!children.value.length) return
|
||||
if (Number.isFinite(selectedNavId.value)) return
|
||||
const first = children.value[0]?.navigationId
|
||||
if (!first) return
|
||||
navigateTo({ path: route.path, query: { ...route.query, nav: String(first) } }, { replace: true })
|
||||
})
|
||||
}
|
||||
|
||||
const selectedKeys = computed(() => {
|
||||
const key = activeNav.value?.navigationId
|
||||
return key ? [String(key)] : []
|
||||
})
|
||||
|
||||
function onMenuClick(info: { key: string }) {
|
||||
navigateTo({ path: route.path, query: { ...route.query, nav: String(info.key) } })
|
||||
}
|
||||
|
||||
async function loadSinglePageContent(nav: CmsNavigation): Promise<{
|
||||
title: string
|
||||
body: string
|
||||
article?: CmsArticle
|
||||
}> {
|
||||
// Common pattern: navigation.itemId binds to an articleId for "single page" content.
|
||||
const articleId =
|
||||
(typeof nav.itemId === 'number' && Number.isFinite(nav.itemId) ? nav.itemId : 0) ||
|
||||
(typeof nav.pageId === 'number' && Number.isFinite(nav.pageId) ? nav.pageId : 0)
|
||||
|
||||
if (articleId > 0) {
|
||||
const article = await getCmsArticle(articleId)
|
||||
let body = String(article.content || article.detail || '').trim()
|
||||
if (!body) {
|
||||
try {
|
||||
const list = await listCmsArticleContent({ articleId, limit: 1 })
|
||||
const hit = Array.isArray(list) ? list[0] : null
|
||||
if (hit?.content) body = String(hit.content).trim()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return { title: article.title?.trim() || String(nav.title || '单页'), body, article }
|
||||
}
|
||||
|
||||
// Fallback: show navigation meta as content when no binding exists.
|
||||
return {
|
||||
title: String(nav.title || '单页'),
|
||||
body: String(nav.comments || nav.meta || '').trim()
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
data: contentData,
|
||||
pending: contentPending,
|
||||
error: contentError
|
||||
} = useAsyncData(
|
||||
() => `cms-single-page-content-${parentId.value}-${activeNav.value?.navigationId ?? 'none'}`,
|
||||
async () => {
|
||||
if (!activeNav.value) return { title: '', body: '' }
|
||||
return loadSinglePageContent(activeNav.value)
|
||||
},
|
||||
{ watch: [activeNav] }
|
||||
)
|
||||
|
||||
const parentTitle = computed(() => children.value[0]?.parentName?.trim() || `页面 ${parentId.value}`)
|
||||
const parentDescription = computed(() => '')
|
||||
const parentBanner = computed(() => '')
|
||||
|
||||
const contentTitle = computed(() => contentData.value?.title?.trim() || '')
|
||||
const contentBody = computed(() => contentData.value?.body || '')
|
||||
const articleMeta = computed(() => contentData.value?.article)
|
||||
|
||||
watchEffect(() => {
|
||||
if (!Number.isFinite(parentId.value)) return
|
||||
usePageSeo({
|
||||
title: contentTitle.value || parentTitle.value,
|
||||
description:
|
||||
(articleMeta.value?.overview?.trim() || parentDescription.value || contentTitle.value || parentTitle.value),
|
||||
path: route.fullPath
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -1,115 +0,0 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">平台能力</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
平台定位为 SaaS 软件开发平台:支持私有化部署,提供模板/插件生态,并将“下单支付→自动开通→初始化交付”标准化。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-tabs>
|
||||
<a-tab-pane key="tenant" tab="多租户与权限">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="租户隔离">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
租户数据隔离、域名/应用信息绑定、独立配置与配额策略,适配多业务线与多客户运营。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="权限体系">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
角色/菜单/按钮权限,审计与操作日志;支持模块级别的权限装配与初始化。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="modules" tab="模块化与扩展">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="模块组合产品">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
将能力拆分为可组合的模块:内容、商品、订单、会员、营销、表单等,可按产品套餐选择并初始化。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="扩展点与二开">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
支持插件化扩展点,便于第三方能力集成与二次开发;可沉淀为生态插件进行复用与变现。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="market" tab="模板/插件生态">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="模板市场">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
行业模板 + 初始化脚本配套,让交付标准化;支持多版本、预览、回滚与迁移。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="插件市场">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
插件按需购买启用,支持授权校验、更新与兼容性管理;可用于增值服务与生态合作。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="mt-6">
|
||||
<a-button @click="navigateTo('/market')">了解模板/插件市场</a-button>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="ops" tab="运维与安全">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="可观测与运维">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
支持日志/告警/指标等可观测能力接入;提供升级策略、灰度与回滚建议。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-card title="私有化与合规">
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
支持专有云/本地部署与数据安全策略;可按客户要求提供交付物清单与验收流程。
|
||||
</a-typography-paragraph>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-card class="mt-10" :bordered="false" style="background: #f6ffed">
|
||||
<a-row :gutter="[16, 16]" align="middle">
|
||||
<a-col :xs="24" :md="16">
|
||||
<a-typography-title :level="3" class="!mb-1">想把你的业务做成可售卖的产品?</a-typography-title>
|
||||
<a-typography-paragraph class="!mb-0 !text-gray-600">
|
||||
我们可以基于平台能力帮你规划产品套餐、开通链路与交付标准。
|
||||
</a-typography-paragraph>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8" class="flex md:justify-end">
|
||||
<a-button type="primary" size="large" @click="navigateTo('/contact')">马上联系</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
|
||||
usePageSeo({
|
||||
title: '平台能力 - SaaS 多租户 / 模块化 / 模板&插件生态',
|
||||
description:
|
||||
'平台定位为 SaaS 软件开发平台,支持私有化部署与模板/插件生态,标准化“下单支付→自动开通→初始化交付”全链路。',
|
||||
path: '/platform'
|
||||
})
|
||||
</script>
|
||||
|
||||
81
app/pages/products.vue
Normal file
81
app/pages/products.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">经营范围</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
以下信息用于公示与参考,最终以工商登记及相关许可文件/许可证件为准。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-row :gutter="[24, 24]">
|
||||
<a-col :xs="24" :lg="14">
|
||||
<a-card title="一般项目(经营范围)">
|
||||
<a-list size="small" :data-source="generalItems">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item class="scope-item">{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
|
||||
<a-card class="mt-6" title="许可项目">
|
||||
<a-list size="small" :data-source="licensedItems">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item class="scope-item">{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :lg="10">
|
||||
<a-card title="基本信息">
|
||||
<a-descriptions bordered :column="1" size="small">
|
||||
<a-descriptions-item label="项目名称">{{ COMPANY.projectName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册地址">{{ COMPANY.address }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-alert
|
||||
class="mt-4"
|
||||
show-icon
|
||||
type="warning"
|
||||
message="涉及许可项目的经营活动,请以主管部门批准文件/许可证件为准。"
|
||||
/>
|
||||
</a-card>
|
||||
|
||||
<a-card class="mt-6" title="快速入口">
|
||||
<a-space direction="vertical" class="w-full">
|
||||
<a-button type="primary" block @click="navigateTo('/contact')">合作咨询</a-button>
|
||||
<a-button block @click="navigateTo('/')">返回首页</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
import { COMPANY } from '@/config/company'
|
||||
|
||||
usePageSeo({
|
||||
title: '经营范围',
|
||||
description: `${COMPANY.projectName} 经营范围公示:一般项目、许可项目及注册地址信息。`,
|
||||
path: '/products'
|
||||
})
|
||||
|
||||
function splitScope(text: string) {
|
||||
return text
|
||||
.split(/[;;]+/g)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((s) => s.replace(/[。.]$/, '').trim())
|
||||
}
|
||||
|
||||
const generalItems = computed(() => splitScope(COMPANY.scope.general))
|
||||
const licensedItems = computed(() => splitScope(COMPANY.scope.licensed))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-item {
|
||||
padding: 6px 0;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user