快乐的梦鱼 1 kuukausi sitten
commit
69cf9a8e70
100 muutettua tiedostoa jossa 14798 lisäystä ja 0 poistoa
  1. 33 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 33 0
      README.md
  4. 1 0
      env.d.ts
  5. 13 0
      index.html
  6. 67 0
      nuxt.config.ts
  7. 13753 0
      package-lock.json
  8. 57 0
      package.json
  9. BIN
      public/favicon.ico
  10. 2 0
      public/robots.txt
  11. 3 0
      server/tsconfig.json
  12. 65 0
      src/App.vue
  13. 417 0
      src/api/CommonContent.ts
  14. 12 0
      src/api/NotConfigue.ts
  15. 223 0
      src/api/RequestModules.ts
  16. 24 0
      src/api/Test.ts
  17. 15 0
      src/api/Utils.ts
  18. 68 0
      src/api/auth/UserApi.ts
  19. BIN
      src/assets/fonts/Impact.ttf
  20. BIN
      src/assets/fonts/Impact.woff
  21. BIN
      src/assets/fonts/Impact.woff2
  22. BIN
      src/assets/fonts/STSongti-SC-Black.ttf
  23. BIN
      src/assets/fonts/STSongti-SC-Black.woff
  24. BIN
      src/assets/fonts/STSongti-SC-Black.woff2
  25. BIN
      src/assets/fonts/SourceHanSerifCN-Bold.otf
  26. BIN
      src/assets/fonts/SourceHanSerifCN-Bold.ttf
  27. BIN
      src/assets/fonts/SourceHanSerifCN-Bold.woff
  28. BIN
      src/assets/fonts/SourceHanSerifCN-Bold.woff2
  29. BIN
      src/assets/fonts/nzgrRuyinZouZhangKai.ttf
  30. BIN
      src/assets/fonts/nzgrRuyinZouZhangKai.woff
  31. BIN
      src/assets/fonts/nzgrRuyinZouZhangKai.woff2
  32. 1 0
      src/assets/images/404.svg
  33. BIN
      src/assets/images/BackArrow.png
  34. BIN
      src/assets/images/Bg1.jpg
  35. BIN
      src/assets/images/Bg2.jpg
  36. BIN
      src/assets/images/BgLong.jpg
  37. BIN
      src/assets/images/CloseMini.png
  38. BIN
      src/assets/images/DropDownArrow.png
  39. BIN
      src/assets/images/Icon@2x.png
  40. BIN
      src/assets/images/IconArrowRight.png
  41. 8 0
      src/assets/images/IconInfo.svg
  42. BIN
      src/assets/images/ImageFailed.png
  43. BIN
      src/assets/images/LargeTitle1.png
  44. BIN
      src/assets/images/LargeTitle2.png
  45. BIN
      src/assets/images/LargeTitle3.png
  46. BIN
      src/assets/images/Logo2.png
  47. BIN
      src/assets/images/LogoIcon.png
  48. BIN
      src/assets/images/LogoIconDark.png
  49. BIN
      src/assets/images/TitleMiniHeader.png
  50. BIN
      src/assets/images/about/Banner.jpg
  51. BIN
      src/assets/images/about/IconLocation.png
  52. BIN
      src/assets/images/about/IconMail.png
  53. BIN
      src/assets/images/about/IconMobile.png
  54. BIN
      src/assets/images/about/Logo.png
  55. BIN
      src/assets/images/communicate/Banner.jpg
  56. BIN
      src/assets/images/communicate/Image1.jpg
  57. BIN
      src/assets/images/communicate/Image2.jpg
  58. BIN
      src/assets/images/communicate/Image3.jpg
  59. BIN
      src/assets/images/favicon.ico
  60. BIN
      src/assets/images/favicon.png
  61. BIN
      src/assets/images/footer/FooterPrinting.png
  62. BIN
      src/assets/images/footer/GonganLogo.png
  63. BIN
      src/assets/images/fusion/Banner.jpg
  64. BIN
      src/assets/images/fusion/Image1.jpg
  65. BIN
      src/assets/images/fusion/Image2.jpg
  66. BIN
      src/assets/images/fusion/Image3.jpg
  67. BIN
      src/assets/images/fusion/Image4.jpg
  68. BIN
      src/assets/images/fusion/Image5.jpg
  69. BIN
      src/assets/images/fusion/Image6.jpg
  70. BIN
      src/assets/images/index/Box1.png
  71. BIN
      src/assets/images/index/Box2.jpg
  72. BIN
      src/assets/images/index/Box3.jpg
  73. BIN
      src/assets/images/index/BoxPrinting1.png
  74. BIN
      src/assets/images/index/BoxPrinting2.png
  75. BIN
      src/assets/images/index/BoxPrinting4.png
  76. BIN
      src/assets/images/index/ButtonMore.png
  77. BIN
      src/assets/images/index/Introd.jpg
  78. BIN
      src/assets/images/index/IntrodLeft.png
  79. BIN
      src/assets/images/index/IntrodRight.jpg
  80. BIN
      src/assets/images/inheritor/Banner.jpg
  81. BIN
      src/assets/images/inheritor/Image1.jpg
  82. BIN
      src/assets/images/inheritor/Image10.jpg
  83. BIN
      src/assets/images/inheritor/Image11.jpg
  84. BIN
      src/assets/images/inheritor/Image2.jpg
  85. BIN
      src/assets/images/inheritor/Image3.jpg
  86. BIN
      src/assets/images/inheritor/Image4.jpg
  87. BIN
      src/assets/images/inheritor/Image5.jpg
  88. BIN
      src/assets/images/inheritor/Image6.jpg
  89. BIN
      src/assets/images/inheritor/Image7.jpg
  90. BIN
      src/assets/images/inheritor/Image8.jpg
  91. BIN
      src/assets/images/inheritor/Image9.jpg
  92. BIN
      src/assets/images/inheritor/LawsTest.jpg
  93. BIN
      src/assets/images/inheritor/SubmitButton.png
  94. BIN
      src/assets/images/introduction/Banner.jpg
  95. BIN
      src/assets/images/introduction/CategoryImage1.jpg
  96. BIN
      src/assets/images/introduction/CategoryImage2.jpg
  97. BIN
      src/assets/images/introduction/CategoryImage3.jpg
  98. BIN
      src/assets/images/introduction/CategoryImage4.jpg
  99. BIN
      src/assets/images/introduction/CategoryImage5.jpg
  100. 0 0
      src/assets/images/introduction/CategoryImage6.jpg

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+.output
+.nuxt
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# mingnan-website
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```

+ 1 - 0
env.d.ts

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

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>闽南文化生态保护区(厦门市)</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 67 - 0
nuxt.config.ts

@@ -0,0 +1,67 @@
+// https://nuxt.com/docs/api/configuration/nuxt-config
+export default defineNuxtConfig({
+  compatibilityDate: '2025-05-15',
+  app: {
+    head: {
+      title: '闽南文化生态保护区(厦门市)',
+      viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
+      htmlAttrs: {
+        lang: 'zh',
+      },
+      link: [
+        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
+      ]
+    }
+  },
+  devtools: { enabled: true },
+  srcDir: 'src/',
+  modules: ['@pinia/nuxt', '@ant-design-vue/nuxt'],
+  components: [
+    {
+      path: '~/components',
+      pathPrefix: false,
+      extensions: ['.vue'],
+    }
+  ],
+  build: {
+    transpile: [
+      '@imengyu/vue-scroll-rect',
+      '@imengyu/imengyu-utils',
+    ],
+  },
+  vite: {
+    build: {
+      minify: 'terser',
+      terserOptions: {
+        compress: {
+          drop_console: true,
+          drop_debugger: true,
+        },
+      },
+    },
+  },
+  routeRules: {
+    //'/**': { swr: false, isr: false, headers: { 'cache-control': 'no-store, max-age=0' } },
+    
+    /**/
+    '/': { swr: 1800 },
+    '/about/': { swr: 1800 },
+    '/communicate/': { swr: 1800 },
+    '/fusion/': { swr: 1800 },
+    '/inheritor/': { swr: 1800 },
+    '/introduction/': { swr: 1800 },
+    '/news/': { swr: 1800 },
+    '/research/': { swr: 1800 },
+
+    '/introduction/**': { swr: true },
+    '/communicate/**': { swr: true },
+    '/fusion/**': { swr: true },
+    '/inheritor/**': { swr: true },
+    '/news/**': { swr: true },
+    '/research/**': { swr: true },
+    '/village/**': { swr: true },
+    
+
+    '/inheritor/submit': { ssr: false },
+  }
+})

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 13753 - 0
package-lock.json


+ 57 - 0
package.json

@@ -0,0 +1,57 @@
+{
+  "name": "wenbao-website",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "nuxt-build": "nuxt build",
+    "nuxt-dev": "nuxt dev",
+    "nuxt-generate": "nuxt generate",
+    "nuxt-preview": "nuxt preview",
+    "nuxt-postinstall": "nuxt prepare",
+    "dev": "nuxt dev",
+    "build": "nuxt build",
+    "updater": "node src/scripts/UpdateScript/index.mjs"
+  },
+  "dependencies": {
+    "@ant-design-vue/nuxt": "^1.4.6",
+    "@imengyu/imengyu-utils": "^0.0.19",
+    "@imengyu/js-request-transform": "^0.3.5",
+    "@imengyu/vue-dynamic-form": "^0.1.1",
+    "@imengyu/vue-scroll-rect": "^0.1.3",
+    "@pinia/nuxt": "^0.11.1",
+    "@vuemap/vue-amap": "^2.1.12",
+    "ant-design-vue": "^4.2.6",
+    "axios": "^1.9.0",
+    "bootstrap": "^5.3.0",
+    "lodash-es": "^4.17.21",
+    "md5": "^2.3.0",
+    "mitt": "^3.0.1",
+    "nuxt": "^3.17.6",
+    "pinia": "^3.0.1",
+    "tslib": "^2.8.1",
+    "vue": "^3.5.13",
+    "vue-router": "^4.5.0",
+    "vue3-carousel": "^0.15.0"
+  },
+  "devDependencies": {
+    "@inquirer/prompts": "^7.5.3",
+    "@tsconfig/node22": "^22.0.1",
+    "@types/node": "^22.14.0",
+    "@vitejs/plugin-vue": "^5.2.3",
+    "@vitejs/plugin-vue-jsx": "^4.1.2",
+    "@vue/tsconfig": "^0.7.0",
+    "ali-oss": "^6.23.0",
+    "archiver": "^7.0.1",
+    "cli-progress": "^3.12.0",
+    "cli-table3": "^0.6.5",
+    "commander": "^14.0.0",
+    "npm-run-all2": "^7.0.2",
+    "sass": "^1.87.0",
+    "terser": "^5.44.1",
+    "typescript": "~5.8.0",
+    "vite": "^6.2.4",
+    "vite-plugin-vue-devtools": "^7.7.2",
+    "vue-tsc": "^2.2.8"
+  }
+}

BIN
public/favicon.ico


+ 2 - 0
public/robots.txt

@@ -0,0 +1,2 @@
+User-Agent: *
+Disallow:

+ 3 - 0
server/tsconfig.json

@@ -0,0 +1,3 @@
+{
+  "extends": "../.nuxt/tsconfig.server.json"
+}

+ 65 - 0
src/App.vue

@@ -0,0 +1,65 @@
+<template>
+  <a-config-provider
+    :locale="zhCN"
+    :theme="{
+      token: {
+        colorPrimary: '#bd4b36',
+      },
+    }"
+  >
+    <NuxtLoadingIndicator />
+    <NavBar />
+    <main>
+      <NuxtPage />
+    </main>
+    <Footer />
+  </a-config-provider>
+</template>
+
+<script setup lang="ts">
+import { onMounted, watch } from 'vue';
+import { RouterView, useRoute } from 'vue-router'
+import NavBar from './components/NavBar.vue';
+import Footer from './components/Footer.vue';
+import { useAuthStore } from './stores/auth';
+import zhCN from 'ant-design-vue/es/locale/zh_CN';
+import VueAMap, {initAMapApiLoader} from '@vuemap/vue-amap';
+import { registryConvert } from './common/ConvertRgeistry'
+import { registerAllFormComponents } from './components/dynamicf';
+
+if (import.meta.client) {
+  initAMapApiLoader({
+    key: '212b86dc49a5116a34e945d31da7ad14',
+    securityJsCode: '46cae8205a707cfaf5801abcc4301566',
+  });
+  registerAllFormComponents();
+} 
+registryConvert();
+
+const authStore = useAuthStore();
+
+onMounted(() => {
+  if (import.meta.server)
+    return;
+  authStore.loadLoginState();
+});
+
+const route = useRoute();
+
+watch(route, () => {
+  window.scrollTo({
+    top: 0,
+    behavior: 'instant'
+  })
+});
+</script>
+
+<style>
+@import "bootstrap/dist/css/bootstrap.css";
+@import "bootstrap/dist/css/bootstrap-grid.css";
+@import "bootstrap/dist/css/bootstrap-utilities.css";
+@import "./assets/scss/main.scss";
+@import "vue3-carousel/carousel.css";
+@import "@vuemap/vue-amap/dist/style.css";
+@import "@imengyu/vue-scroll-rect/lib/vue-scroll-rect.css";
+</style>

+ 417 - 0
src/api/CommonContent.ts

@@ -0,0 +1,417 @@
+import { DataModel, transformArrayDataModel, type NewDataModel } from '@imengyu/js-request-transform';
+import { AppServerRequestModule } from './RequestModules';
+import type { QueryParams } from "@imengyu/imengyu-utils";
+import { transformSomeToArray } from './Utils';
+
+export class GetColumListParams extends DataModel<GetColumListParams> {
+  
+  public constructor() {
+    super(GetColumListParams);
+    this.setNameMapperCase('Camel', 'Snake');
+  }
+
+  setModelId(val: number) {
+    this.modelId = val;
+    return this;
+  }
+  setMainBodyColumnId(val: number) {
+    this.mainBodyColumnId = val;
+    return this;
+  }
+  setFlag(val: 'hot'|'recommend'|'top') {
+    this.flag = val;
+    return this; 
+  }
+  setSize(val: number) {
+    this.size = val;
+    return this; 
+  }
+
+  modelId?: number;
+  /**
+   * 	主体栏目id
+   */
+  mainBodyColumnId: number = 0;
+  /**
+   * 标志:hot=热门,recommend=推荐,top=置顶
+   */
+  flag ?: 'hot'|'recommend'|'top';
+  /**
+   * 内容数量,默认4
+   */
+  size = 4;
+}
+export class GetContentListParams extends DataModel<GetContentListParams> {
+  
+  public constructor() {
+    super(GetContentListParams);
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      ids: {
+        customToServerFn: (val) => (val as number[]).join(','),
+        customToClientFn: (val) => (val as string).split(',').map((item) => parseInt(item)),
+      },
+    }
+  }
+
+
+  setMainBodyColumnId(val: number|number[]) {
+    this.mainBodyColumnId = val;
+    return this;
+  }
+  setFlag(val: 'hot'|'recommend'|'top') {
+    this.flag = val;
+    return this; 
+  }
+  setIds(val: number[]) {
+    this.ids = val;
+    return this; 
+  }
+  setType(val: 1|2|3|4) {
+    this.type = val;
+    return this;
+  }
+  setSize(val: number) {
+    this.size = val;
+    return this;
+  }
+  setKeywords(val: string) {
+    this.keywords = val;
+    return this; 
+  }
+  setModelId(val: number) {
+    this.modelId = val;
+    return this; 
+  }
+
+  static TYPE_ARTICLE = 1;
+  static TYPE_AUDIO = 2;
+  static TYPE_VIDEO = 3;
+  static TYPE_IMAGE = 4;
+
+  modelId ?: number;
+  /**
+   * 主体栏目id
+   */
+  mainBodyColumnId: number|number[] = 0;
+  /**
+   * 标志:hot=热门,recommend=推荐,top=置顶
+   */
+  flag ?: 'hot'|'recommend'|'top';
+  /**
+   * 内容id(逗号隔开)如:3 或者 1,2,3
+   */
+  ids?: number[];
+  /**
+   * 类型:1=文章,2=音频,3=视频,4=相册
+   */
+  type?: 1|2|3|4;
+  /**
+   * 内容数量,默认4
+   */
+  size = 4;
+  /**
+   * 关键字查询
+   */
+  keywords?: string;
+
+}
+
+export class GetColumContentList extends DataModel<GetColumContentList> {
+  constructor() {
+    super(GetColumContentList, "主体栏目列表");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      name: { clientSide: 'string', serverSide: 'string', clientSideRequired: true },
+      content_list: { 
+        clientSide: 'array',
+        clientSideRequired: true,
+        clientSideChildDataModel: GetContentListItem,
+      },
+    }
+  }
+
+  name = '';
+  overview = '';
+}
+export class GetContentListItem extends DataModel<GetContentListItem> {
+  constructor() {
+    super(GetContentListItem, "内容列表");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      mainBodyColumnId: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      title: { clientSide: 'string', serverSide: 'string', clientSideRequired: true },
+      isGuest: { clientSide: 'boolean', serverSide: 'number' },
+      isLogin: { clientSide: 'boolean', serverSide: 'number' },
+      isComment: { clientSide: 'boolean', serverSide: 'number' },
+      isLike: { clientSide: 'boolean', serverSide: 'number' },
+      isCollect: { clientSide: 'boolean', serverSide: 'number' },
+      latitude: { clientSide: 'number', serverSide: 'number' },
+      longitude: { clientSide: 'number', serverSide: 'number' },
+      publishAt: { clientSide: 'date', serverSide: 'string' },
+      flag: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      tags: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      keywords: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      type: { clientSide: 'number', serverSide: 'number' },
+    };
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Time') || key.endsWith('At'))
+        return {
+          clientSide: 'date',
+          serverSide: 'string',
+        };
+      return undefined;
+    };
+  }
+  id = 0;
+  mainBodyColumnId = 0;
+  latitude = 0;
+  longitude = 0;
+  mapX = '';
+  mapY = '';
+  from = '';
+  modelId = 0;
+  title = '!title';
+  typeText = '';
+  region = 0;
+  image = '';
+  thumbnail = '';
+  desc = '!desc';
+  content = '!content';
+  type = 0;
+  keywords ?: string[];
+  flag ?: string[];
+  tags ?: string[];
+  views = 0;
+  comments = 0;
+  likes = 0;
+  collects = 0;
+  dislikes = 0;
+  district = '';
+  publishAt = new Date();
+}
+export class GetContentDetailItem extends DataModel<GetContentDetailItem> {
+  constructor() {
+    super(GetContentDetailItem, "内容详情");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      title: { clientSide: 'string', serverSide: 'string' },
+      content: { clientSide: 'string', serverSide: 'string' },
+      isGuest: { clientSide: 'boolean', serverSide: 'number' },
+      isLogin: { clientSide: 'boolean', serverSide: 'number' },
+      isComment: { clientSide: 'boolean', serverSide: 'number' },
+      isLike: { clientSide: 'boolean', serverSide: 'number' },
+      isCollect: { clientSide: 'boolean', serverSide: 'number' },
+      publishAt: { clientSide: 'date', serverSide: 'string' },
+      flag: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      tags: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      type: { clientSide: 'number', serverSide: 'number' },
+      ichSitesList: { clientSide: 'array', clientSideChildDataModel: GetContentDetailItem },
+      inheritorsList: { clientSide: 'array', clientSideChildDataModel: GetContentDetailItem },
+      otherLevel: { clientSide: 'array', clientSideChildDataModel: GetContentDetailItem },
+    }
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Time') || key.endsWith('At'))
+        return {
+          clientSide: 'date',
+          serverSide: 'string',
+        };
+      else if (key.endsWith('List')) {
+        return [
+          { clientSide: 'map', serverSide: 'original'},
+          { clientSide: 'array', clientSideChildDataModel: GetContentDetailItem, serverSide: 'original' },
+        ]
+      }
+      return undefined;
+    };
+    this._afterSolveServer = () => {
+      if (this.image === 'https://mncdn.wenlvti.net')
+        this.image = '';
+      if (!this.image && this.images && this.images && this.images.length > 0  ) {
+        this.image = this.images[0]
+      }
+      if ((!this.images || this.images.length == 0) && this.image && this.image != '') {
+        this.images = [ this.image ]
+      }
+      if (!this.images)
+        this.images = []
+      if (this.publishVideo)
+        this.video = this.publishVideo;
+    }
+  }
+
+  id = 0;
+  from = '';
+  modelId = 0;
+  type = 0;
+  title = '';
+  region = 0;
+  image = '';
+  images = [] as string[];
+  audio = '';
+  video = '';
+  desc = '';
+  flag ?: string[];
+  tags ?: string[];
+  publishVideo?: string;
+  views = 0;
+  comments = 0;
+  likes = 0;
+  collects = 0;
+  dislikes = 0;
+  isLogin = false;
+  isGuest = false;
+  isComment = false;
+  isLike = false;
+  isCollect = false;
+  content = '';
+  value = '';
+  intro = '';
+  publishAt = new Date();
+  associationMeList = [] as {
+    id: number,
+    title: string,
+    image: string,
+    thumbnail: string,
+  }[];
+  otherLevel : GetContentDetailItem[] = [];
+}
+
+export class CategoryListItem extends DataModel<CategoryListItem> {
+  constructor() {
+    super(CategoryListItem, "分类列表");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      pid: { clientSide: 'number', serverSide: 'number' },
+      haschild: { clientSide: 'boolean', serverSide: 'number' },
+    }
+  }
+
+  id !: number;
+  pid !: number;
+  title = '';
+  status = 'normal';
+  weight = 0;
+  spacer = '';
+  haschild = false;
+  children?: CategoryListItem[];
+}
+
+export class CommonContentApi extends AppServerRequestModule<DataModel> {
+
+  constructor(modelId: number, debugName: string, mainBodyColumnId?: number|number[]) {
+    super();
+    this.modelId = modelId;
+    this.mainBodyColumnId = mainBodyColumnId;
+    this.debugName = debugName;
+  }
+
+  public mainBodyColumnId?: number|number[];
+  public modelId: number;
+  protected debugName: string;
+
+  /**
+   * 获取分类列表
+   * @param type 根级类型:1=区域、2=级别、3=文物类型、4=非遗类型、42=事件类型
+   * @param withself 是否返回包含自己:true=是,false=否 ,默认false
+   * @returns 
+   */
+  async getCategoryList(
+    type?: number,
+    withself?: boolean,
+  ) {
+    return (this.get('/content/category/getCategoryList', '获取分类列表', {
+      type,
+      is_tree: false,
+      withself,
+    }))
+      .then(res => transformArrayDataModel<CategoryListItem>(CategoryListItem, res.data2, `获取分类列表`, true))
+      .catch(e => { throw e });
+  }
+  /**
+   * 用于获取某一个分类需要用的子级
+   * @param pid 父级
+   * @returns 
+   */
+  async getCategoryChildList(pid?: number) {
+    return (this.get('/content/category/getCategoryOnlyChildList', '获取分类子级列表', {
+      pid,
+    }))
+      .then(res => transformArrayDataModel<CategoryListItem>(
+        CategoryListItem, 
+        transformSomeToArray(res.data2), 
+        `获取分类列表`, 
+        true
+      ))
+      .catch(e => { throw e });
+  }
+
+  private toStringArray(arr: number|number[]|undefined) {
+    if (typeof arr === 'undefined') 
+      return '';
+    return typeof arr === 'object' ? arr.join(',') : arr.toString();
+  }
+
+  /**
+   * 主体栏目列表
+   * @param params 参数 
+   * @param querys 额外参数
+   * @returns 
+   */
+  getColumList<T extends DataModel = GetColumContentList>(params: GetColumListParams, modelClassCreator: NewDataModel = GetColumContentList, querys?: QueryParams) {
+    return this.get('/content/content/getMainBodyColumnContentList', `${this.debugName} 主体栏目列表`, {
+      model_id: this.modelId,
+      ...params.toServerSide(),
+      ...querys,
+    })
+      .then(res => ({
+        list: transformArrayDataModel<T>(modelClassCreator, res.data2.list, `${this.debugName} 主体栏目列表`, true),
+        total: res.data2.total as number,
+      }))
+      .catch(e => { throw e });
+  }
+  /**
+   * 模型内容列表
+   * @param params 参数
+   * @param page 页码
+   * @param pageSize 页大小
+   * @param querys 额外参数
+   * @returns 
+   */
+  getContentList<T extends DataModel = GetContentListItem>(params: GetContentListParams, page: number, pageSize: number = 10, modelClassCreator: NewDataModel = GetContentListItem, querys?: QueryParams) {
+    return this.get('/content/content/getContentList', `${this.debugName} 模型内容列表`, {
+      ...params.toServerSide(),
+      ...querys,
+      model_id: params.modelId || this.modelId,
+      main_body_column_id: this.toStringArray(params.mainBodyColumnId || this.mainBodyColumnId),
+      page,
+      pageSize,
+    })
+      .then(res => ({ 
+        list: transformArrayDataModel<T>(modelClassCreator, res.data2.list, `${this.debugName} 模型内容列表`, true),
+        total: res.data2.total as number,
+      }))
+      .catch(e => { throw e });
+  }
+  /**
+   * 内容详情
+   * @param id id 
+   * @param querys 额外参数
+   * @returns 
+   */
+  getContentDetail<T extends DataModel = GetContentDetailItem>(id: number, modelId?: number, modelClassCreator: NewDataModel = GetContentDetailItem, querys?: QueryParams) {
+    return this.get('/content/content/getContentDetail', `${this.debugName} (${id}) 内容详情`, {
+      model_id: modelId ?? this.modelId,
+      id,
+      ...querys,
+    }, modelClassCreator)
+      .then(res => res.data as T)
+      .catch(e => { throw e });
+  }
+}
+
+export default new CommonContentApi(0, '默认通用内容');

+ 12 - 0
src/api/NotConfigue.ts

@@ -0,0 +1,12 @@
+import { DataModel } from '@imengyu/js-request-transform';
+import { AppServerRequestModule } from './RequestModules';
+
+export class CommonApi extends AppServerRequestModule<DataModel> {
+
+  constructor() {
+    super();
+    this.config.modelClassCreator = DataModel;
+  }
+}
+
+export default new CommonApi();

+ 223 - 0
src/api/RequestModules.ts

@@ -0,0 +1,223 @@
+
+/**
+ * 这里写的是业务相关的:
+ * * 请求数据处理函数。
+ * * 自定义请求模块。
+ * * 自定义错误报告处理函数。
+ */
+
+import AppCofig from "@/common/config/AppCofig";
+import ApiCofig from "@/common/config/ApiCofig";
+import { 
+  RequestApiConfig,
+  RequestApiError, RequestApiResult, type RequestApiErrorType, 
+  RequestCoreInstance, RequestOptions, 
+  defaultResponseDataGetErrorInfo, defaultResponseDataHandlerCatch, 
+  RequestResponse,
+  WebFetchImplementer,
+  StringUtils,
+  appendGetUrlParams, 
+  appendPostParams,
+} from "@imengyu/imengyu-utils";
+import { logError } from "@/components/error/ErrorReporterIs";
+import type { DataModel, KeyValue, NewDataModel } from "@imengyu/js-request-transform";
+import { useAuthStore } from "@/stores/auth";
+import { Modal } from "ant-design-vue";
+
+/**
+ * 不报告错误的 code
+ */
+const notReportErrorCode = [401] as number[];
+const notReportMessages = [
+  /请授权绑定手机号/g,
+] as RegExp[];
+function matchNotReportMessage(str: string) {
+  for (let i = 0; i < notReportMessages.length; i++) {
+    if (notReportMessages[i].test(str))
+      return true;
+  }
+  return false;
+}
+
+//请求拦截器
+function requestInceptor(url: string, req: RequestOptions) {
+  //获取store中的token,追加到头;
+  const autoStore = useAuthStore();
+  if (StringUtils.isNullOrEmpty((req.header as KeyValue).token as string)) {
+    req.header['token'] = autoStore.token;
+    req.header['__token__'] = autoStore.token;
+  }
+  if (req.method == 'GET') {
+    //追加GET参数
+    url = appendGetUrlParams(url, 'main_body_id', ApiCofig.mainBodyId);
+  } else {
+    req.data = appendPostParams(req.data,'main_body_id', ApiCofig.mainBodyId);
+  }
+  return { newUrl: url, newReq: req };
+}
+//响应数据处理函数
+function responseDataHandler<T extends DataModel>(response: RequestResponse, req: RequestOptions, resultModelClass: NewDataModel|undefined, instance: RequestCoreInstance<T>, apiName: string | undefined): Promise<RequestApiResult<T>> {
+  return new Promise<RequestApiResult<T>>((resolve, reject) => {
+    const method = req.method || 'GET';
+    response.json().then((json) => {
+      if (response.ok) {
+        if (!json) {
+          reject(new RequestApiError(
+            'businessError',
+            '后端未返回数据',
+            '',
+            response.status,
+            null,
+            null,
+            req,
+            apiName,
+            response.url
+          ));
+          return;
+        }
+
+        //code == 0 错误
+        if (json.code === 0) {
+          handleError();
+          return;
+        }
+
+        //处理后端的数据
+        let message = '未知错误';
+        let data = {} as any;
+
+        //后端返回格式不统一,所以在这里处理格式
+        if (typeof json.data === 'object') {
+          data = json.data;
+          message = json.data?.msg || response.statusText;
+        }
+        else {
+          //否则返回上层对象
+          data = json;
+          message = json.msg || response.statusText;
+        }
+
+        resolve(new RequestApiResult(
+          resultModelClass ?? instance.config.modelClassCreator,
+          json?.code || response.status,
+          message,
+          data,
+          json
+        ));
+      }
+      else {
+        handleError();
+      }
+
+      function handleError() {
+        let errType : RequestApiErrorType = 'unknow';
+        let errString = '';
+        let errCodeStr = '';
+
+        if (typeof json.message === 'string') 
+          errString = json.message;
+        if (typeof json.msg === 'string') 
+          errString += json.msg;
+
+        if (StringUtils.isStringAllEnglish(errString))
+          errString = '服务器返回:' + errString;
+
+        //错误处理
+        if (errString) {
+          //如果后端有返回错误信息,则收集错误信息并返回
+          errType = 'businessError';
+          if (typeof json.data === 'object' && json.data?.errmsg) {
+            errString += '\n' + json.data.errmsg;
+          }
+          if (typeof json.errors === 'object') {
+            for (const key in json.errors) {
+              if (Object.prototype.hasOwnProperty.call(json.errors, key)) {
+                errString += '\n' + json.errors[key];
+              }
+            }
+          }
+        } else {
+          const res = defaultResponseDataGetErrorInfo(response, json);
+          errType = res.errType;
+          errString = res.errString;
+          errCodeStr = res.errCodeStr;
+        }
+
+        reject(new RequestApiError(
+          errType,
+          errString,
+          errCodeStr,
+          response.status,
+          null,
+          null,
+          req,
+          apiName,
+          response.url
+        ));
+      }
+    }).catch((err) => {
+      //错误统一处理
+      defaultResponseDataHandlerCatch(method, req, response, null, err, apiName, response.url, reject, instance);
+    });
+  });
+}
+//错误报告处理
+function responseErrReoprtInceptor<T extends DataModel>(instance: RequestCoreInstance<T>, response: RequestApiError) {
+  return (
+    (response.errorType !== 'businessError' && response.errorType !== 'networkError') ||
+    notReportErrorCode.indexOf(response.code) >= 0 ||
+    matchNotReportMessage(response.errorMessage) === true
+  );
+}
+
+//错误报告处理
+export function reportError<T extends DataModel>(instance: RequestCoreInstance<T>, response: RequestApiError | Error) {
+  if (import.meta.env.DEV) {
+    if (response instanceof RequestApiError) {
+      logError({
+        message: `请求错误 ${response.apiName} : ${response.errorMessage}`,
+        detail: response.toString() +
+          '\r\n请求接口:' + response.apiName +
+          '\r\n请求地址:' + response.apiUrl +
+          '\r\n请求参数:' + JSON.stringify(response.rawRequest) +
+          '\r\n返回参数:' + JSON.stringify(response.rawData) +
+          '\r\n状态码:' + response.code +
+          '\r\n信息:' + response.errorCodeMessage,
+        type: 'error',
+      });
+    } else {
+      logError({
+        message: '错误报告 代码错误',
+        detail: response?.stack || ('' + response),
+        type: 'error',
+      });
+    }
+  } else {    
+    let errMsg = '';
+    if (response instanceof RequestApiError)
+      errMsg = response.errorMessage + '。';
+      
+    errMsg += '服务出现了异常,请稍后重试或联系客服。';
+    errMsg += '版本:' + AppCofig.version;
+
+    Modal.error({
+      title: '抱歉',
+      content: errMsg,
+    });
+}
+}
+
+/**
+ * App服务请求模块
+ */
+export class AppServerRequestModule<T extends DataModel> extends RequestCoreInstance<T> {
+  constructor() {
+    super(WebFetchImplementer);
+    this.config.baseUrl = ApiCofig.serverProd;
+    this.config.errCodes = []; //
+    this.config.requestInceptor = requestInceptor;
+    this.config.responseDataHandler = responseDataHandler;
+    this.config.responseErrReoprtInceptor = responseErrReoprtInceptor;
+    this.config.reportError = reportError;
+  }
+}

+ 24 - 0
src/api/Test.ts

@@ -0,0 +1,24 @@
+import { DataModel } from '@imengyu/js-request-transform';
+import { DefaultRequestModule } from '../common/request';
+
+export class Test extends DataModel {
+  public constructor() {
+    super();
+    this._convertTable = {
+    };
+  }
+}
+
+export class TestApi extends DefaultRequestModule<Test> {
+
+  constructor() {
+    super();
+    this.config.modelClassCreator = Test;
+  }
+
+  getDataTest() {
+    return this.get('http://127.0.0.1', 'getDataTest');
+  }
+}
+
+export default new TestApi();

+ 15 - 0
src/api/Utils.ts

@@ -0,0 +1,15 @@
+export function transformSomeToArray(source: any) {
+  if (typeof source === 'string') 
+    return source.split(','); 
+  if (typeof source === 'object') {
+    if (source instanceof Array)
+      return source; 
+    else {
+      const arr = [];
+      for (const key in source)
+        arr.push(source[key]);
+      return arr;
+    }
+  }
+  return source;
+}

+ 68 - 0
src/api/auth/UserApi.ts

@@ -0,0 +1,68 @@
+import { DataModel } from '@imengyu/js-request-transform';
+import { AppServerRequestModule } from '../RequestModules';
+
+
+export class LoginResult extends DataModel<LoginResult> {
+  constructor() {
+    super(LoginResult, "登录结果");
+    this._convertTable = {
+      userInfo: { clientSide: 'object', clientSideChildDataModel: UserInfo },
+    };
+    this._nameMapperServer = {
+      'userinfo': 'userInfo',
+      'mainBodyUserInfo': 'userInfo',
+    }
+    this._afterSolveServer = () => {
+      if (this.mainBodyUserInfo) {
+        this.userInfo.token = this.mainBodyUserInfo.token;
+      }
+    };
+  }
+  userInfo !:UserInfo;
+  mainBodyUserInfo?:UserInfo;
+}
+export class UserInfo extends DataModel<UserInfo> {
+  constructor() {
+    super(UserInfo, "用户信息");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+    }
+  }
+
+  expiresIn = 0;
+  id = 0;
+  userId = 0;
+  mobile = '';
+  nickname = '';
+  avatar = '';
+  username = '';
+  token = '';
+}
+
+export class UserApi extends AppServerRequestModule<DataModel> {
+
+  constructor() {
+    super();
+  }
+  async loginAdmin(data?: {
+    account: string,
+    password: string,
+  }) {
+    const form = new FormData();
+    form.append('account', data?.account || '');
+    form.append('password', data?.password || '');
+    return (await this.post('/user/adminLogin', form, '登录', undefined, LoginResult)).data as LoginResult;
+  }
+  async getUserInfo(main_body_user_id: number) {
+    return (await this.post('/content/main_body_user/getMainBodyUser', {
+      main_body_user_id,
+    }, '获取用户信息', undefined, UserInfo)).data as UserInfo;
+  }
+  async refresh() {
+    return (await this.post('/content/main_body_user/refreshUser', {
+    }, '刷新用户', undefined, LoginResult)).data as LoginResult;
+  }
+}
+
+export default new UserApi();

BIN
src/assets/fonts/Impact.ttf


BIN
src/assets/fonts/Impact.woff


BIN
src/assets/fonts/Impact.woff2


BIN
src/assets/fonts/STSongti-SC-Black.ttf


BIN
src/assets/fonts/STSongti-SC-Black.woff


BIN
src/assets/fonts/STSongti-SC-Black.woff2


BIN
src/assets/fonts/SourceHanSerifCN-Bold.otf


BIN
src/assets/fonts/SourceHanSerifCN-Bold.ttf


BIN
src/assets/fonts/SourceHanSerifCN-Bold.woff


BIN
src/assets/fonts/SourceHanSerifCN-Bold.woff2


BIN
src/assets/fonts/nzgrRuyinZouZhangKai.ttf


BIN
src/assets/fonts/nzgrRuyinZouZhangKai.woff


BIN
src/assets/fonts/nzgrRuyinZouZhangKai.woff2


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
src/assets/images/404.svg


BIN
src/assets/images/BackArrow.png


BIN
src/assets/images/Bg1.jpg


BIN
src/assets/images/Bg2.jpg


BIN
src/assets/images/BgLong.jpg


BIN
src/assets/images/CloseMini.png


BIN
src/assets/images/DropDownArrow.png


BIN
src/assets/images/Icon@2x.png


BIN
src/assets/images/IconArrowRight.png


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 8 - 0
src/assets/images/IconInfo.svg


BIN
src/assets/images/ImageFailed.png


BIN
src/assets/images/LargeTitle1.png


BIN
src/assets/images/LargeTitle2.png


BIN
src/assets/images/LargeTitle3.png


BIN
src/assets/images/Logo2.png


BIN
src/assets/images/LogoIcon.png


BIN
src/assets/images/LogoIconDark.png


BIN
src/assets/images/TitleMiniHeader.png


BIN
src/assets/images/about/Banner.jpg


BIN
src/assets/images/about/IconLocation.png


BIN
src/assets/images/about/IconMail.png


BIN
src/assets/images/about/IconMobile.png


BIN
src/assets/images/about/Logo.png


BIN
src/assets/images/communicate/Banner.jpg


BIN
src/assets/images/communicate/Image1.jpg


BIN
src/assets/images/communicate/Image2.jpg


BIN
src/assets/images/communicate/Image3.jpg


BIN
src/assets/images/favicon.ico


BIN
src/assets/images/favicon.png


BIN
src/assets/images/footer/FooterPrinting.png


BIN
src/assets/images/footer/GonganLogo.png


BIN
src/assets/images/fusion/Banner.jpg


BIN
src/assets/images/fusion/Image1.jpg


BIN
src/assets/images/fusion/Image2.jpg


BIN
src/assets/images/fusion/Image3.jpg


BIN
src/assets/images/fusion/Image4.jpg


BIN
src/assets/images/fusion/Image5.jpg


BIN
src/assets/images/fusion/Image6.jpg


BIN
src/assets/images/index/Box1.png


BIN
src/assets/images/index/Box2.jpg


BIN
src/assets/images/index/Box3.jpg


BIN
src/assets/images/index/BoxPrinting1.png


BIN
src/assets/images/index/BoxPrinting2.png


BIN
src/assets/images/index/BoxPrinting4.png


BIN
src/assets/images/index/ButtonMore.png


BIN
src/assets/images/index/Introd.jpg


BIN
src/assets/images/index/IntrodLeft.png


BIN
src/assets/images/index/IntrodRight.jpg


BIN
src/assets/images/inheritor/Banner.jpg


BIN
src/assets/images/inheritor/Image1.jpg


BIN
src/assets/images/inheritor/Image10.jpg


BIN
src/assets/images/inheritor/Image11.jpg


BIN
src/assets/images/inheritor/Image2.jpg


BIN
src/assets/images/inheritor/Image3.jpg


BIN
src/assets/images/inheritor/Image4.jpg


BIN
src/assets/images/inheritor/Image5.jpg


BIN
src/assets/images/inheritor/Image6.jpg


BIN
src/assets/images/inheritor/Image7.jpg


BIN
src/assets/images/inheritor/Image8.jpg


BIN
src/assets/images/inheritor/Image9.jpg


BIN
src/assets/images/inheritor/LawsTest.jpg


BIN
src/assets/images/inheritor/SubmitButton.png


BIN
src/assets/images/introduction/Banner.jpg


BIN
src/assets/images/introduction/CategoryImage1.jpg


BIN
src/assets/images/introduction/CategoryImage2.jpg


BIN
src/assets/images/introduction/CategoryImage3.jpg


BIN
src/assets/images/introduction/CategoryImage4.jpg


BIN
src/assets/images/introduction/CategoryImage5.jpg


+ 0 - 0
src/assets/images/introduction/CategoryImage6.jpg


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä