diff --git a/dashboard/frontend/src/locales/langs/en-us.ts b/dashboard/frontend/src/locales/langs/en-us.ts index b864913..a162ff4 100644 --- a/dashboard/frontend/src/locales/langs/en-us.ts +++ b/dashboard/frontend/src/locales/langs/en-us.ts @@ -242,7 +242,33 @@ const local: App.I18n.Schema = { system: 'System', system_users: 'Users', toolbox: 'Toolbox', - 'toolbox_luxsin-controller': 'Luxsin Controller' + 'toolbox_luxsin-controller': 'Luxsin Controller', + www: 'Website', + www_global: 'Global Config', + www_global_basic: 'Site Info', + www_global_footer: 'Footer', + www_global_social: 'Social Links', + www_global_seo: 'SEO Defaults', + www_nav: 'Navigation', + www_nav_items: 'Nav Items', + www_nav_appearance: 'Nav Appearance', + www_page: 'Pages', + www_page_home: 'Home Sections', + 'www_page_product-list': 'Product List Page', + 'www_page_news-list': 'News List Page', + www_page_about: 'About Us', + www_page_support: 'Support', + www_product: 'Products', + www_product_series: 'Product Series', + www_product_list: 'Product List', + www_news: 'News', + www_news_articles: 'Articles', + www_media: 'Media Library', + www_i18n: 'Translations', + www_contact: 'Contact Form', + www_contact_settings: 'Form Settings', + www_contact_submissions: 'Submissions', + 'www_page-seo': 'Page SEO' }, page: { login: { diff --git a/dashboard/frontend/src/locales/langs/zh-cn.ts b/dashboard/frontend/src/locales/langs/zh-cn.ts index 64e5089..5006e7b 100644 --- a/dashboard/frontend/src/locales/langs/zh-cn.ts +++ b/dashboard/frontend/src/locales/langs/zh-cn.ts @@ -238,7 +238,34 @@ const local: App.I18n.Schema = { system: '系统管理', system_users: '账号管理', toolbox: '工具箱', - 'toolbox_luxsin-controller': 'luxsin 控制器' + 'toolbox_luxsin-controller': 'luxsin 控制器', + // 官网管理 + www: '官网管理', + www_global: '全局配置', + www_global_basic: '站点信息', + www_global_footer: '页脚配置', + www_global_social: '社交链接', + www_global_seo: 'SEO 默认值', + www_nav: '导航管理', + www_nav_items: '导航入口', + www_nav_appearance: '导航外观', + www_page: '页面管理', + www_page_home: '首页区块', + 'www_page_product-list': '产品列表页', + 'www_page_news-list': '新闻列表页', + www_page_about: '关于我们', + www_page_support: '技术支持', + www_product: '产品管理', + www_product_series: '产品系列', + www_product_list: '产品列表', + www_news: '新闻管理', + www_news_articles: '新闻文章', + www_media: '素材库', + www_i18n: '翻译管理', + www_contact: '联系表单', + www_contact_settings: '表单配置', + www_contact_submissions: '提交记录', + 'www_page-seo': '页面 SEO' }, page: { login: { diff --git a/dashboard/frontend/src/router/elegant/imports.ts b/dashboard/frontend/src/router/elegant/imports.ts index 246ca05..1d901b0 100644 --- a/dashboard/frontend/src/router/elegant/imports.ts +++ b/dashboard/frontend/src/router/elegant/imports.ts @@ -29,4 +29,23 @@ export const views: Record Promise import("@/views/upgrade/blacklist/index.vue"), "upgrade_ota-target-device": () => import("@/views/upgrade/ota-target-device/index.vue"), upgrade_ota: () => import("@/views/upgrade/ota/index.vue"), + www_contact_settings: () => import("@/views/www/contact/settings/index.vue"), + www_contact_submissions: () => import("@/views/www/contact/submissions/index.vue"), + www_global_basic: () => import("@/views/www/global/basic/index.vue"), + www_global_footer: () => import("@/views/www/global/footer/index.vue"), + www_global_seo: () => import("@/views/www/global/seo/index.vue"), + www_global_social: () => import("@/views/www/global/social/index.vue"), + www_i18n: () => import("@/views/www/i18n/index.vue"), + www_media: () => import("@/views/www/media/index.vue"), + www_nav_appearance: () => import("@/views/www/nav/appearance/index.vue"), + www_nav_items: () => import("@/views/www/nav/items/index.vue"), + www_news_articles: () => import("@/views/www/news/articles/index.vue"), + "www_page-seo": () => import("@/views/www/page-seo/index.vue"), + www_page_about: () => import("@/views/www/page/about/index.vue"), + www_page_home: () => import("@/views/www/page/home/index.vue"), + "www_page_news-list": () => import("@/views/www/page/news-list/index.vue"), + "www_page_product-list": () => import("@/views/www/page/product-list/index.vue"), + www_page_support: () => import("@/views/www/page/support/index.vue"), + www_product_list: () => import("@/views/www/product/list/index.vue"), + www_product_series: () => import("@/views/www/product/series/index.vue"), }; diff --git a/dashboard/frontend/src/router/elegant/routes.ts b/dashboard/frontend/src/router/elegant/routes.ts index 3dfc95c..965c50f 100644 --- a/dashboard/frontend/src/router/elegant/routes.ts +++ b/dashboard/frontend/src/router/elegant/routes.ts @@ -238,5 +238,318 @@ export const generatedRoutes: GeneratedRoute[] = [ } } ] + }, + { + name: 'www', + path: '/www', + component: 'layout.base', + meta: { + title: 'www', + i18nKey: 'route.www', + icon: 'mdi:globe-model', + order: 7 + }, + children: [ + { + name: 'www_contact', + path: '/www/contact', + meta: { + title: 'www_contact', + i18nKey: 'route.www_contact', + icon: 'mdi:email-outline', + order: 8 + }, + children: [ + { + name: 'www_contact_settings', + path: '/www/contact/settings', + component: 'view.www_contact_settings', + meta: { + title: 'www_contact_settings', + i18nKey: 'route.www_contact_settings', + icon: 'mdi:form-textbox', + order: 1, + keepAlive: true + } + }, + { + name: 'www_contact_submissions', + path: '/www/contact/submissions', + component: 'view.www_contact_submissions', + meta: { + title: 'www_contact_submissions', + i18nKey: 'route.www_contact_submissions', + icon: 'mdi:inbox-outline', + order: 2, + keepAlive: true + } + } + ] + }, + { + name: 'www_global', + path: '/www/global', + meta: { + title: 'www_global', + i18nKey: 'route.www_global', + icon: 'mdi:application-cog-outline', + order: 1 + }, + children: [ + { + name: 'www_global_basic', + path: '/www/global/basic', + component: 'view.www_global_basic', + meta: { + title: 'www_global_basic', + i18nKey: 'route.www_global_basic', + icon: 'mdi:information-outline', + order: 1, + keepAlive: true + } + }, + { + name: 'www_global_footer', + path: '/www/global/footer', + component: 'view.www_global_footer', + meta: { + title: 'www_global_footer', + i18nKey: 'route.www_global_footer', + icon: 'mdi:page-layout-footer', + order: 2, + keepAlive: true + } + }, + { + name: 'www_global_seo', + path: '/www/global/seo', + component: 'view.www_global_seo', + meta: { + title: 'www_global_seo', + i18nKey: 'route.www_global_seo', + icon: 'mdi:magnify', + order: 4, + keepAlive: true + } + }, + { + name: 'www_global_social', + path: '/www/global/social', + component: 'view.www_global_social', + meta: { + title: 'www_global_social', + i18nKey: 'route.www_global_social', + icon: 'mdi:share-variant-outline', + order: 3, + keepAlive: true + } + } + ] + }, + { + name: 'www_i18n', + path: '/www/i18n', + component: 'view.www_i18n', + meta: { + title: 'www_i18n', + i18nKey: 'route.www_i18n', + icon: 'mdi:translate', + order: 7, + keepAlive: true + } + }, + { + name: 'www_media', + path: '/www/media', + component: 'view.www_media', + meta: { + title: 'www_media', + i18nKey: 'route.www_media', + icon: 'mdi:image-multiple-outline', + order: 6, + keepAlive: true + } + }, + { + name: 'www_nav', + path: '/www/nav', + meta: { + title: 'www_nav', + i18nKey: 'route.www_nav', + icon: 'mdi:menu', + order: 2 + }, + children: [ + { + name: 'www_nav_appearance', + path: '/www/nav/appearance', + component: 'view.www_nav_appearance', + meta: { + title: 'www_nav_appearance', + i18nKey: 'route.www_nav_appearance', + icon: 'mdi:palette-outline', + order: 2, + keepAlive: true + } + }, + { + name: 'www_nav_items', + path: '/www/nav/items', + component: 'view.www_nav_items', + meta: { + title: 'www_nav_items', + i18nKey: 'route.www_nav_items', + icon: 'mdi:format-list-bulleted', + order: 1, + keepAlive: true + } + } + ] + }, + { + name: 'www_news', + path: '/www/news', + meta: { + title: 'www_news', + i18nKey: 'route.www_news', + icon: 'mdi:newspaper-variant-multiple-outline', + order: 5 + }, + children: [ + { + name: 'www_news_articles', + path: '/www/news/articles', + component: 'view.www_news_articles', + meta: { + title: 'www_news_articles', + i18nKey: 'route.www_news_articles', + icon: 'mdi:article-outline', + order: 1, + keepAlive: true + } + } + ] + }, + { + name: 'www_page', + path: '/www/page', + meta: { + title: 'www_page', + i18nKey: 'route.www_page', + icon: 'mdi:file-document-outline', + order: 3 + }, + children: [ + { + name: 'www_page_about', + path: '/www/page/about', + component: 'view.www_page_about', + meta: { + title: 'www_page_about', + i18nKey: 'route.www_page_about', + icon: 'mdi:account-group-outline', + order: 4, + keepAlive: true + } + }, + { + name: 'www_page_home', + path: '/www/page/home', + component: 'view.www_page_home', + meta: { + title: 'www_page_home', + i18nKey: 'route.www_page_home', + icon: 'mdi:home-outline', + order: 1, + keepAlive: true + } + }, + { + name: 'www_page_news-list', + path: '/www/page/news-list', + component: 'view.www_page_news-list', + meta: { + title: 'www_page_news-list', + i18nKey: 'route.www_page_news-list', + icon: 'mdi:newspaper-variant-outline', + order: 3, + keepAlive: true + } + }, + { + name: 'www_page_product-list', + path: '/www/page/product-list', + component: 'view.www_page_product-list', + meta: { + title: 'www_page_product-list', + i18nKey: 'route.www_page_product-list', + icon: 'mdi:format-list-bulleted-square', + order: 2, + keepAlive: true + } + }, + { + name: 'www_page_support', + path: '/www/page/support', + component: 'view.www_page_support', + meta: { + title: 'www_page_support', + i18nKey: 'route.www_page_support', + icon: 'mdi:lifebuoy', + order: 5, + keepAlive: true + } + } + ] + }, + { + name: 'www_page-seo', + path: '/www/page-seo', + component: 'view.www_page-seo', + meta: { + title: 'www_page-seo', + i18nKey: 'route.www_page-seo', + icon: 'mdi:search-web', + order: 9, + keepAlive: true + } + }, + { + name: 'www_product', + path: '/www/product', + meta: { + title: 'www_product', + i18nKey: 'route.www_product', + icon: 'mdi:package-variant-closed', + order: 4 + }, + children: [ + { + name: 'www_product_list', + path: '/www/product/list', + component: 'view.www_product_list', + meta: { + title: 'www_product_list', + i18nKey: 'route.www_product_list', + icon: 'mdi:view-list-outline', + order: 2, + keepAlive: true + } + }, + { + name: 'www_product_series', + path: '/www/product/series', + component: 'view.www_product_series', + meta: { + title: 'www_product_series', + i18nKey: 'route.www_product_series', + icon: 'mdi:layers-outline', + order: 1, + keepAlive: true + } + } + ] + } + ] } ]; diff --git a/dashboard/frontend/src/router/elegant/transform.ts b/dashboard/frontend/src/router/elegant/transform.ts index 0643515..b30f864 100644 --- a/dashboard/frontend/src/router/elegant/transform.ts +++ b/dashboard/frontend/src/router/elegant/transform.ts @@ -181,7 +181,33 @@ const routeMap: RouteMap = { "upgrade": "/upgrade", "upgrade_blacklist": "/upgrade/blacklist", "upgrade_ota": "/upgrade/ota", - "upgrade_ota-target-device": "/upgrade/ota-target-device" + "upgrade_ota-target-device": "/upgrade/ota-target-device", + "www": "/www", + "www_contact": "/www/contact", + "www_contact_settings": "/www/contact/settings", + "www_contact_submissions": "/www/contact/submissions", + "www_global": "/www/global", + "www_global_basic": "/www/global/basic", + "www_global_footer": "/www/global/footer", + "www_global_seo": "/www/global/seo", + "www_global_social": "/www/global/social", + "www_i18n": "/www/i18n", + "www_media": "/www/media", + "www_nav": "/www/nav", + "www_nav_appearance": "/www/nav/appearance", + "www_nav_items": "/www/nav/items", + "www_news": "/www/news", + "www_news_articles": "/www/news/articles", + "www_page": "/www/page", + "www_page_about": "/www/page/about", + "www_page_home": "/www/page/home", + "www_page_news-list": "/www/page/news-list", + "www_page_product-list": "/www/page/product-list", + "www_page_support": "/www/page/support", + "www_page-seo": "/www/page-seo", + "www_product": "/www/product", + "www_product_list": "/www/product/list", + "www_product_series": "/www/product/series" }; /** diff --git a/dashboard/frontend/src/service/api/index.ts b/dashboard/frontend/src/service/api/index.ts index 3daea45..5ae286b 100644 --- a/dashboard/frontend/src/service/api/index.ts +++ b/dashboard/frontend/src/service/api/index.ts @@ -8,3 +8,15 @@ export * from './blacklist'; export * from './ota-target-device'; export * from './share-code-log'; export * from './model'; + +// 官网 CMS +export * from './www-global'; +export * from './www-nav'; +export * from './www-sections'; +export * from './www-pages'; +export * from './www-products'; +export * from './www-news'; +export * from './www-media'; +export * from './www-i18n'; +export * from './www-contact'; +export * from './www-seo'; diff --git a/dashboard/frontend/src/service/api/www-contact.ts b/dashboard/frontend/src/service/api/www-contact.ts new file mode 100644 index 0000000..25bfa4b --- /dev/null +++ b/dashboard/frontend/src/service/api/www-contact.ts @@ -0,0 +1,49 @@ +import { request } from '../request'; +import type { PageParams } from './brand'; + +// ===== 表单配置 ===== +export function fetchGetFormSettings() { + return request({ url: '/www/contact/settings', method: 'get' }); +} + +export function fetchUpdateFormSettings(data: Omit) { + return request({ url: '/www/contact/settings', method: 'put', data }); +} + +// ===== 表单字段 ===== +export function fetchGetFormFields() { + return request<{ items: Api.Www.FormField[]; total: number }>({ url: '/www/contact/fields', method: 'get' }); +} + +export function fetchCreateFormField(data: Partial) { + return request({ url: '/www/contact/fields', method: 'post', data }); +} + +export function fetchUpdateFormField(id: number, data: Partial) { + return request({ url: `/www/contact/fields/${id}`, method: 'put', data }); +} + +export function fetchDeleteFormField(id: number) { + return request({ url: `/www/contact/fields/${id}`, method: 'delete' }); +} + +export function fetchSortFormFields(data: Array<{ id: number; sort_order: number }>) { + return request({ url: '/www/contact/fields/sort', method: 'patch', data }); +} + +// ===== 提交记录 ===== +export function fetchGetFormSubmissions(params?: PageParams & { is_read?: 0 | 1; start_date?: string; end_date?: string }) { + return request>({ url: '/www/contact/submissions', method: 'get', params }); +} + +export function fetchGetFormSubmission(id: number) { + return request({ url: `/www/contact/submissions/${id}`, method: 'get' }); +} + +export function fetchMarkSubmissionRead(id: number) { + return request({ url: `/www/contact/submissions/${id}/read`, method: 'patch' }); +} + +export function fetchDeleteSubmission(id: number) { + return request({ url: `/www/contact/submissions/${id}`, method: 'delete' }); +} diff --git a/dashboard/frontend/src/service/api/www-global.ts b/dashboard/frontend/src/service/api/www-global.ts new file mode 100644 index 0000000..17bdae1 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-global.ts @@ -0,0 +1,41 @@ +import { request } from '../request'; +import type { PageParams } from './brand'; + +// ===== 站点信息 ===== +export function fetchGetSiteSettings() { + return request({ url: '/www/global/site', method: 'get' }); +} + +export function fetchUpdateSiteSettings(data: Omit) { + return request({ url: '/www/global/site', method: 'put', data }); +} + +// ===== 页脚配置 ===== +export function fetchGetFooterSettings() { + return request({ url: '/www/global/footer', method: 'get' }); +} + +export function fetchUpdateFooterSettings(data: Omit) { + return request({ url: '/www/global/footer', method: 'put', data }); +} + +// ===== 社交链接 ===== +export function fetchGetSocialLinks() { + return request({ url: '/www/global/social', method: 'get' }); +} + +export function fetchUpdateSocialLinks(data: Array>) { + return request({ url: '/www/global/social', method: 'put', data }); +} + +// ===== SEO 默认值 ===== +export function fetchGetSeoDefaults() { + return request({ url: '/www/global/seo', method: 'get' }); +} + +export function fetchUpdateSeoDefaults(data: Omit) { + return request({ url: '/www/global/seo', method: 'put', data }); +} + +// 保留 PageParams 导出兼容 +export type { PageParams }; diff --git a/dashboard/frontend/src/service/api/www-i18n.ts b/dashboard/frontend/src/service/api/www-i18n.ts new file mode 100644 index 0000000..0d9f673 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-i18n.ts @@ -0,0 +1,13 @@ +import { request } from '../request'; + +export function fetchGetI18nEntries(params?: { keyword?: string }) { + return request<{ items: Api.Www.I18nEntry[]; total: number }>({ url: '/www/i18n', method: 'get', params }); +} + +export function fetchUpdateI18nEntry(id: number, data: { value_zh: string; value_en: string }) { + return request({ url: `/www/i18n/${id}`, method: 'put', data }); +} + +export function fetchBatchUpdateI18nEntries(data: Array<{ id: number; value_zh: string; value_en: string }>) { + return request({ url: '/www/i18n/batch', method: 'put', data }); +} diff --git a/dashboard/frontend/src/service/api/www-media.ts b/dashboard/frontend/src/service/api/www-media.ts new file mode 100644 index 0000000..9feef03 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-media.ts @@ -0,0 +1,51 @@ +import { request } from '../request'; +import type { PageParams } from './brand'; + +// ===== 素材库 ===== +export function fetchGetMedia(params?: PageParams & { category?: string; tag?: string; keyword?: string; file_type?: string }) { + return request>({ url: '/www/media', method: 'get', params }); +} + +export function fetchUploadMedia(file: File, onProgress?: (percent: number) => void) { + const formData = new FormData(); + formData.append('file', file); + return request({ + url: '/www/media/upload', + method: 'post', + data: formData, + onUploadProgress: (e: { loaded: number; total?: number }) => { + if (onProgress && e.total) { + onProgress(Math.round((e.loaded / e.total) * 100)); + } + } + }); +} + +export function fetchGetMediaItem(id: number) { + return request({ url: `/www/media/${id}`, method: 'get' }); +} + +export function fetchUpdateMediaItem(id: number, data: { filename?: string; category?: string; tags?: string[] }) { + return request({ url: `/www/media/${id}`, method: 'put', data }); +} + +export function fetchDeleteMediaItem(id: number) { + return request({ url: `/www/media/${id}`, method: 'delete' }); +} + +// ===== 素材标签 ===== +export function fetchGetMediaTags() { + return request({ url: '/www/media/tags', method: 'get' }); +} + +export function fetchCreateMediaTag(tagName: string) { + return request({ url: '/www/media/tags', method: 'post', data: { tag_name: tagName } }); +} + +export function fetchUpdateMediaTag(id: number, tagName: string) { + return request({ url: `/www/media/tags/${id}`, method: 'put', data: { tag_name: tagName } }); +} + +export function fetchDeleteMediaTag(id: number) { + return request({ url: `/www/media/tags/${id}`, method: 'delete' }); +} diff --git a/dashboard/frontend/src/service/api/www-nav.ts b/dashboard/frontend/src/service/api/www-nav.ts new file mode 100644 index 0000000..beced1f --- /dev/null +++ b/dashboard/frontend/src/service/api/www-nav.ts @@ -0,0 +1,31 @@ +import { request } from '../request'; + +// ===== 导航入口 ===== +export function fetchGetNavItems() { + return request<{ items: Api.Www.NavItem[]; total: number }>({ url: '/www/nav/items', method: 'get' }); +} + +export function fetchCreateNavItem(data: Api.Www.NavItemPayload) { + return request({ url: '/www/nav/items', method: 'post', data }); +} + +export function fetchUpdateNavItem(id: number, data: Partial) { + return request({ url: `/www/nav/items/${id}`, method: 'put', data }); +} + +export function fetchDeleteNavItem(id: number) { + return request({ url: `/www/nav/items/${id}`, method: 'delete' }); +} + +export function fetchSortNavItems(data: Array<{ id: number; sort_order: number }>) { + return request({ url: '/www/nav/items/sort', method: 'patch', data }); +} + +// ===== 导航外观 ===== +export function fetchGetNavAppearance() { + return request({ url: '/www/nav/appearance', method: 'get' }); +} + +export function fetchUpdateNavAppearance(data: Omit) { + return request({ url: '/www/nav/appearance', method: 'put', data }); +} diff --git a/dashboard/frontend/src/service/api/www-news.ts b/dashboard/frontend/src/service/api/www-news.ts new file mode 100644 index 0000000..fbbe5fe --- /dev/null +++ b/dashboard/frontend/src/service/api/www-news.ts @@ -0,0 +1,32 @@ +import { request } from '../request'; +import type { PageParams } from './brand'; + +// ===== 新闻文章 ===== +export function fetchGetNews(params?: PageParams & { status?: string; title?: string }) { + return request>({ url: '/www/news', method: 'get', params }); +} + +export function fetchGetNewsArticle(id: number) { + return request({ url: `/www/news/${id}`, method: 'get' }); +} + +export function fetchCreateNewsArticle(data: Api.Www.NewsArticlePayload) { + return request({ url: '/www/news', method: 'post', data }); +} + +export function fetchUpdateNewsArticle(id: number, data: Partial) { + return request({ url: `/www/news/${id}`, method: 'put', data }); +} + +export function fetchDeleteNewsArticle(id: number) { + return request({ url: `/www/news/${id}`, method: 'delete' }); +} + +// ===== 文章推荐 ===== +export function fetchGetNewsRecommendations(id: number) { + return request({ url: `/www/news/${id}/recommendations`, method: 'get' }); +} + +export function fetchUpdateNewsRecommendations(id: number, recommendedIds: number[]) { + return request({ url: `/www/news/${id}/recommendations`, method: 'put', data: { recommended_ids: recommendedIds } }); +} diff --git a/dashboard/frontend/src/service/api/www-pages.ts b/dashboard/frontend/src/service/api/www-pages.ts new file mode 100644 index 0000000..db7ab9a --- /dev/null +++ b/dashboard/frontend/src/service/api/www-pages.ts @@ -0,0 +1,37 @@ +import { request } from '../request'; + +// ===== 产品列表页配置 ===== +export function fetchGetProductListSettings() { + return request({ url: '/www/page/product-list', method: 'get' }); +} + +export function fetchUpdateProductListSettings(data: Omit) { + return request({ url: '/www/page/product-list', method: 'put', data }); +} + +// ===== 新闻列表页配置 ===== +export function fetchGetNewsListSettings() { + return request({ url: '/www/page/news-list', method: 'get' }); +} + +export function fetchUpdateNewsListSettings(data: Omit) { + return request({ url: '/www/page/news-list', method: 'put', data }); +} + +// ===== 关于我们配置 ===== +export function fetchGetAboutSettings() { + return request({ url: '/www/page/about', method: 'get' }); +} + +export function fetchUpdateAboutSettings(data: Record) { + return request({ url: '/www/page/about', method: 'put', data }); +} + +// ===== 技术支持配置 ===== +export function fetchGetSupportSettings() { + return request({ url: '/www/page/support', method: 'get' }); +} + +export function fetchUpdateSupportSettings(data: Record) { + return request({ url: '/www/page/support', method: 'put', data }); +} diff --git a/dashboard/frontend/src/service/api/www-products.ts b/dashboard/frontend/src/service/api/www-products.ts new file mode 100644 index 0000000..05462a8 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-products.ts @@ -0,0 +1,51 @@ +import { request } from '../request'; +import type { PageParams } from './brand'; + +// ===== 产品系列 ===== +export function fetchGetProductSeries() { + return request<{ items: Api.Www.ProductSeries[]; total: number }>({ + url: '/www/product/series', + method: 'get' + }); +} + +export function fetchCreateProductSeries(data: Api.Www.ProductSeriesPayload) { + return request({ url: '/www/product/series', method: 'post', data }); +} + +export function fetchGetProductSeriesById(id: number) { + return request({ url: `/www/product/series/${id}`, method: 'get' }); +} + +export function fetchUpdateProductSeries(id: number, data: Partial) { + return request({ url: `/www/product/series/${id}`, method: 'put', data }); +} + +export function fetchDeleteProductSeries(id: number) { + return request({ url: `/www/product/series/${id}`, method: 'delete' }); +} + +export function fetchSortProductSeries(data: Array<{ id: number; sort_order: number }>) { + return request({ url: '/www/product/series/sort', method: 'patch', data }); +} + +// ===== 产品 ===== +export function fetchGetProducts(params?: PageParams & { series_id?: number; is_visible?: 0 | 1; name?: string }) { + return request>({ url: '/www/products', method: 'get', params }); +} + +export function fetchGetProduct(id: number) { + return request({ url: `/www/products/${id}`, method: 'get' }); +} + +export function fetchCreateProduct(data: Api.Www.ProductPayload) { + return request({ url: '/www/products', method: 'post', data }); +} + +export function fetchUpdateProduct(id: number, data: Partial) { + return request({ url: `/www/products/${id}`, method: 'put', data }); +} + +export function fetchDeleteProduct(id: number) { + return request({ url: `/www/products/${id}`, method: 'delete' }); +} diff --git a/dashboard/frontend/src/service/api/www-sections.ts b/dashboard/frontend/src/service/api/www-sections.ts new file mode 100644 index 0000000..df34ec6 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-sections.ts @@ -0,0 +1,76 @@ +import { request } from '../request'; + +// ===== Section 区块 ===== +export function fetchGetSections(params: { page_type: string; product_id?: number }) { + return request<{ items: Api.Www.SectionBlock[]; total: number }>({ + url: '/www/sections', + method: 'get', + params + }); +} + +export function fetchGetSection(id: number) { + return request({ url: `/www/sections/${id}`, method: 'get' }); +} + +export function fetchCreateSection(data: Api.Www.SectionPayload) { + return request({ url: '/www/sections', method: 'post', data }); +} + +export function fetchUpdateSection(id: number, data: Partial) { + return request({ url: `/www/sections/${id}`, method: 'put', data }); +} + +export function fetchDeleteSection(id: number) { + return request({ url: `/www/sections/${id}`, method: 'delete' }); +} + +export function fetchSortSections(data: Array<{ id: number; sort_order: number }>) { + return request({ url: '/www/sections/sort', method: 'patch', data }); +} + +export function fetchDuplicateSection(id: number) { + return request({ url: `/www/sections/${id}/duplicate`, method: 'post' }); +} + +// ===== 参数分组 ===== +export function fetchGetSpecGroups(sectionId: number) { + return request({ url: `/www/sections/${sectionId}/spec-groups`, method: 'get' }); +} + +export function fetchCreateSpecGroup(sectionId: number, data: { title_zh: string; title_en: string; default_visible?: number }) { + return request({ url: `/www/sections/${sectionId}/spec-groups`, method: 'post', data }); +} + +export function fetchUpdateSpecGroup(sectionId: number, groupId: number, data: Partial<{ title_zh: string; title_en: string; default_visible: number }>) { + return request({ url: `/www/sections/${sectionId}/spec-groups/${groupId}`, method: 'put', data }); +} + +export function fetchDeleteSpecGroup(sectionId: number, groupId: number) { + return request({ url: `/www/sections/${sectionId}/spec-groups/${groupId}`, method: 'delete' }); +} + +export function fetchSortSpecGroups(sectionId: number, data: Array<{ id: number; sort_order: number }>) { + return request({ url: `/www/sections/${sectionId}/spec-groups/sort`, method: 'patch', data }); +} + +// ===== 参数项 ===== +export function fetchGetSpecItems(groupId: number) { + return request({ url: `/www/spec-groups/${groupId}/items`, method: 'get' }); +} + +export function fetchCreateSpecItem(groupId: number, data: { name_zh: string; name_en: string; value_zh: string; value_en: string }) { + return request({ url: `/www/spec-groups/${groupId}/items`, method: 'post', data }); +} + +export function fetchUpdateSpecItem(id: number, data: Partial<{ name_zh: string; name_en: string; value_zh: string; value_en: string }>) { + return request({ url: `/www/spec-items/${id}`, method: 'put', data }); +} + +export function fetchDeleteSpecItem(id: number) { + return request({ url: `/www/spec-items/${id}`, method: 'delete' }); +} + +export function fetchSortSpecItems(groupId: number, data: Array<{ id: number; sort_order: number }>) { + return request({ url: `/www/spec-groups/${groupId}/items/sort`, method: 'patch', data }); +} diff --git a/dashboard/frontend/src/service/api/www-seo.ts b/dashboard/frontend/src/service/api/www-seo.ts new file mode 100644 index 0000000..5427c23 --- /dev/null +++ b/dashboard/frontend/src/service/api/www-seo.ts @@ -0,0 +1,9 @@ +import { request } from '../request'; + +export function fetchGetPageSeo(params: { page_type: string; entity_id?: number }) { + return request({ url: '/www/page-seo', method: 'get', params }); +} + +export function fetchUpdatePageSeo(data: Omit) { + return request({ url: '/www/page-seo', method: 'put', data }); +} diff --git a/dashboard/frontend/src/typings/api/www.d.ts b/dashboard/frontend/src/typings/api/www.d.ts new file mode 100644 index 0000000..3ff7543 --- /dev/null +++ b/dashboard/frontend/src/typings/api/www.d.ts @@ -0,0 +1,348 @@ +declare namespace Api { + namespace Www { + // ===== 1. 全局配置 ===== + interface SiteSettings { + id?: number; + site_title: string; + favicon_url: string; + logo_dark_url: string; + logo_light_url: string; + updated_at?: string; + } + + interface FooterSettings { + id?: number; + company_name: string; + company_address: string; + contact_phone: string; + contact_email: string; + copyright_text: string; + icp_number: string; + police_number: string; + } + + interface SocialLink { + id?: number; + platform: string; + url: string; + qrcode_url: string; + sort_order?: number; + } + + interface SeoDefaults { + id?: number; + meta_description: string; + meta_keywords: string; + og_image_url: string; + } + + // ===== 2. 导航管理 ===== + interface NavItem { + id: number; + name_zh: string; + name_en: string; + link: string; + is_visible: boolean; + sort_order: number; + display_mode: 'text' | 'icon' | 'image'; + icon_name: string; + image_url: string; + open_new_tab: boolean; + created_at?: string; + updated_at?: string; + } + + interface NavItemPayload { + name_zh: string; + name_en: string; + link: string; + is_visible?: boolean; + display_mode?: string; + icon_name?: string; + image_url?: string; + open_new_tab?: boolean; + } + + interface NavAppearance { + id?: number; + homepage_style: string; + non_homepage_style: string; + } + + // ===== 3. Section 区块 ===== + interface SectionBlock { + id: number; + page_type: string; + product_id: number | null; + layout: string; + theme: string; + bg_type: string; + bg_value: string; + overlay_enabled: boolean; + overlay_opacity: number; + overline_zh: string; + overline_en: string; + title_zh: string; + title_en: string; + subtitle_zh: string; + subtitle_en: string; + content_zh: string; + content_en: string; + cta_primary_zh: string; + cta_primary_en: string; + cta_primary_url: string; + cta_secondary_zh: string; + cta_secondary_en: string; + cta_secondary_url: string; + media_url_zh: string; + media_url_en: string; + video_url: string; + config: Record; + is_visible: boolean; + sort_order: number; + created_at?: string; + updated_at?: string; + } + + interface SectionPayload { + page_type: string; + product_id?: number | null; + layout: string; + theme?: string; + bg_type?: string; + bg_value?: string; + overlay_enabled?: boolean; + overlay_opacity?: number; + overline_zh?: string; + overline_en?: string; + title_zh?: string; + title_en?: string; + subtitle_zh?: string; + subtitle_en?: string; + content_zh?: string; + content_en?: string; + cta_primary_zh?: string; + cta_primary_en?: string; + cta_primary_url?: string; + cta_secondary_zh?: string; + cta_secondary_en?: string; + cta_secondary_url?: string; + media_url_zh?: string; + media_url_en?: string; + video_url?: string; + config?: Record; + is_visible?: boolean; + } + + interface SpecGroup { + id: number; + section_id: number; + title_zh: string; + title_en: string; + default_visible: number; + sort_order: number; + } + + interface SpecItem { + id: number; + group_id: number; + name_zh: string; + name_en: string; + value_zh: string; + value_en: string; + sort_order: number; + } + + // ===== 4. 页面配置 ===== + interface ProductListSettings { + id?: number; + hero_title_zh: string; + hero_title_en: string; + hero_subtitle_zh: string; + hero_subtitle_en: string; + hero_bg_url: string; + columns: number; + show_series_filter: boolean; + default_sort: string; + } + + interface NewsListSettings { + id?: number; + hero_title_zh: string; + hero_title_en: string; + hero_subtitle_zh: string; + hero_subtitle_en: string; + hero_bg_url: string; + page_size: number; + show_cover: boolean; + show_summary: boolean; + } + + interface AboutSettings { + id?: number; + [key: string]: unknown; + } + + interface SupportSettings { + id?: number; + [key: string]: unknown; + } + + // ===== 5. 产品管理 ===== + interface ProductSeries { + id: number; + name_zh: string; + name_en: string; + overline: string; + subtitle_zh: string; + subtitle_en: string; + cover_url: string; + sort_order: number; + is_visible: boolean; + created_at?: string; + updated_at?: string; + } + + interface ProductSeriesPayload { + name_zh: string; + name_en: string; + overline?: string; + subtitle_zh?: string; + subtitle_en?: string; + cover_url?: string; + is_visible?: boolean; + } + + interface Product { + id: number; + series_id: number; + name_zh: string; + name_en: string; + slug: string; + intro_zh: string; + intro_en: string; + cover_url: string; + is_visible: boolean; + sort_order: number; + series?: ProductSeries; + created_at?: string; + updated_at?: string; + } + + interface ProductPayload { + series_id: number; + name_zh: string; + name_en: string; + slug: string; + intro_zh?: string; + intro_en?: string; + cover_url?: string; + is_visible?: boolean; + } + + // ===== 6. 新闻管理 ===== + interface NewsArticle { + id: number; + title_zh: string; + title_en: string; + slug: string; + summary_zh: string; + summary_en: string; + cover_url: string; + content_zh: string; + content_en: string; + status: 'draft' | 'published' | 'offline'; + published_at: string | null; + created_at?: string; + updated_at?: string; + } + + interface NewsArticlePayload { + title_zh: string; + title_en: string; + slug: string; + summary_zh?: string; + summary_en?: string; + cover_url?: string; + content_zh?: string; + content_en?: string; + status?: string; + published_at?: string | null; + } + + // ===== 7. 素材库 ===== + interface MediaItem { + id: number; + filename: string; + file_path: string; + file_type: 'image' | 'video' | 'file'; + mime_type: string; + file_size: number; + width: number | null; + height: number | null; + category: string; + thumbnail_url: string; + webp_url: string; + ref_count: number; + tags: string[]; + created_at?: string; + } + + interface MediaTag { + id: number; + tag_name: string; + } + + // ===== 8. 翻译管理 ===== + interface I18nEntry { + id: number; + dict_key: string; + value_zh: string; + value_en: string; + description: string; + } + + // ===== 9. 联系表单 ===== + interface FormSettings { + id?: number; + recipient_email: string; + success_message_zh: string; + success_message_en: string; + error_message_zh: string; + error_message_en: string; + cooldown_seconds: number; + } + + interface FormField { + id: number; + name_zh: string; + name_en: string; + field_type: 'text' | 'email' | 'phone' | 'textarea' | 'select'; + is_required: boolean; + placeholder_zh: string; + placeholder_en: string; + options_json: string | null; + sort_order: number; + } + + interface FormSubmission { + id: number; + form_data: Record; + visitor_ip: string; + user_agent: string; + is_read: boolean; + submitted_at: string; + } + + // ===== 10. 页面 SEO ===== + interface PageSeo { + id?: number; + page_type: string; + entity_id?: number | null; + meta_title: string; + meta_description: string; + meta_keywords: string; + og_image_url: string; + } + } +} diff --git a/dashboard/frontend/src/typings/components.d.ts b/dashboard/frontend/src/typings/components.d.ts index 04ca23f..ba7d237 100644 --- a/dashboard/frontend/src/typings/components.d.ts +++ b/dashboard/frontend/src/typings/components.d.ts @@ -68,10 +68,12 @@ declare module 'vue' { NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NPagination: typeof import('naive-ui')['NPagination'] NProgress: typeof import('naive-ui')['NProgress'] + NRadio: typeof import('naive-ui')['NRadio'] NRadioButton: typeof import('naive-ui')['NRadioButton'] NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NScrollbar: typeof import('naive-ui')['NScrollbar'] NSelect: typeof import('naive-ui')['NSelect'] + NSlider: typeof import('naive-ui')['NSlider'] NSpace: typeof import('naive-ui')['NSpace'] NSpin: typeof import('naive-ui')['NSpin'] NSwitch: typeof import('naive-ui')['NSwitch'] @@ -156,10 +158,12 @@ declare global { const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] const NPagination: typeof import('naive-ui')['NPagination'] const NProgress: typeof import('naive-ui')['NProgress'] + const NRadio: typeof import('naive-ui')['NRadio'] const NRadioButton: typeof import('naive-ui')['NRadioButton'] const NRadioGroup: typeof import('naive-ui')['NRadioGroup'] const NScrollbar: typeof import('naive-ui')['NScrollbar'] const NSelect: typeof import('naive-ui')['NSelect'] + const NSlider: typeof import('naive-ui')['NSlider'] const NSpace: typeof import('naive-ui')['NSpace'] const NSpin: typeof import('naive-ui')['NSpin'] const NSwitch: typeof import('naive-ui')['NSwitch'] diff --git a/dashboard/frontend/src/typings/elegant-router.d.ts b/dashboard/frontend/src/typings/elegant-router.d.ts index 9ba461e..4068b8d 100644 --- a/dashboard/frontend/src/typings/elegant-router.d.ts +++ b/dashboard/frontend/src/typings/elegant-router.d.ts @@ -36,6 +36,32 @@ declare module "@elegant-router/types" { "upgrade_blacklist": "/upgrade/blacklist"; "upgrade_ota": "/upgrade/ota"; "upgrade_ota-target-device": "/upgrade/ota-target-device"; + "www": "/www"; + "www_contact": "/www/contact"; + "www_contact_settings": "/www/contact/settings"; + "www_contact_submissions": "/www/contact/submissions"; + "www_global": "/www/global"; + "www_global_basic": "/www/global/basic"; + "www_global_footer": "/www/global/footer"; + "www_global_seo": "/www/global/seo"; + "www_global_social": "/www/global/social"; + "www_i18n": "/www/i18n"; + "www_media": "/www/media"; + "www_nav": "/www/nav"; + "www_nav_appearance": "/www/nav/appearance"; + "www_nav_items": "/www/nav/items"; + "www_news": "/www/news"; + "www_news_articles": "/www/news/articles"; + "www_page": "/www/page"; + "www_page_about": "/www/page/about"; + "www_page_home": "/www/page/home"; + "www_page_news-list": "/www/page/news-list"; + "www_page_product-list": "/www/page/product-list"; + "www_page_support": "/www/page/support"; + "www_page-seo": "/www/page-seo"; + "www_product": "/www/product"; + "www_product_list": "/www/product/list"; + "www_product_series": "/www/product/series"; }; /** @@ -78,6 +104,7 @@ declare module "@elegant-router/types" { | "system" | "toolbox" | "upgrade" + | "www" >; /** @@ -108,6 +135,25 @@ declare module "@elegant-router/types" { | "upgrade_blacklist" | "upgrade_ota-target-device" | "upgrade_ota" + | "www_contact_settings" + | "www_contact_submissions" + | "www_global_basic" + | "www_global_footer" + | "www_global_seo" + | "www_global_social" + | "www_i18n" + | "www_media" + | "www_nav_appearance" + | "www_nav_items" + | "www_news_articles" + | "www_page-seo" + | "www_page_about" + | "www_page_home" + | "www_page_news-list" + | "www_page_product-list" + | "www_page_support" + | "www_product_list" + | "www_product_series" >; /** diff --git a/dashboard/frontend/src/views/www/contact/settings/index.vue b/dashboard/frontend/src/views/www/contact/settings/index.vue new file mode 100644 index 0000000..f6a7478 --- /dev/null +++ b/dashboard/frontend/src/views/www/contact/settings/index.vue @@ -0,0 +1,262 @@ + + + diff --git a/dashboard/frontend/src/views/www/contact/submissions/index.vue b/dashboard/frontend/src/views/www/contact/submissions/index.vue new file mode 100644 index 0000000..ba4b842 --- /dev/null +++ b/dashboard/frontend/src/views/www/contact/submissions/index.vue @@ -0,0 +1,187 @@ + + + diff --git a/dashboard/frontend/src/views/www/global/basic/index.vue b/dashboard/frontend/src/views/www/global/basic/index.vue new file mode 100644 index 0000000..12be7ae --- /dev/null +++ b/dashboard/frontend/src/views/www/global/basic/index.vue @@ -0,0 +1,73 @@ + + + diff --git a/dashboard/frontend/src/views/www/global/footer/index.vue b/dashboard/frontend/src/views/www/global/footer/index.vue new file mode 100644 index 0000000..f13ede5 --- /dev/null +++ b/dashboard/frontend/src/views/www/global/footer/index.vue @@ -0,0 +1,77 @@ + + + diff --git a/dashboard/frontend/src/views/www/global/seo/index.vue b/dashboard/frontend/src/views/www/global/seo/index.vue new file mode 100644 index 0000000..75fb09d --- /dev/null +++ b/dashboard/frontend/src/views/www/global/seo/index.vue @@ -0,0 +1,59 @@ + + + diff --git a/dashboard/frontend/src/views/www/global/social/index.vue b/dashboard/frontend/src/views/www/global/social/index.vue new file mode 100644 index 0000000..70f19f7 --- /dev/null +++ b/dashboard/frontend/src/views/www/global/social/index.vue @@ -0,0 +1,111 @@ + + + diff --git a/dashboard/frontend/src/views/www/i18n/index.vue b/dashboard/frontend/src/views/www/i18n/index.vue new file mode 100644 index 0000000..74c1213 --- /dev/null +++ b/dashboard/frontend/src/views/www/i18n/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/dashboard/frontend/src/views/www/media/index.vue b/dashboard/frontend/src/views/www/media/index.vue new file mode 100644 index 0000000..35639f5 --- /dev/null +++ b/dashboard/frontend/src/views/www/media/index.vue @@ -0,0 +1,247 @@ + + + diff --git a/dashboard/frontend/src/views/www/nav/appearance/index.vue b/dashboard/frontend/src/views/www/nav/appearance/index.vue new file mode 100644 index 0000000..c68f8c9 --- /dev/null +++ b/dashboard/frontend/src/views/www/nav/appearance/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/dashboard/frontend/src/views/www/nav/items/index.vue b/dashboard/frontend/src/views/www/nav/items/index.vue new file mode 100644 index 0000000..28ccb93 --- /dev/null +++ b/dashboard/frontend/src/views/www/nav/items/index.vue @@ -0,0 +1,224 @@ + + + diff --git a/dashboard/frontend/src/views/www/news/articles/index.vue b/dashboard/frontend/src/views/www/news/articles/index.vue new file mode 100644 index 0000000..9bd6d14 --- /dev/null +++ b/dashboard/frontend/src/views/www/news/articles/index.vue @@ -0,0 +1,299 @@ + + + diff --git a/dashboard/frontend/src/views/www/page-seo/index.vue b/dashboard/frontend/src/views/www/page-seo/index.vue new file mode 100644 index 0000000..04d4317 --- /dev/null +++ b/dashboard/frontend/src/views/www/page-seo/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/dashboard/frontend/src/views/www/page/about/index.vue b/dashboard/frontend/src/views/www/page/about/index.vue new file mode 100644 index 0000000..261dc68 --- /dev/null +++ b/dashboard/frontend/src/views/www/page/about/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/dashboard/frontend/src/views/www/page/home/index.vue b/dashboard/frontend/src/views/www/page/home/index.vue new file mode 100644 index 0000000..382fac4 --- /dev/null +++ b/dashboard/frontend/src/views/www/page/home/index.vue @@ -0,0 +1,338 @@ + + + diff --git a/dashboard/frontend/src/views/www/page/news-list/index.vue b/dashboard/frontend/src/views/www/page/news-list/index.vue new file mode 100644 index 0000000..4eb4a39 --- /dev/null +++ b/dashboard/frontend/src/views/www/page/news-list/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/dashboard/frontend/src/views/www/page/product-list/index.vue b/dashboard/frontend/src/views/www/page/product-list/index.vue new file mode 100644 index 0000000..a7f6f24 --- /dev/null +++ b/dashboard/frontend/src/views/www/page/product-list/index.vue @@ -0,0 +1,119 @@ + + + diff --git a/dashboard/frontend/src/views/www/page/support/index.vue b/dashboard/frontend/src/views/www/page/support/index.vue new file mode 100644 index 0000000..2a48686 --- /dev/null +++ b/dashboard/frontend/src/views/www/page/support/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/dashboard/frontend/src/views/www/product/list/index.vue b/dashboard/frontend/src/views/www/product/list/index.vue new file mode 100644 index 0000000..0814482 --- /dev/null +++ b/dashboard/frontend/src/views/www/product/list/index.vue @@ -0,0 +1,336 @@ + + + diff --git a/dashboard/frontend/src/views/www/product/series/index.vue b/dashboard/frontend/src/views/www/product/series/index.vue new file mode 100644 index 0000000..184ad43 --- /dev/null +++ b/dashboard/frontend/src/views/www/product/series/index.vue @@ -0,0 +1,197 @@ + + +