瀏覽代碼

first commit

tangning 2 年之前
當前提交
3874cfc009
共有 93 個文件被更改,包括 8903 次插入0 次删除
  1. 15 0
      .eslintrc.cjs
  2. 28 0
      .gitignore
  3. 8 0
      .prettierrc.json
  4. 3 0
      .vscode/extensions.json
  5. 46 0
      README.md
  6. 8 0
      auto-imports.d.ts
  7. 30 0
      components.d.ts
  8. 1 0
      env.d.ts
  9. 13 0
      index.html
  10. 2941 0
      package-lock.json
  11. 42 0
      package.json
  12. 二進制
      public/favicon.ico
  13. 48 0
      src/App.vue
  14. 45 0
      src/api/api.ts
  15. 75 0
      src/assets/base.css
  16. 1 0
      src/assets/config.less
  17. 二進制
      src/assets/images/active.png
  18. 二進制
      src/assets/images/alipay56.png
  19. 二進制
      src/assets/images/baicon.png
  20. 二進制
      src/assets/images/icon/error.png
  21. 二進制
      src/assets/images/icon/success.png
  22. 二進制
      src/assets/images/icon/warn.png
  23. 二進制
      src/assets/images/logoCn.png
  24. 二進制
      src/assets/images/logoEn.png
  25. 二進制
      src/assets/images/paypal.png
  26. 二進制
      src/assets/images/scan-tip-en.png
  27. 二進制
      src/assets/images/scan-tip.png
  28. 二進制
      src/assets/images/wechat56.png
  29. 二進制
      src/assets/images/whietlogo.png
  30. 二進制
      src/assets/images/whietlogoEn.png
  31. 1 0
      src/assets/logo.svg
  32. 25 0
      src/assets/main.css
  33. 40 0
      src/components/HelloWorld.vue
  34. 5 0
      src/components/Qrcode/index.ts
  35. 112 0
      src/components/Qrcode/src/Qrcode.vue
  36. 37 0
      src/components/Qrcode/src/drawCanvas.ts
  37. 88 0
      src/components/Qrcode/src/drawLogo.ts
  38. 4 0
      src/components/Qrcode/src/qrcodePlus.ts
  39. 10 0
      src/components/Qrcode/src/toCanvas.ts
  40. 38 0
      src/components/Qrcode/src/typing.ts
  41. 86 0
      src/components/TheWelcome.vue
  42. 177 0
      src/components/Toast/Confirm.vue
  43. 45 0
      src/components/Toast/Toast.vue
  44. 58 0
      src/components/Toast/index.ts
  45. 86 0
      src/components/WelcomeItem.vue
  46. 7 0
      src/components/icons/IconCommunity.vue
  47. 7 0
      src/components/icons/IconDocumentation.vue
  48. 7 0
      src/components/icons/IconEcosystem.vue
  49. 7 0
      src/components/icons/IconSupport.vue
  50. 19 0
      src/components/icons/IconTooling.vue
  51. 8 0
      src/components/mobile/footer.vue
  52. 30 0
      src/components/mobile/header.vue
  53. 24 0
      src/components/mobile/index.vue
  54. 81 0
      src/components/pc/footer.vue
  55. 29 0
      src/components/pc/header.vue
  56. 45 0
      src/components/pc/index.vue
  57. 135 0
      src/i18n/en.js
  58. 25 0
      src/i18n/index.js
  59. 133 0
      src/i18n/zh.js
  60. 18 0
      src/main.ts
  61. 42 0
      src/router/index.ts
  62. 11 0
      src/stores/counter.ts
  63. 16 0
      src/stores/user.ts
  64. 115 0
      src/utils/api.js
  65. 41 0
      src/utils/file/base64Conver.ts
  66. 136 0
      src/utils/file/download.ts
  67. 106 0
      src/utils/index.ts
  68. 99 0
      src/utils/is.ts
  69. 31 0
      src/utils/request.js
  70. 41 0
      src/utils/status.ts
  71. 15 0
      src/views/AboutView.vue
  72. 9 0
      src/views/HomeView.vue
  73. 238 0
      src/views/mobile/index.vue
  74. 279 0
      src/views/pc/index.vue
  75. 16 0
      tsconfig.json
  76. 8 0
      tsconfig.node.json
  77. 24 0
      vite-project/.gitignore
  78. 3 0
      vite-project/.vscode/extensions.json
  79. 18 0
      vite-project/README.md
  80. 13 0
      vite-project/index.html
  81. 20 0
      vite-project/package.json
  82. 1 0
      vite-project/public/vite.svg
  83. 30 0
      vite-project/src/App.vue
  84. 1 0
      vite-project/src/assets/vue.svg
  85. 38 0
      vite-project/src/components/HelloWorld.vue
  86. 5 0
      vite-project/src/main.ts
  87. 80 0
      vite-project/src/style.css
  88. 1 0
      vite-project/src/vite-env.d.ts
  89. 18 0
      vite-project/tsconfig.json
  90. 9 0
      vite-project/tsconfig.node.json
  91. 7 0
      vite-project/vite.config.ts
  92. 49 0
      vite.config.ts
  93. 2692 0
      yarn.lock

+ 15 - 0
.eslintrc.cjs

@@ -0,0 +1,15 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  root: true,
+  'extends': [
+    'plugin:vue/vue3-essential',
+    'eslint:recommended',
+    '@vue/eslint-config-typescript',
+    '@vue/eslint-config-prettier/skip-formatting'
+  ],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  }
+}

+ 28 - 0
.gitignore

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

+ 8 - 0
.prettierrc.json

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

+ 3 - 0
.vscode/extensions.json

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

+ 46 - 0
README.md

@@ -0,0 +1,46 @@
+# payPcMobile
+
+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) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## 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 [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+    1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+    2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 8 - 0
auto-imports.d.ts

@@ -0,0 +1,8 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-auto-import
+export {}
+declare global {
+
+}

+ 30 - 0
components.d.ts

@@ -0,0 +1,30 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+import '@vue/runtime-core'
+
+export {}
+
+declare module '@vue/runtime-core' {
+  export interface GlobalComponents {
+    Confirm: typeof import('./src/components/Toast/Confirm.vue')['default']
+    Footer: typeof import('./src/components/mobile/footer.vue')['default']
+    Header: typeof import('./src/components/mobile/header.vue')['default']
+    HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
+    IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
+    IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
+    IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
+    IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
+    IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
+    Mobile: typeof import('./src/components/mobile/index.vue')['default']
+    Pc: typeof import('./src/components/pc/index.vue')['default']
+    Qrcode: typeof import('./src/components/Qrcode/src/Qrcode.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
+    Toast: typeof import('./src/components/Toast/Toast.vue')['default']
+    WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
+  }
+}

+ 1 - 0
env.d.ts

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

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Vite App</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

File diff suppressed because it is too large
+ 2941 - 0
package-lock.json


+ 42 - 0
package.json

@@ -0,0 +1,42 @@
+{
+  "name": "paypcmobile",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check build-only",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --noEmit",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+    "format": "prettier --write src/"
+  },
+  "dependencies": {
+    "@vueuse/core": "^9.13.0",
+    "axios": "^1.3.5",
+    "element-plus": "^2.3.3",
+    "less": "^4.1.3",
+    "pinia": "^2.0.32",
+    "qrcode.vue": "^3.3.4",
+    "vue": "^3.2.47",
+    "vue-i18n": "^9.2.2",
+    "vue-router": "^4.1.6"
+  },
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.2.0",
+    "@types/node": "^18.14.2",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "@vue/eslint-config-prettier": "^7.1.0",
+    "@vue/eslint-config-typescript": "^11.0.2",
+    "@vue/tsconfig": "^0.1.3",
+    "eslint": "^8.34.0",
+    "eslint-plugin-vue": "^9.9.0",
+    "npm-run-all": "^4.1.5",
+    "prettier": "^2.8.4",
+    "typescript": "~4.8.4",
+    "unplugin-auto-import": "^0.15.3",
+    "unplugin-vue-components": "^0.24.1",
+    "vite": "^4.1.4",
+    "vue-tsc": "^1.2.0"
+  }
+}

二進制
public/favicon.ico


+ 48 - 0
src/App.vue

@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import { RouterLink, RouterView } from 'vue-router'
+</script>
+
+<template>
+  <div class="app">
+    <RouterView />
+  </div>
+</template>
+
+<style scoped>
+header {
+  line-height: 1.5;
+  max-height: 100vh;
+}
+
+.logo {
+  display: block;
+  margin: 0 auto 2rem;
+}
+
+nav {
+  width: 100%;
+  font-size: 12px;
+  text-align: center;
+  margin-top: 2rem;
+}
+
+nav a.router-link-exact-active {
+  color: var(--color-text);
+}
+
+nav a.router-link-exact-active:hover {
+  background-color: transparent;
+}
+
+nav a {
+  display: inline-block;
+  padding: 0 1rem;
+  border-left: 1px solid var(--color-border);
+}
+
+nav a:first-of-type {
+  border: 0;
+}
+
+
+</style>

+ 45 - 0
src/api/api.ts

@@ -0,0 +1,45 @@
+import { request } from "@/utils/request";
+
+export const getOrder = (orderSn) => {
+  return request({
+    url: "/service/pay/order/info",
+    method: "get",
+    data:{
+        orderSn
+    },
+    config: {
+      timeout: 10000,
+      headers: {
+        "Content-Type": "application/json;charset=UTF-8",
+      },
+    },
+  });
+};
+
+export const openPay = (data) => {
+    return request({
+      url: "/service/pay/openPay",
+      method: "post",
+      data,
+      config: {
+        timeout: 10000,
+        headers: {
+            "Content-Type": "application/json;charset=UTF-8",
+        },
+      },
+    });
+  };
+
+
+export const getOrderInfo = (orderSn) => {
+    return request({
+      url: `/service/pay/order/info/${orderSn}`,
+      method: "get",
+      config: {
+        timeout: 10000,
+        headers: {
+            "Content-Type": "application/json;charset=UTF-8",
+        },
+      },
+    });
+  };

+ 75 - 0
src/assets/base.css

@@ -0,0 +1,75 @@
+/* color palette from <https://github.com/vuejs/theme> */
+:root {
+  --vt-c-white: #ffffff;
+  --vt-c-white-soft: #f8f8f8;
+  --vt-c-white-mute: #f2f2f2;
+
+  --vt-c-black: #181818;
+  --vt-c-black-soft: #222222;
+  --vt-c-black-mute: #282828;
+
+  --vt-c-indigo: #2c3e50;
+
+  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
+  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
+  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
+  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+
+  --vt-c-text-light-1: var(--vt-c-indigo);
+  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
+  --vt-c-text-dark-1: var(--vt-c-white);
+  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+}
+
+/* semantic color variables for this project */
+:root {
+  --color-background: var(--vt-c-white);
+  --color-background-soft: var(--vt-c-white-soft);
+  --color-background-mute: var(--vt-c-white-mute);
+
+  --color-border: var(--vt-c-divider-light-2);
+  --color-border-hover: var(--vt-c-divider-light-1);
+
+  --color-heading: var(--vt-c-text-light-1);
+  --color-text: var(--vt-c-text-light-1);
+
+  --section-gap: 160px;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-background: var(--vt-c-black);
+    --color-background-soft: var(--vt-c-black-soft);
+    --color-background-mute: var(--vt-c-black-mute);
+
+    --color-border: var(--vt-c-divider-dark-2);
+    --color-border-hover: var(--vt-c-divider-dark-1);
+
+    --color-heading: var(--vt-c-text-dark-1);
+    --color-text: var(--vt-c-text-dark-2);
+  }
+}
+
+*,
+*::before,
+*::after {
+  /* box-sizing: border-box; */
+  margin: 0;
+  position: relative;
+  font-weight: normal;
+}
+
+body {
+  min-height: 100vh;
+  margin: 0;
+  color: var(--color-text);
+  background: var(--color-background);
+  transition: color 0.5s, background-color 0.5s;
+  line-height: 1.6;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  font-size: 15px;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}

+ 1 - 0
src/assets/config.less

@@ -0,0 +1 @@
+@primary-color: #29b2ff;

二進制
src/assets/images/active.png


二進制
src/assets/images/alipay56.png


二進制
src/assets/images/baicon.png


二進制
src/assets/images/icon/error.png


二進制
src/assets/images/icon/success.png


二進制
src/assets/images/icon/warn.png


二進制
src/assets/images/logoCn.png


二進制
src/assets/images/logoEn.png


二進制
src/assets/images/paypal.png


二進制
src/assets/images/scan-tip-en.png


二進制
src/assets/images/scan-tip.png


二進制
src/assets/images/wechat56.png


二進制
src/assets/images/whietlogo.png


二進制
src/assets/images/whietlogoEn.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 25 - 0
src/assets/main.css

@@ -0,0 +1,25 @@
+@import './base.css';
+
+#app {
+  /* margin: 0 auto;
+  font-weight: normal; */
+}
+
+a,
+.green {
+  text-decoration: none;
+  color: hsla(160, 100%, 37%, 1);
+  transition: 0.4s;
+}
+
+@media (hover: hover) {
+  a:hover {
+    background-color: hsla(160, 100%, 37%, 0.2);
+  }
+}
+
+.contentPage{
+  max-width: 1366px;
+  margin: 0 auto;
+  min-width: 1200px;
+}

+ 40 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+defineProps<{
+  msg: string
+}>()
+</script>
+
+<template>
+  <div class="greetings">
+    <h1 class="green">{{ msg }}</h1>
+    <h3>
+      You’ve successfully created a project with
+      <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
+      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
+    </h3>
+  </div>
+</template>
+
+<style scoped>
+h1 {
+  font-weight: 500;
+  font-size: 2.6rem;
+  top: -10px;
+}
+
+h3 {
+  font-size: 1.2rem;
+}
+
+.greetings h1,
+.greetings h3 {
+  text-align: center;
+}
+
+@media (min-width: 1024px) {
+  .greetings h1,
+  .greetings h3 {
+    text-align: left;
+  }
+}
+</style>

+ 5 - 0
src/components/Qrcode/index.ts

@@ -0,0 +1,5 @@
+import { withInstall } from '@/utils';
+import qrCode from './src/Qrcode.vue';
+
+export const QrCode = withInstall(qrCode);
+export * from './src/typing';

+ 112 - 0
src/components/Qrcode/src/Qrcode.vue

@@ -0,0 +1,112 @@
+<template>
+  <div>
+    <component :is="tag" ref="wrapRef" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, watch, PropType, ref, unref, onMounted } from 'vue';
+  import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus';
+  import { toDataURL } from 'qrcode';
+  import { downloadByUrl } from '@/utils/file/download';
+  import { QrcodeDoneEventParams } from './typing';
+
+  export default defineComponent({
+    name: 'QrCode',
+    props: {
+      value: {
+        type: [String, Array] as PropType<string | any[]>,
+        default: null,
+      },
+      // 参数
+      options: {
+        type: Object as PropType<QRCodeRenderersOptions>,
+        default: null,
+      },
+      // 宽度
+      width: {
+        type: Number as PropType<number>,
+        default: 200,
+      },
+      // 中间logo图标
+      logo: {
+        type: [String, Object] as PropType<Partial<LogoType> | string>,
+        default: '',
+      },
+      // img 不支持内嵌logo
+      tag: {
+        type: String as PropType<'canvas' | 'img'>,
+        default: 'canvas',
+        validator: (v: string) => ['canvas', 'img'].includes(v),
+      },
+    },
+    emits: { done: (data: QrcodeDoneEventParams) => !!data, error: (error: any) => !!error },
+    setup(props, { emit }) {
+      const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null);
+      async function createQrcode() {
+        try {
+          const { tag, value, options = {}, width, logo } = props;
+          const renderValue = String(value);
+          const wrapEl = unref(wrapRef);
+
+          if (!wrapEl) return;
+
+          if (tag === 'canvas') {
+            const url: string = await toCanvas({
+              canvas: wrapEl,
+              width,
+              logo: logo as any,
+              content: renderValue,
+              options: options || {},
+            });
+            emit('done', { url, ctx: (wrapEl as HTMLCanvasElement).getContext('2d') });
+            return;
+          }
+
+          if (tag === 'img') {
+            const url = await toDataURL(renderValue, {
+              errorCorrectionLevel: 'H',
+              width,
+              ...options,
+            });
+            (unref(wrapRef) as HTMLImageElement).src = url;
+            emit('done', { url });
+          }
+        } catch (error) {
+          emit('error', error);
+        }
+      }
+      /**
+       * file download
+       */
+      function download(fileName?: string) {
+        let url = '';
+        const wrapEl = unref(wrapRef);
+        if (wrapEl instanceof HTMLCanvasElement) {
+          url = wrapEl.toDataURL();
+        } else if (wrapEl instanceof HTMLImageElement) {
+          url = wrapEl.src;
+        }
+        if (!url) return;
+        downloadByUrl({
+          url,
+          fileName,
+        });
+      }
+
+      onMounted(createQrcode);
+
+      // 监听参数变化重新生成二维码
+      watch(
+        props,
+        () => {
+          createQrcode();
+        },
+        {
+          deep: true,
+        },
+      );
+
+      return { wrapRef, download };
+    },
+  });
+</script>

+ 37 - 0
src/components/Qrcode/src/drawCanvas.ts

@@ -0,0 +1,37 @@
+import { toCanvas } from 'qrcode';
+import type { QRCodeRenderersOptions } from 'qrcode';
+import { RenderQrCodeParams, ContentType } from './typing';
+import { cloneDeep } from 'lodash-es';
+
+export const renderQrCode = ({
+  canvas,
+  content,
+  width = 0,
+  options: params = {},
+}: RenderQrCodeParams) => {
+  const options = cloneDeep(params);
+  // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
+  options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content);
+
+  return getOriginWidth(content, options).then((_width: number) => {
+    options.scale = width === 0 ? undefined : (width / _width) * 4;
+    return toCanvas(canvas, content, options);
+  });
+};
+
+// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
+function getOriginWidth(content: ContentType, options: QRCodeRenderersOptions) {
+  const _canvas = document.createElement('canvas');
+  return toCanvas(_canvas, content, options).then(() => _canvas.width);
+}
+
+// 对于内容少的QrCode,增大容错率
+function getErrorCorrectionLevel(content: ContentType) {
+  if (content.length > 36) {
+    return 'M';
+  } else if (content.length > 16) {
+    return 'Q';
+  } else {
+    return 'H';
+  }
+}

+ 88 - 0
src/components/Qrcode/src/drawLogo.ts

@@ -0,0 +1,88 @@
+import { isString } from '@/utils/is';
+import { RenderQrCodeParams, LogoType } from './typing';
+export const drawLogo = ({ canvas, logo }: RenderQrCodeParams) => {
+  if (!logo) {
+    return new Promise((resolve) => {
+      resolve((canvas as HTMLCanvasElement).toDataURL());
+    });
+  }
+  const canvasWidth = (canvas as HTMLCanvasElement).width;
+  const {
+    logoSize = 0.15,
+    bgColor = '#ffffff',
+    borderSize = 0.05,
+    crossOrigin,
+    borderRadius = 8,
+    logoRadius = 0,
+  } = logo as LogoType;
+
+  const logoSrc: string = isString(logo) ? logo : logo.src;
+  const logoWidth = canvasWidth * logoSize;
+  const logoXY = (canvasWidth * (1 - logoSize)) / 2;
+  const logoBgWidth = canvasWidth * (logoSize + borderSize);
+  const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2;
+
+  const ctx = canvas.getContext('2d');
+  if (!ctx) return;
+
+  // logo 底色
+  canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius);
+  ctx.fillStyle = bgColor;
+  ctx.fill();
+
+  // logo
+  const image = new Image();
+  if (crossOrigin || logoRadius) {
+    image.setAttribute('crossOrigin', crossOrigin || 'anonymous');
+  }
+  image.src = logoSrc;
+
+  // 使用image绘制可以避免某些跨域情况
+  const drawLogoWithImage = (image: CanvasImageSource) => {
+    ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
+  };
+
+  // 使用canvas绘制以获得更多的功能
+  const drawLogoWithCanvas = (image: HTMLImageElement) => {
+    const canvasImage = document.createElement('canvas');
+    canvasImage.width = logoXY + logoWidth;
+    canvasImage.height = logoXY + logoWidth;
+    const imageCanvas = canvasImage.getContext('2d');
+    if (!imageCanvas || !ctx) return;
+    imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
+
+    canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius);
+    if (!ctx) return;
+    const fillStyle = ctx.createPattern(canvasImage, 'no-repeat');
+    if (fillStyle) {
+      ctx.fillStyle = fillStyle;
+      ctx.fill();
+    }
+  };
+
+  // 将 logo绘制到 canvas上
+  return new Promise((resolve) => {
+    image.onload = () => {
+      logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
+      resolve((canvas as HTMLCanvasElement).toDataURL());
+    };
+  });
+};
+
+// copy来的方法,用于绘制圆角
+function canvasRoundRect(ctx: CanvasRenderingContext2D) {
+  return (x: number, y: number, w: number, h: number, r: number) => {
+    const minSize = Math.min(w, h);
+    if (r > minSize / 2) {
+      r = minSize / 2;
+    }
+    ctx.beginPath();
+    ctx.moveTo(x + r, y);
+    ctx.arcTo(x + w, y, x + w, y + h, r);
+    ctx.arcTo(x + w, y + h, x, y + h, r);
+    ctx.arcTo(x, y + h, x, y, r);
+    ctx.arcTo(x, y, x + w, y, r);
+    ctx.closePath();
+    return ctx;
+  };
+}

+ 4 - 0
src/components/Qrcode/src/qrcodePlus.ts

@@ -0,0 +1,4 @@
+// 参考 qr-code-with-logo 进行ts版本修改
+import { toCanvas } from './toCanvas';
+export * from './typing';
+export { toCanvas };

+ 10 - 0
src/components/Qrcode/src/toCanvas.ts

@@ -0,0 +1,10 @@
+import { renderQrCode } from './drawCanvas';
+import { drawLogo } from './drawLogo';
+import { RenderQrCodeParams } from './typing';
+export const toCanvas = (options: RenderQrCodeParams) => {
+  return renderQrCode(options)
+    .then(() => {
+      return options;
+    })
+    .then(drawLogo) as Promise<string>;
+};

+ 38 - 0
src/components/Qrcode/src/typing.ts

@@ -0,0 +1,38 @@
+import type { QRCodeSegment, QRCodeRenderersOptions } from 'qrcode';
+type Fn = (...arg: any) => any;
+export type ContentType = string | QRCodeSegment[];
+
+export type { QRCodeRenderersOptions };
+
+export type LogoType = {
+  src: string;
+  logoSize: number;
+  borderColor: string;
+  bgColor: string;
+  borderSize: number;
+  crossOrigin: string;
+  borderRadius: number;
+  logoRadius: number;
+};
+
+export interface RenderQrCodeParams {
+  canvas: any;
+  content: ContentType;
+  width?: number;
+  options?: QRCodeRenderersOptions;
+  logo?: LogoType | string;
+  image?: HTMLImageElement;
+  downloadName?: string;
+  download?: boolean | Fn;
+}
+
+export type ToCanvasFn = (options: RenderQrCodeParams) => Promise<unknown>;
+
+export interface QrCodeActionType {
+  download: (fileName?: string) => void;
+}
+
+export interface QrcodeDoneEventParams {
+  url: string;
+  ctx?: CanvasRenderingContext2D | null;
+}

+ 86 - 0
src/components/TheWelcome.vue

@@ -0,0 +1,86 @@
+<script setup lang="ts">
+import WelcomeItem from './WelcomeItem.vue'
+import DocumentationIcon from './icons/IconDocumentation.vue'
+import ToolingIcon from './icons/IconTooling.vue'
+import EcosystemIcon from './icons/IconEcosystem.vue'
+import CommunityIcon from './icons/IconCommunity.vue'
+import SupportIcon from './icons/IconSupport.vue'
+</script>
+
+<template>
+  <WelcomeItem>
+    <template #icon>
+      <DocumentationIcon />
+    </template>
+    <template #heading>Documentation</template>
+
+    Vue’s
+    <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
+    provides you with all information you need to get started.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <ToolingIcon />
+    </template>
+    <template #heading>Tooling</template>
+
+    This project is served and bundled with
+    <a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
+    recommended IDE setup is
+    <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
+    <a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
+    you need to test your components and web pages, check out
+    <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
+    <a href="https://on.cypress.io/component" target="_blank">Cypress Component Testing</a>.
+
+    <br />
+
+    More instructions are available in <code>README.md</code>.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <EcosystemIcon />
+    </template>
+    <template #heading>Ecosystem</template>
+
+    Get official tools and libraries for your project:
+    <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
+    <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
+    <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
+    <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
+    you need more resources, we suggest paying
+    <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
+    a visit.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <CommunityIcon />
+    </template>
+    <template #heading>Community</template>
+
+    Got stuck? Ask your question on
+    <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
+    Discord server, or
+    <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
+      >StackOverflow</a
+    >. You should also subscribe to
+    <a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
+    the official
+    <a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
+    twitter account for latest news in the Vue world.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <SupportIcon />
+    </template>
+    <template #heading>Support Vue</template>
+
+    As an independent project, Vue relies on community backing for its sustainability. You can help
+    us by
+    <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
+  </WelcomeItem>
+</template>

+ 177 - 0
src/components/Toast/Confirm.vue

@@ -0,0 +1,177 @@
+<template>
+  <div class="xtx-confirm">
+    <div class="wrapper" ref="target">
+      <div class="header" v-if="title">
+        <h3>{{ title }}</h3>
+        <a href="JavaScript:;" class="iconfont icon-close-new" @click="cancelCallback"></a>
+      </div>
+      <div class="body">
+        <div class="typeImg">
+          <img style="height:48px;width:48px" v-if="type == 'success'" src="@/assets/images/icon/success.png" alt="" />
+          <img style="height:48px;width:48px" v-else-if="type == 'warn'" src="@/assets/images/icon/warn.png" alt="" />
+          <img style="height:48px;width:48px" v-else src="@/assets/images/icon/error.png" alt="" />
+        </div>
+        <span>{{ options.text }}</span>
+      </div>
+      <div class="footer">
+        <button
+          @click="
+            cancelCallback;
+            close()
+          "
+          size="mini"
+          type="gray"
+        >
+          取消
+        </button>
+        <button
+          @click="confirmCallback;close"
+          size="mini"
+          type="primary"
+        >
+          确认
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+  
+  <script>
+// 注意:当前组件不是在 #app 下进行渲染,无法使用 #app 下的环境(全局组件,全局指令,原型属性函数)
+import { ref } from 'vue'
+// import { onClickOutside } from '@vueuse/core'
+export default {
+  name: 'showConfirm',
+  props: {
+    type: {
+      type: String,
+      default: 'success'
+    },
+    options: {
+      type: Object,
+      default: ()=>{
+        return {
+          title:'',
+          text:'',
+          type:'success',
+          callback:()=>{}
+        }
+      }
+    },
+    // 关闭方法
+    close: {
+      type: Function,
+      default: () => {}
+    },
+    // 确认按钮
+    confirmCallback: {
+      type: Function,
+      default: () => {}
+    },
+    // 取消按钮
+    cancelCallback: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  setup(props) {
+    // 点击 target 目标元素外部相当于点击了取消
+    const target = ref(null)
+    // onClickOutside(target, () => {
+    //   props.cancelCallback()
+    // })
+    return { target }
+  }
+}
+</script>
+
+<style scoped lang="less">
+.xtx-confirm {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 8888;
+  background: rgba(0, 0, 0, 0.5);
+  .wrapper {
+    width: calc(100% - 40px);
+    border-radius: 10px 10px 10px 10px;
+    background: #fff;
+    border-radius: 4px;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    .header,
+    .footer {
+      height: 50px;
+      line-height: 50px;
+      padding: 0 20px;
+      text-align: center;
+    }
+    .body {
+      padding: 30px 40px;
+      font-size: 16px;
+      font-family: PingFang SC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #202020;
+      line-height: 19px;
+      text-align: center;
+      .icon-warning {
+        color: red;
+        margin-right: 3px;
+        font-size: 16px;
+      }
+      .typeImg{
+        margin-bottom: 20px;
+      }
+    }
+    .footer {
+      //   text-align: right;
+      border-top: 1px solid #EBEBEB;
+      padding: 20px;
+      .xtx-button {
+        margin-left: 20px;
+      }
+      button{
+        height: 40px;
+        background: #29B2FF;
+        border-radius: 4px 4px 4px 4px;
+        opacity: 1;
+        font-size: 14px;
+        font-family: PingFang SC-Regular, PingFang SC;
+        font-weight: 400;
+        color: #FFFFFF;
+        border: none;
+        padding: 10px 50px;
+        text-align: center;
+        margin: 0 15px;
+      }
+    }
+    .header {
+      position: relative;
+      h3 {
+        font-weight: normal;
+        font-size: 18px;
+      }
+      a {
+        position: absolute;
+        right: 15px;
+        top: 15px;
+        font-size: 20px;
+        width: 20px;
+        height: 20px;
+        line-height: 20px;
+        text-align: center;
+        color: #999;
+        &:hover {
+          color: #666;
+        }
+      }
+    }
+  }
+}
+</style>
+  
+  

+ 45 - 0
src/components/Toast/Toast.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="toast-wrap">
+    <div class="toast">
+      {{ title }}
+    </div>
+  </div>
+</template>
+  <script lang="ts">
+export default {
+  name: 'Toast',
+  props: {
+    title: {
+      type: String,
+      default: '我是toast'
+    }
+  },
+  setup() {
+    return {}
+  }
+}
+</script>
+  <style lang="less" scoped>
+.toast-wrap {
+  z-index: 1000;
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0,0,0,0.5);
+}
+
+.toast {
+  max-width: 6rem;
+  color: #ffffff;
+  font-size: 14px;
+  text-align: center;
+  border-radius: 5px;
+  padding: 10px;
+  background: rgba(0, 0, 0, 0.8);
+}
+</style>

+ 58 - 0
src/components/Toast/index.ts

@@ -0,0 +1,58 @@
+import { createVNode, render } from 'vue';
+import Toast from '@/components/Toast/Toast.vue';
+import Confirm from '@/components/Toast/Confirm.vue'
+// 准备一个DOM容器
+const div = document.createElement('div');
+div.setAttribute('class', 'toast-wrapper');
+document.body.appendChild(div);
+const divConfirm = document.createElement('div');
+divConfirm.setAttribute('class', 'toast-wrapper-confirm');
+document.body.appendChild(divConfirm);
+let time: any = null;
+
+// 初始数据
+type title = string;
+interface ToastType {
+  title: title;
+  duration?: number;
+}
+interface showConfirmType {
+    title?:string;
+    text?:string;
+    callback?:Function;
+}
+type union = title | ToastType;
+let timer: any = null;
+export function show(options: union) {
+  let title, duration;
+  if (typeof options === 'string') {
+    title = options || '我是默认文案';
+    duration = 2000;
+  } else {
+    title = (options as ToastType).title || '我是默认文案';
+    duration = (options as ToastType).duration || 2000;
+  }
+  // 创建虚拟dom  (组件对象, props)
+  const vnode = createVNode(Toast, { title });
+  
+  // 把虚拟dom渲染到div
+  render(vnode, div);
+  clearTimeout(timer);
+  timer = setTimeout(() => {
+    render(null, div);
+  }, duration);
+}
+export function showConfirm(options:showConfirmType) {
+    // 创建虚拟dom  (组件对象, props)
+    function close() {
+        console.log('close')
+        render(null, divConfirm);
+    }
+    const vnode = createVNode(Confirm, {options, close,});
+    // 把虚拟dom渲染到div
+    render(vnode, divConfirm);
+    // clearTimeout(time);
+    // time = setTimeout(() => {
+    //   render(null, divConfirm);
+    // }, duration);
+}

+ 86 - 0
src/components/WelcomeItem.vue

@@ -0,0 +1,86 @@
+<template>
+  <div class="item">
+    <i>
+      <slot name="icon"></slot>
+    </i>
+    <div class="details">
+      <h3>
+        <slot name="heading"></slot>
+      </h3>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.item {
+  margin-top: 2rem;
+  display: flex;
+}
+
+.details {
+  flex: 1;
+  margin-left: 1rem;
+}
+
+i {
+  display: flex;
+  place-items: center;
+  place-content: center;
+  width: 32px;
+  height: 32px;
+
+  color: var(--color-text);
+}
+
+h3 {
+  font-size: 1.2rem;
+  font-weight: 500;
+  margin-bottom: 0.4rem;
+  color: var(--color-heading);
+}
+
+@media (min-width: 1024px) {
+  .item {
+    margin-top: 0;
+    padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
+  }
+
+  i {
+    top: calc(50% - 25px);
+    left: -26px;
+    position: absolute;
+    border: 1px solid var(--color-border);
+    background: var(--color-background);
+    border-radius: 8px;
+    width: 50px;
+    height: 50px;
+  }
+
+  .item:before {
+    content: ' ';
+    border-left: 1px solid var(--color-border);
+    position: absolute;
+    left: 0;
+    bottom: calc(50% + 25px);
+    height: calc(50% - 25px);
+  }
+
+  .item:after {
+    content: ' ';
+    border-left: 1px solid var(--color-border);
+    position: absolute;
+    left: 0;
+    top: calc(50% + 25px);
+    height: calc(50% - 25px);
+  }
+
+  .item:first-of-type:before {
+    display: none;
+  }
+
+  .item:last-of-type:after {
+    display: none;
+  }
+}
+</style>

File diff suppressed because it is too large
+ 7 - 0
src/components/icons/IconCommunity.vue


File diff suppressed because it is too large
+ 7 - 0
src/components/icons/IconDocumentation.vue


File diff suppressed because it is too large
+ 7 - 0
src/components/icons/IconEcosystem.vue


+ 7 - 0
src/components/icons/IconSupport.vue

@@ -0,0 +1,7 @@
+<template>
+  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
+    <path
+      d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
+    />
+  </svg>
+</template>

+ 19 - 0
src/components/icons/IconTooling.vue

@@ -0,0 +1,19 @@
+<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
+<template>
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    aria-hidden="true"
+    role="img"
+    class="iconify iconify--mdi"
+    width="24"
+    height="24"
+    preserveAspectRatio="xMidYMid meet"
+    viewBox="0 0 24 24"
+  >
+    <path
+      d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
+      fill="currentColor"
+    ></path>
+  </svg>
+</template>

+ 8 - 0
src/components/mobile/footer.vue

@@ -0,0 +1,8 @@
+<script setup lang="ts">
+</script>
+
+<template>
+  <div class="footer">
+    我的底部
+  </div>
+</template>

+ 30 - 0
src/components/mobile/header.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import logoCn from '@/assets/images/whietlogo.png'
+import logoEn from '@/assets/images/whietlogoEn.png'
+const language = ref('zh')
+</script>
+
+<template>
+  <div class="header">
+    <div class="logo">
+      <img :src="language === 'zh' ? logoCn : logoEn" alt="logo">
+    </div>
+  </div>
+</template>
+<style lang="less" scoped>
+.header{
+  height: 50px;
+  background: #202020;
+  border-radius: 0px 0px 0px 0px;
+  opacity: 1;
+  .logo{
+    text-align: center;
+    padding: 8px 0;
+    img{
+      // height: 20px;
+      width: 80px;
+    }
+  }
+}
+</style>

+ 24 - 0
src/components/mobile/index.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import Header from "./header.vue";
+// import Footer from "./footer.vue";
+</script>
+
+<template>
+    <div class="container">
+      <Header />
+      <router-view />
+    </div>
+</template>
+
+<style scoped>
+.container{
+  height: 100vh;
+  overflow-y: auto;
+  overflow-x: hidden;
+  *{
+    box-sizing: content-box;
+  }
+}
+</style>
+
+

+ 81 - 0
src/components/pc/footer.vue

@@ -0,0 +1,81 @@
+<script setup lang="ts">
+</script>
+
+<template>
+  <div class="footer">
+    <div class="contentPage">
+      <div class="relevant clear">
+        <p class="relevant-1">Copyright © 2022 4DAGE Co., Ltd. All rights reserved.</p>
+        <span>
+          <p class="relevant-2">
+          <a target="_blank" href="https://beian.miit.gov.cn/" class="a_class">
+            <img src="@/assets/images/baicon.png" alt="" />
+            粤ICP备14078495号
+          </a>
+        </p>
+        <p class="relevant-2">
+          <a
+            target="_blank"
+            href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44049102496647"
+            class="a_class"
+          >
+            <img src="@/assets/images/baicon.png" alt="" />
+            粤公网安备 44049102496647号
+          </a>
+        </p>
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+<style lang="less" scoped>
+.footer {
+  width: 100%;
+  height: 83px;
+  background: #202020;
+  border-radius: 0px 0px 0px 0px;
+  opacity: 1;
+  .contentPage{
+    height: 100%;
+  }
+  .relevant {
+    // border-top: 1px solid #909090;
+    color: #909090;
+    height: 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    &-1 {
+      float: left;
+      background-color: transparent;
+    }
+    &-2 {
+      float: right;
+      margin-left: 20px;
+    }
+    div {
+      margin-bottom: 8px;
+
+      a {
+        color: @primary-color;
+        font-size: 14px;
+        text-decoration: underline;
+        display: inline-block;
+        line-height: 25px;
+
+        &:nth-child(2) {
+          margin: 0 42px;
+        }
+      }
+    }
+    a {
+      color: #909090;
+    }
+    p {
+      font-size: 14px;
+      color: #909090;
+      margin-bottom: 8px;
+    }
+  }
+}
+</style>

+ 29 - 0
src/components/pc/header.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+import { ref } from 'vue'
+import logoCn from '@/assets/images/logoCn.png'
+import logoEn from '@/assets/images/logoEn.png'
+//得到i18n的locale
+const { locale } = useI18n();
+</script>
+
+<template>
+  <div class="header">
+    <div class="logo contentPage">
+      <img :src="locale === 'zh' ? logoCn : logoEn" alt="logo">
+    </div>
+  </div>
+</template>
+<style lang="less" scoped>
+.header{
+  height: 80px;
+  background: #FFFFFF;
+  box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.1);
+  border-radius: 0px 0px 0px 0px;
+  opacity: 1;
+  .logo{
+    height: 100%;
+    padding: 17px 0;
+  }
+}
+</style>

+ 45 - 0
src/components/pc/index.vue

@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import Header from "./header.vue";
+import Footer from "./footer.vue";
+</script>
+
+<template>
+    <div class="container">
+      <Header />
+      <div class="main_container">
+        <router-view class="contentPage" />
+      </div>
+      <Footer />
+    </div>
+</template>
+
+<style scoped>
+h1 {
+  font-weight: 500;
+  font-size: 2.6rem;
+  top: -10px;
+}
+
+h3 {
+  font-size: 1.2rem;
+}
+
+.greetings h1,
+.greetings h3 {
+  text-align: center;
+}
+.container{
+  min-height: 100vh;
+}
+.main_container{
+  min-height: calc(100vh - 163px);
+  background-color: #F7F7F7;
+}
+@media (min-width: 1024px) {
+  .greetings h1,
+  .greetings h3 {
+    text-align: left;
+  }
+}
+</style>
+

File diff suppressed because it is too large
+ 135 - 0
src/i18n/en.js


+ 25 - 0
src/i18n/index.js

@@ -0,0 +1,25 @@
+import { createI18n } from 'vue-i18n';
+import ZH from './zh.js';
+import EN from './en.js';
+const messages = {
+  zh: { ...ZH  },
+  en: { ...EN  },
+};
+let current = (localStorage && localStorage.getItem('language'))
+if (!current) {
+  current = window.navigator.language || window.navigator.userLanguage || null
+    if (current && !/^zh/.test(current)) {
+        console.log('自动获取浏览器语言:' + current)
+        current = 'en'
+    }else{
+      current = 'zh'
+    }
+}
+
+const i18n = createI18n({
+  legacy: false,
+  globalInjection: true,
+  locale: current,
+  messages
+});
+export default i18n;

+ 133 - 0
src/i18n/zh.js

@@ -0,0 +1,133 @@
+export default {
+    person: {
+        name: '张三',
+        hobby: '唱跳,rap,篮球'
+    },
+    mall: {
+        buyGoods: '购买商品',
+        "postage": "运费:顺丰包邮",
+        "baseVolume": "包含基础云容量",
+        "knowMore": "了解详情",
+        "standard": "标准套餐",
+        "addCou": "增值套餐(包含三脚架)",
+        "count": "数量",
+        "couTip": "是否需要添加推荐的配件(标准套餐不含以下配件)",
+        "jiaojia": "四维看看三脚架套装",
+        "usb": "四维深时高速固态 U 盘",
+        "battery": "四维深时专用电池",
+        "jiaojiaDetail": "三脚架 + 滑轮 + 云台",
+        "cart": "加入购物车",
+        "fahuo": "预计3日内发货",
+        "product": "产品说明",
+        "techDetail": "技术参数",
+        "widthDetail": "尺寸参数",
+        "purchaseTpis": "购买请联系:overseas@4dage.com",
+        "cartTitle": "购物车",
+        "xzsh": "新增收货地址",
+        "cartTitleTip": "本订单顺丰包邮",
+        "goods": "商品",
+        "price": "价格",
+        "mini": "小计",
+        "continueBuy": "继续选购",
+        "nowJieSuan": "立即结算",
+        "nowBuy": "立即购买",
+        "countNum": "共计{num}件商品",
+        "addBuy": "加入购物车",
+        "confirmOrder": "确认订单",
+        "submitOrder": "下单",
+        "receiveAddress": "收货地址",
+        "invoiceIntro": "发票信息",
+        "normalInvoice": "电子普通发票",
+        "zengzhiInvoice": "专用发票",
+        "people": "个人",
+        "enterprise": "企业",
+        "wenxinTip": "温馨提示",
+        "wenxinTipContent": "专票随商品一并寄出,若因特殊情况会于10天内顺丰寄送给您(如遇节假日稍有延迟)。请注意查收;增值税专用发票收到后请妥善保存,如退货请一同寄回,如退货专票未能寄回,则需扣除相应的税点。",
+        "goodsInfo": "商品信息",
+        "kankanPro": "四维看看 八目相机",
+        "zhijiaName": "四维看看三脚架套装",
+        "goodsTotalPrice": "商品总价",
+        "otherPrice": "税费及其它费用",
+        "yunfei": "运费",
+        "total": "合计",
+        "blackColor": "静谧黑",
+        "black": "黑色",
+        "silvery": "银色",
+        "voice": "容量套餐(10G)",
+        "zhijiaColor": "标准色",
+        "payOrder": "付款",
+        "addAddressTip": "请添加地址",
+        "payCenter": "支付中心",
+        "nowAccount": "当前帐号",
+        "payType": "支付方式",
+        "aliPay": "支付宝",
+        "wechatPay": "微信支付",
+        "payNum": "应付金额",
+        "wechat": "微信",
+        "payTip": "使用{type}app扫码完成支付",
+        "noGoodsConfirm": "请选择需要购买的商品",
+        "yunVoice": "关于云容量",
+        "editAddress": "编辑收货地址",
+        "receiver": "收货人",
+        "phone": "手机号码",
+        "area": "所在地区",
+        "addressDetail": "详细地址",
+        "nashui": "纳税人识别号",
+        "paySuccess": "恭喜您,支付成功!",
+        "paySuccessTip": "支付成功",
+        "payFail": "支付失败!请重新支付",
+        "fahuoTip": "我们将尽快安排为您发货!",
+        "reWriteToCameraTip": "……{count}秒后跳转至我的相机页面",
+        "reWriteToOrderTip": "……{count}秒后跳转至我的订单页面",
+        "member": {
+            "support": "支持",
+            "notSupport": "不支持",
+            "startUse": "开始使用",
+            "downloadPrice": "99元/次",
+            "memberTitle": "会员权益",
+            "privilege": "会员权益",
+            "buySuccessTip": "支付成功!请前往账户信息页授权相机使用",
+            "privilegeIntro": "适用于品牌宣传要求较高,灵活定制品牌信息,拍摄场景较多,小程序部署的专业用户或企业。",
+            "privilegeCapacity": "无限容量",
+            "privilegeCal": "弹性服务器免排队",
+            "mapping": '空间贴图',
+            "mappingbeautify": '场景美化调节',
+            "3dModel": '三维模型',
+            "privilegeDownload": "免费下载10次",
+            "privilegePrice": "5980",
+            "normal": "普通权益",
+            "normalIntro": "具备基础的场景编辑功能及有限云容量,适用于个人爱好者,或无强定制信息需求的团队。",
+            "normalCapacity": "10GB",
+            "normalCal": "根据实际情况排队",
+            "normalPrice": "免费",
+            "capacity": "相机云容量",
+            "customLogo": "自定义LOGO",
+            "customHot": "自定义热点样式",
+            "daikan": "带看",
+            "sceneCal": "场景计算",
+            "sceneDownload": "场景下载",
+            "sceneCoyp": "场景复制",
+            "quanjing": "四维全景制作",
+            "price": "年费",
+            "aboutMember": "关于会员权益",
+            "question1": "会员权益如何生效?",
+            "answer1": "答:用户点击购买会员权益,并付款成功后即开始生效。生效日期即权益的开始日期,用户可将会员权益对相机进行授权使用,即使不进行授权也视为生效。",
+            "question2": "如何授权会员权益?",
+            "answer2": "答:成功购买会员权益后,请您进入我的账号 - 账号信息页面,在页面下方的会员权益栏点击授权按钮,并输入相机S/N码完成授权。会员权益不可授权非本账号相机,您可在我的账号 - 我的相机页面先绑定相机后再进行授权。",
+            "question3": "会员权益是否可以更换授权相机?",
+            "answer3": "答:授权相机不影响权益的生效和结束。在会员权益生效期间,可更换授权给任意相机。但是要注意,如A相机为授权相机,且拍摄了多于10G的场景,当会员权益到期或被解除授权后,超出10G部分的场景将被封存。续费或相机重新被授权后,场景才能解封。",
+            "question4": "权益相机的场景还可以协作给他人吗?",
+            "answer4": "答:可以,而且权益相机拍摄的场景,协作给协作者后,协作者也可以同样享受针对此场景的会员权益功能。",
+            "question5": "场景更新LOGO后,权益到期会恢复默认吗?",
+            "answer5": "答:不会,用户在会员权益生效期间自定义的logo,在权益到期后,不会恢复为默认的四维看看logo。",
+            "question6": "拥有多台相机,可以购买多个会员权益吗?",
+            "answer6": "答:可以,每个会员权益,默认有效期为一年,一年后到期。用户可购买多个,分别授权给不同的相机。",
+            "question7": "什么是空间贴图?",
+            "answer7": "答:支持在漫游场景中任意位置张贴图片。功能位置:我的账号 / 场景管理 / 编辑 / 空间装饰。",
+            "question8": "什么是三维模型?",
+            "answer8": "答:支持上传您自己的模型,仅支持obj格式模型。模型文件需包含obj模型、贴图、mtl文件。功能位置:我的账号 / 场景管理 / 编辑 / 空间装饰。",
+            "question9": "什么是场景美化调节?",
+            "answer9": "答:支持对全景图的亮度、饱和度、色温、对比度等进行调节。功能位置:我的账号 / 场景管理 / 编辑 / 场景美化 / 调节。"
+        }
+    }
+};

+ 18 - 0
src/main.ts

@@ -0,0 +1,18 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+
+import App from './App.vue'
+import router from './router'
+import i18n from './i18n/index.js'
+
+import './assets/main.css'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+app.use(i18n)
+
+app.config.globalProperties.$cdn = 'https://4dscene.4dage.com/new4dkk/';
+
+app.mount('#app')

+ 42 - 0
src/router/index.ts

@@ -0,0 +1,42 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import HomeView from '../views/HomeView.vue'
+import pc from '../components/pc/index.vue'
+import mobile from '../components/mobile/index.vue'
+import pcHome from '../views/pc/index.vue'
+import mobileHome from '../views/mobile/index.vue'
+const routesP = [{
+  path: '/',
+  name: 'Pc',
+  redirect: '/',
+  component: pc,
+  children: [{
+    path: '/',
+    name: 'index',
+    component: pcHome
+  }]
+}]
+
+const routesM = [{
+  path: '/',
+  name: 'Mobile',
+  redirect: '/',
+  component: mobile,
+  children: [{
+    path: '/',
+    name: 'index',
+    component: mobileHome
+  }]
+}]
+
+var routes = [];
+if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
+  routes = routesM
+} else {
+  routes = routesP
+}
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes,
+})
+
+export default router

+ 11 - 0
src/stores/counter.ts

@@ -0,0 +1,11 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+  return { count, doubleCount, increment }
+})

+ 16 - 0
src/stores/user.ts

@@ -0,0 +1,16 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useUserStore = defineStore('user', () => {
+  const token = ref('')
+  const info = ref({
+    userName: '我的测试',
+  })
+  const isEur = ref(false)
+  const getToken = computed(() => token.value )
+  function setToken(newToken:string) {
+    token.value = newToken
+  }
+  isEur.value = window.location.hostname.includes('eur')
+  return { token, getToken, setToken, info ,isEur}
+})

+ 115 - 0
src/utils/api.js

@@ -0,0 +1,115 @@
+import axios from "axios";
+import { ElLoading, ElMessage } from "element-plus";
+import router from "@/router";
+// request是一个axios实例,每一个实例你都可以单独定制它的baseURL,超时时间,请求头和一些其他配置项。
+const baseUrl = import.meta.env.VITE_BASE_URL; //接口统一域名
+const instance = axios.create({
+    baseURL: baseUrl,
+    timeout: 60 * 1000, //设置超时
+    headers: {
+        "Content-Type": "application/json;charset=UTF-8;",
+        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
+        ".AspNetCore.Culture": "c=zh-Hans|uic=zh-Hans",
+    },
+});
+
+let loading;
+//正在请求的数量
+let requestCount = 0;
+//显示loading
+const showLoading = () => {
+    if (requestCount === 0) {
+        loading = ElLoading.service({
+            fullscreen: true,
+            text: "Loading  ",
+            background: "rgba(0, 0, 0, 0.7)",
+        });
+    }
+    requestCount++;
+};
+//隐藏loading
+const hideLoading = () => {
+    requestCount--;
+    if (requestCount == 0) {
+        loading.close();
+    }
+};
+
+//请求拦截器
+instance.interceptors.request.use(
+    (config) => {
+        showLoading();
+        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
+        //若请求方式为post,则将data参数转为JSON字符串
+        if (config.method === "POST") {
+            config.data = JSON.stringify(config.data);
+        }
+        return config;
+    },
+    (error) =>
+        // 对请求错误做些什么
+        Promise.reject(error)
+);
+
+//响应拦截器
+instance.interceptors.response.use(
+    (response) => {
+        hideLoading();
+        if (response.data.code == 402) {
+            ElMessage.error(response.data.msg);
+            router.push("/");
+        }else if(response.data.code !== 200){
+            ElMessage.error(response.data);
+            return response.data
+        }
+        else return response.data?.data;
+    },
+    (error) => {
+        hideLoading();
+        //响应错误
+        let message = "";
+        if (error.response && error.response.status) {
+            const status = error.response.status;
+            switch (status) {
+                case 400:
+                    message = "请求错误";
+                    break;
+                case 401:
+                    message = "请求错误";
+                    break;
+                case 404:
+                    message = "请求地址出错";
+                    break;
+                case 408:
+                    message = "请求超时";
+                    break;
+                case 500:
+                    message = "服务器内部错误!";
+                    break;
+                case 501:
+                    message = "服务未实现!";
+                    break;
+                case 502:
+                    message = "网关错误!";
+                    break;
+                case 503:
+                    message = "服务不可用!";
+                    break;
+                case 504:
+                    message = "网关超时!";
+                    break;
+                case 505:
+                    message = "HTTP版本不受支持";
+                    break;
+                default:
+                    message = "请求失败";
+            }
+
+            ElMessage.error(message);
+            return Promise.reject(error);
+        }
+        return Promise.reject(error);
+    }
+);
+
+export default instance;

+ 41 - 0
src/utils/file/base64Conver.ts

@@ -0,0 +1,41 @@
+/**
+ * @description: base64 to blob
+ */
+export function dataURLtoBlob(base64Buf: string): Blob {
+  const arr = base64Buf.split(',');
+  const typeItem = arr[0];
+  const mime = typeItem.match(/:(.*?);/)![1];
+  const bstr = atob(arr[1]);
+  let n = bstr.length;
+  const u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
+  }
+  return new Blob([u8arr], { type: mime });
+}
+
+/**
+ * img url to base64
+ * @param url
+ */
+export function urlToBase64(url: string, mineType?: string): Promise<string> {
+  return new Promise((resolve, reject) => {
+    let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>;
+    const ctx = canvas!.getContext('2d');
+
+    const img = new Image();
+    img.crossOrigin = '';
+    img.onload = function () {
+      if (!canvas || !ctx) {
+        return reject();
+      }
+      canvas.height = img.height;
+      canvas.width = img.width;
+      ctx.drawImage(img, 0, 0);
+      const dataURL = canvas.toDataURL(mineType || 'image/png');
+      canvas = null;
+      resolve(dataURL);
+    };
+    img.src = url;
+  });
+}

+ 136 - 0
src/utils/file/download.ts

@@ -0,0 +1,136 @@
+import { openWindow } from '..';
+import { dataURLtoBlob, urlToBase64 } from './base64Conver';
+// import * as XLSX from 'xlsx' // Vue3 版本
+// import fs from 'file-saver'
+// /**
+//  * Download online pictures
+//  * @param data
+//  * @param filename
+//  */
+//  export function exportElsxFile(json, fields, filename = '表格.xlsx') {
+//   json.forEach(item => {
+//       for (let i in item) {
+//           if (fields.hasOwnProperty(i)) {
+//               item[fields[i]] = item[i];
+//           }
+//           delete item[i]; //删除原先的对象属性
+//       }
+//   })
+
+//   let sheetName = filename //excel的文件名称
+//   let wb = XLSX.utils.book_new()  //工作簿对象包含一SheetNames数组,以及一个表对象映射表名称到表对象。XLSX.utils.book_new实用函数创建一个新的工作簿对象。
+//   let ws = XLSX.utils.json_to_sheet(json, { header: Object.values(fields) }) //将JS对象数组转换为工作表。
+//   wb.SheetNames.push(sheetName)
+//   wb.Sheets[sheetName] = ws
+//   const defaultCellStyle = { font: { name: "Verdana", sz: 13, color: "FF00FF88" }, fill: { fgColor: { rgb: "FFFFAA00" } } };//设置表格的样式
+//   let wopts = { bookType: 'xlsx', bookSST: false, type: 'binary', cellStyles: true, defaultCellStyle: defaultCellStyle, showGridLines: false }  //写入的样式
+//   let wbout = XLSX.write(wb, wopts)
+//   let blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' })
+//   fs.saveAs(blob, filename + '.xlsx')
+// }
+// const s2ab = s => {
+//   if (typeof ArrayBuffer !== 'undefined') {
+//       var buf = new ArrayBuffer(s.length)
+//       var view = new Uint8Array(buf)
+//       for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
+//       return buf
+//   } else {
+//       var buf = new Array(s.length);
+//       for (var i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xFF;
+//       return buf;
+//   }
+// }
+
+/**
+ * Download online pictures
+ * @param url
+ * @param filename
+ * @param mime
+ * @param bom
+ */
+export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) {
+  urlToBase64(url).then((base64) => {
+    downloadByBase64(base64, filename, mime, bom);
+  });
+}
+
+/**
+ * Download pictures based on base64
+ * @param buf
+ * @param filename
+ * @param mime
+ * @param bom
+ */
+export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) {
+  const base64Buf = dataURLtoBlob(buf);
+  downloadByData(base64Buf, filename, mime, bom);
+}
+
+/**
+ * Download according to the background interface file stream
+ * @param {*} data
+ * @param {*} filename
+ * @param {*} mime
+ * @param {*} bom
+ */
+export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) {
+  const blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
+  const blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
+
+  const blobURL = window.URL.createObjectURL(blob);
+  const tempLink = document.createElement('a');
+  tempLink.style.display = 'none';
+  tempLink.href = blobURL;
+  tempLink.setAttribute('download', filename);
+  if (typeof tempLink.download === 'undefined') {
+    tempLink.setAttribute('target', '_blank');
+  }
+  document.body.appendChild(tempLink);
+  tempLink.click();
+  document.body.removeChild(tempLink);
+  window.URL.revokeObjectURL(blobURL);
+}
+
+/**
+ * Download file according to file address
+ * @param {*} sUrl
+ */
+export function downloadByUrl({
+  url,
+  target = '_blank',
+  fileName,
+}: {
+  url: string;
+  target?: TargetContext;
+  fileName?: string;
+}): boolean {
+  const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
+  const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1;
+
+  if (/(iP)/g.test(window.navigator.userAgent)) {
+    console.error('Your browser does not support download!');
+    return false;
+  }
+  if (isChrome || isSafari) {
+    const link = document.createElement('a');
+    link.href = url;
+    link.target = target;
+
+    if (link.download !== undefined) {
+      link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length);
+    }
+
+    if (document.createEvent) {
+      const e = document.createEvent('MouseEvents');
+      e.initEvent('click', true, true);
+      link.dispatchEvent(e);
+      return true;
+    }
+  }
+  if (url.indexOf('?') === -1) {
+    url += '?download';
+  }
+
+  openWindow(url, { target });
+  return true;
+}

+ 106 - 0
src/utils/index.ts

@@ -0,0 +1,106 @@
+import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
+import type { App, Plugin } from 'vue';
+
+import { unref } from 'vue';
+import { isObject } from '@/utils/is';
+
+export const noop = () => { };
+
+/**
+ * @description:  Set ui mount node
+ */
+export function getPopupContainer(node?: HTMLElement): HTMLElement {
+  return (node?.parentNode as HTMLElement) ?? document.body;
+}
+
+/**
+ * Add the object as a parameter to the URL
+ * @param baseUrl url
+ * @param obj
+ * @returns {string}
+ * eg:
+ *  let obj = {a: '3', b: '4'}
+ *  setObjToUrlParams('www.baidu.com', obj)
+ *  ==>www.baidu.com?a=3&b=4
+ */
+export function setObjToUrlParams(baseUrl: string, obj: any): string {
+  let parameters = '';
+  for (const key in obj) {
+    parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
+  }
+  parameters = parameters.replace(/&$/, '');
+  return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
+}
+
+export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
+  let key: string;
+  for (key in target) {
+    src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
+  }
+  return src;
+}
+
+export function openWindow(
+  url: string,
+  opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean },
+) {
+  const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
+  const feature: string[] = [];
+
+  noopener && feature.push('noopener=yes');
+  noreferrer && feature.push('noreferrer=yes');
+
+  window.open(url, target, feature.join(','));
+}
+
+// dynamic use hook props
+export function getDynamicProps<T, U>(props: T): Partial<U> {
+  const ret: Recordable = {};
+
+  Object.keys(props).map((key) => {
+    ret[key] = unref((props as Recordable)[key]);
+  });
+
+  return ret as Partial<U>;
+}
+
+export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized {
+  if (!route) return route;
+  const { matched, ...opt } = route;
+  return {
+    ...opt,
+    matched: (matched
+      ? matched.map((item) => ({
+        meta: item.meta,
+        name: item.name,
+        path: item.path,
+      }))
+      : undefined) as RouteRecordNormalized[],
+  };
+}
+
+export const withInstall = <T>(component: T, alias?: string) => {
+  const comp = component as any;
+  comp.install = (app: App) => {
+    app.component(comp.name || comp.displayName, component);
+    if (alias) {
+      app.config.globalProperties[alias] = component;
+    }
+  };
+  return component as T & Plugin;
+};
+
+export function GetRequest(value: string) {
+  var url = decodeURI(window.location.search); //?id="123456"&name="www";
+  var object = {};
+  if (url.indexOf("?") != -1)//url中存在问号,也就说有参数。  
+  {
+    var str = url.substr(1);  //得到?后面的字符串
+    var strs = str.split("&");  //将得到的参数分隔成数组[id="123456",name="www"];
+    for (var i = 0; i < strs.length; i++) {
+      object[strs[i].split("=")[0]] = strs[i].split("=")[1];//得到{id:'123456',name:'www'}
+    }
+  }
+  console.log('GetRequest',object)
+  return object[value];
+}

+ 99 - 0
src/utils/is.ts

@@ -0,0 +1,99 @@
+const toString = Object.prototype.toString;
+
+export function is(val: unknown, type: string) {
+  return toString.call(val) === `[object ${type}]`;
+}
+
+export function isDef<T = unknown>(val?: T): val is T {
+  return typeof val !== 'undefined';
+}
+
+export function isUnDef<T = unknown>(val?: T): val is T {
+  return !isDef(val);
+}
+
+export function isObject(val: any): val is Record<any, any> {
+  return val !== null && is(val, 'Object');
+}
+
+export function isEmpty<T = unknown>(val: T): val is T {
+  if (isArray(val) || isString(val)) {
+    return val.length === 0;
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0;
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0;
+  }
+
+  return false;
+}
+
+export function isDate(val: unknown): val is Date {
+  return is(val, 'Date');
+}
+
+export function isNull(val: unknown): val is null {
+  return val === null;
+}
+
+export function isNullAndUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) && isNull(val);
+}
+
+export function isNullOrUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) || isNull(val);
+}
+
+export function isNumber(val: unknown): val is number {
+  return is(val, 'Number');
+}
+
+export function isPromise<T = any>(val: unknown): val is Promise<T> {
+  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
+}
+
+export function isString(val: unknown): val is string {
+  return is(val, 'String');
+}
+
+export function isFunction(val: unknown): val is Function {
+  return typeof val === 'function';
+}
+
+export function isBoolean(val: unknown): val is boolean {
+  return is(val, 'Boolean');
+}
+
+export function isRegExp(val: unknown): val is RegExp {
+  return is(val, 'RegExp');
+}
+
+export function isArray(val: any): val is Array<any> {
+  return val && Array.isArray(val);
+}
+
+export function isWindow(val: any): val is Window {
+  return typeof window !== 'undefined' && is(val, 'Window');
+}
+
+export function isElement(val: unknown): val is Element {
+  return isObject(val) && !!val.tagName;
+}
+
+export function isMap(val: unknown): val is Map<any, any> {
+  return is(val, 'Map');
+}
+
+export const isServer = typeof window === 'undefined';
+
+export const isClient = !isServer;
+
+export function isUrl(path: string): boolean {
+  const reg =
+    /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
+  return reg.test(path);
+}

+ 31 - 0
src/utils/request.js

@@ -0,0 +1,31 @@
+import instance from "./api.js";
+/**
+ * @param {String} method  请求的方法:get、post、delete、put
+ * @param {String} url     请求的url:
+ * @param {Object} data    请求的参数
+ * @param {Object} config  请求的配置
+ * @returns {Promise}     返回一个promise对象,其实就相当于axios请求数据的返回值
+ */
+
+export const request = ({ method, url, data, config }) => {
+  //把大写转换成小写
+  method = method.toLowerCase();
+  if (method == "post") {
+    return instance.post(url, data, { ...config });
+  } else if (method == "get") {
+    return instance.get(url, {
+      params: data,
+      ...config,
+    });
+  } else if (method == "delete") {
+    return instance.delete(url, {
+      params: data,
+      ...config,
+    });
+  } else if (method == "put") {
+    return instance.put(url, data, { ...config });
+  } else {
+    console.error("未知的method" + method);
+    return false;
+  }
+};

+ 41 - 0
src/utils/status.ts

@@ -0,0 +1,41 @@
+export const showMessage = (status:number|string) : string => {
+    let message:string = "";
+    switch (status) {
+        case 400:
+            message = "请求错误(400)";
+            break;
+        case 401:
+            message = "未授权,请重新登录(401)";
+            break;
+        case 403:
+            message = "拒绝访问(403)";
+            break;
+        case 404:
+            message = "请求出错(404)";
+            break;
+        case 408:
+            message = "请求超时(408)";
+            break;
+        case 500:
+            message = "服务器错误(500)";
+            break;
+        case 501:
+            message = "服务未实现(501)";
+            break;
+        case 502:
+            message = "网络错误(502)";
+            break;
+        case 503:
+            message = "服务不可用(503)";
+            break;
+        case 504:
+            message = "网络超时(504)";
+            break;
+        case 505:
+            message = "HTTP版本不受支持(505)";
+            break;
+        default:
+            message = `连接出错(${status})!`;
+    }
+    return `${message},请检查网络或联系管理员!`;
+};

+ 15 - 0
src/views/AboutView.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="about">
+    <h1>This is an about page</h1>
+  </div>
+</template>
+
+<style>
+@media (min-width: 1024px) {
+  .about {
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 9 - 0
src/views/HomeView.vue

@@ -0,0 +1,9 @@
+<script setup lang="ts">
+import TheWelcome from '../components/TheWelcome.vue'
+</script>
+
+<template>
+  <main>
+    <div>test</div>
+  </main>
+</template>

+ 238 - 0
src/views/mobile/index.vue

@@ -0,0 +1,238 @@
+<script setup lang="ts">
+import { show, showConfirm } from '@/components/Toast'
+import { ref, computed, onMounted } from 'vue'
+import { useUserStore } from '@/stores/user'
+import { openPay, getOrderInfo } from '@/api/api'
+import { GetRequest } from '@/utils/index'
+const payType = ref('0')
+const orderSn = ref(GetRequest('orderSn'))
+const orderDetal = ref({})
+const is_weixn = computed(() => {
+  if (typeof WeixinJSBridge == 'undefined') {
+    return false
+  } else {
+    return true
+  }
+})
+let PAYSID = {
+  wechatPay: is_weixn.value ? 1 : 0,
+  alipay: 4,
+  paypal: 5
+}
+//判断是否微信
+onMounted(() => {
+  getDetial()
+})
+const { token, info, isEur } = useUserStore()
+async function handelPay() {
+  let apiData = {
+    orderSn: GetRequest('orderSn'),
+    payType: !is_weixn.value?'1':payType.value,
+    openId: 'dolor exercitation velit'
+  }
+  const res = await openPay(apiData)
+  console.log('apiData', res)
+  if (is_weixn.value) {
+    //微信内支付
+    onBridgeReady(res)
+  } else {
+    window.location.href = res.form || res.h5Url;
+  }
+}
+// 调微信支付
+function onBridgeReady(obj) {
+  WeixinJSBridge.invoke(
+    'getBrandWCPayRequest',
+    {
+      appId: obj.appid,
+      //公众号名称,由商户传入
+      timeStamp: obj.timeStamp + '',
+      //时间戳,自1970年以来的秒数
+      nonceStr: obj.nonceStr,
+      //随机串
+      package: `prepay_id=${obj.prepayId}`,
+      signType: obj.signType,
+      //微信签名方式:
+      paySign: obj.paySign
+    },
+    function (res) {
+      if (res.err_msg == 'get_brand_wcpay_request:ok') {
+        // 使用以上方式判断前端返回,微信团队郑重提示:
+        //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
+        showConfirm({
+          text: '成功了',
+          type: 'warn',
+          callback: (val) => {
+            console.log('我的测试计划', val)
+          }
+        })
+        console.log('成功了', res)
+      } else {
+        console.log('失败了', res)
+        showConfirm({
+          text: '失败了',
+          type: 'warn',
+          callback: (val) => {
+            console.log('我的测试计划', val)
+          }
+        })
+      }
+    }
+  )
+}
+function handleType(params: string) {
+  payType.value = params
+}
+function getDetial() {
+  getOrderInfo(orderSn.value).then((res) => {
+    orderDetal.value = res
+  })
+}
+async function handleOpenPay() {
+  openPay({
+    orderSn: GetRequest('orderSn'),
+    payType: payType.value,
+    openId: ''
+  }).then((res) => {
+    let url = res.qrCodeUrl
+  })
+}
+</script>
+<template>
+  <div class="mobilePage">
+    <div class="pageTitle">支付中心</div>
+    <div class="contentInfo">
+      <div class="info">
+        <div class="cell">
+          <span>当前账号</span>
+          <span>15915816041</span>
+        </div>
+        <div class="cell">
+          <span>购买会员</span>
+          <span>专业会员 1 ,有效期1年</span>
+        </div>
+      </div>
+      <div class="payType">
+        <div class="cell" @click="handleType('0')">
+          <span>
+            <img class="payTypeImg" src="@/assets/images/wechat56.png" alt="" />
+            微信支付
+          </span>
+          <div class="select" :class="{ active: payType == '0' }"></div>
+        </div>
+        <div class="cell" @click="handleType('4')" v-if="!is_weixn">
+          <span>
+            <img class="payTypeImg" src="@/assets/images/alipay56.png" alt="" />
+            支付宝支付
+          </span>
+          <div class="select" :class="{ active: payType == '4' }"></div>
+        </div>
+        <div class="cell" @click="handleType('5')" v-if="isEur">
+          <span>
+            <img class="payTypeImg" src="@/assets/images/paypal.png" alt="" />
+            paypal
+          </span>
+          <div class="select" :class="{ active: payType == '5' }"></div>
+        </div>
+      </div>
+    </div>
+    <div class="bottomInfo">
+      <div class="price">
+        应付金额<span>¥{{ orderDetal.orderMoney }}</span>
+      </div>
+      <div class="payBut" @click="handelPay">付款</div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.mobilePage {
+  background: #f7f7f7;
+  min-height: calc(100vh - 50px);
+  max-width: 100vw;
+  display: block;
+  .pageTitle {
+    width: calc(100% - 40px);
+    padding: 23px 20px;
+    background: #ffffff;
+    border-radius: 0px 0px 0px 0px;
+    opacity: 1;
+    border: 1px solid #e6e6e6;
+    margin-bottom: 10px;
+  }
+  .contentInfo {
+    .cell {
+      display: flex;
+      justify-content: space-between;
+      background-color: #fff;
+      padding: 15px 20px;
+      border-bottom: 1px solid #ebedf0;
+      align-items: center;
+      &:last-child {
+        border-bottom: none;
+      }
+      .select {
+        border-radius: 50%;
+        height: 20px;
+        width: 20px;
+        border: 1px solid #d5d8de;
+      }
+      .active {
+        background: url('@/assets/images/active.png');
+        background-position: center center;
+        background-size: 22px;
+        height: 22px;
+        width: 22px;
+        border: none;
+      }
+      .payTypeImg {
+        height: 18px;
+        width: 20px;
+        margin-right: 8px;
+        position: relative;
+        top: 3px;
+      }
+    }
+  }
+  .bottomInfo {
+    height: 56px;
+    width: 100%;
+    border-radius: 0px 0px 0px 0px;
+    display: flex;
+    align-items: center;
+    background-color: #fff;
+    justify-content: space-between;
+    position: fixed;
+    bottom: 0px;
+    .price {
+      padding: 5px 20px;
+      span {
+        font-size: 16px;
+        font-family: PingFang SC-Regular, PingFang SC;
+        font-weight: 400;
+        color: #ff0000;
+        line-height: 19px;
+        letter-spacing: 4px;
+      }
+    }
+    .payBut {
+      background: @primary-color;
+      border-radius: 0px 0px 0px 0px;
+      opacity: 1;
+      width: 120px;
+      line-height: 56px;
+      height: 100%;
+      color: #fff;
+      text-align: center;
+    }
+  }
+  .info {
+    margin-bottom: 10px;
+    font-size: 16px;
+    font-family: PingFang SC-Regular, PingFang SC;
+    font-weight: 400;
+    color: #202020;
+    line-height: 19px;
+  }
+}
+</style>

+ 279 - 0
src/views/pc/index.vue

@@ -0,0 +1,279 @@
+<script setup lang="ts">
+import { mapState } from 'pinia'
+import { useI18n } from 'vue-i18n'
+import { ref, watch, onMounted, nextTick } from 'vue'
+import logoCn from '@/assets/images/logoCn.png'
+import logoEn from '@/assets/images/logoEn.png'
+import { useUserStore } from '@/stores/user'
+import QrcodeVue from 'qrcode.vue'
+import { show, showConfirm } from '@/components/Toast'
+import { openPay } from '@/api/api'
+import { useRouter, useRoute } from 'vue-router';
+import { GetRequest } from '@/utils/index'
+import { getCurrentInstance } from 'vue'
+
+//得到i18n的locale
+const { locale: language } = useI18n()
+const { token, info, isEur } = useUserStore()
+let { $cdn } = getCurrentInstance()?.proxy;
+const qrcodeRef = ref<HTMLElement | null>(null)
+let PAYSID = {
+  wechatPay: 2,
+  alipay: 3,
+  paypal: 0
+}
+const selectedPayType = ref('alipay')
+watch(selectedPayType, (newValue, oldValue) => {
+  getCode()
+})
+const qrCodeUrl = ref('')
+const response = ref({
+  price: 0.01,
+  src: ''
+})
+const t1 = ref(null)
+onMounted(() => {
+  getCode()
+})
+
+
+async function getCode() {
+  openPay({
+    orderSn:GetRequest('orderSn'),
+    payType:PAYSID[selectedPayType.value],
+    openId:'',
+  }).then(res => {
+    qrCodeUrl.value = res.qrCodeUrl
+    console.log(res, '请求数据')
+  })
+}
+</script>
+<template>
+  <div class="pcPage">
+    {{ token }}
+    <div class="mall-pay">
+      <div class="container">
+        <div class="pay-header">
+          <div class="logo">
+            <img :src="language === 'zh' ? logoCn : logoEn" alt="" />
+          </div>
+          <p>{{ $t('mall.payCenter') }}</p>
+        </div>
+        <div class="pay-content">
+          <div class="account">
+            <span class="label">{{ $t('mall.nowAccount') }}:</span><span>{{ info.userName }}</span>
+          </div>
+          <div class="account">
+            <span class="label">{{ $t('mall.buyGoods') }}:</span><span>配件</span>
+          </div>
+          <div class="pay-types">
+            <p class="label">{{ $t('mall.payType') }}:</p>
+            <div
+              class="pay-type ali-pay"
+              @click="selectedPayType = 'alipay'"
+              :class="{ 'is-active': selectedPayType === 'alipay' }"
+            >
+              <img :src="`${$cdn}images/ali-pay.png`" class="t-icon" alt />
+              <h-icon type="vip_true" class="select-icon"></h-icon>
+              <img :src="`${$cdn}images/tag-icon.png`" class="t-click" alt />
+              {{ $t('mall.aliPay') }}
+            </div>
+            <div
+              class="pay-type wechat-pay"
+              @click="selectedPayType = 'wechatPay'"
+              :class="{ 'is-active': selectedPayType === 'wechatPay' }"
+            >
+              <img :src="`${$cdn}images/wechat-pay.png`" class="t-icon" alt />
+              <h-icon type="vip_true" class="select-icon"></h-icon>
+              <img :src="`${$cdn}images/tag-icon.png`" class="t-click" alt />
+              {{ $t('mall.wechatPay') }}
+            </div>
+            <div v-if="isEur"
+              class="pay-type paypal-pay"
+              @click="selectedPayType = 'paypal'"
+              :class="{ 'is-active': selectedPayType === 'paypal' }"
+            >
+              <img :src="`${$cdn}images/paypal.png`" class="t-icon" alt />
+              <h-icon type="vip_true" class="select-icon"></h-icon>
+              <img :src="`${$cdn}images/tag-icon.png`" class="t-click" alt />
+              paypal
+            </div>
+          </div>
+          <div class="pay-info">
+            <p class="label">{{ $t('mall.payNum') }}:</p>
+            <p class="price">¥{{ response.price }}</p>
+            <div class="pay-qrcode" style="min-height:280px">
+              <QrcodeVue v-if="qrCodeUrl"
+                :value="qrCodeUrl"
+                class="enter-x flex justify-center xl:justify-start"
+                :size="190"
+              />
+              <p>
+                {{
+                  $t('mall.payTip', {
+                    type: selectedPayType === 'alipay' ? $t('mall.aliPay') : $t('mall.wechat')
+                  })
+                }}
+              </p>
+            </div>
+            <div :class="`pay-scan-tip ${language === 'zh' ? '' : 'isEn'}`"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<style lang="less" scoped>
+.mall-pay {
+  padding: 61px 0 61px;
+  background: #f7f7f7;
+  .container {
+    background: #fff;
+  }
+  .pay-header {
+    border-bottom: 1px solid #e4e4e4;
+    padding: 26px 0 21px 92px;
+    font-size: 24px;
+    line-height: 32px;
+    .logo,
+    p {
+      display: inline-block;
+      vertical-align: middle;
+    }
+    .logo {
+      margin-right: 52px;
+      position: relative;
+      &::after {
+        content: '';
+        display: block;
+        width: 1px;
+        height: 34px;
+        background: #c8c8c8;
+        position: absolute;
+        right: -30px;
+        top: 11px;
+      }
+    }
+  }
+  .pay-content {
+    padding: 0 92px 80px;
+  }
+  .account {
+    height: 56px;
+    line-height: 56px;
+    font-weight: bold;
+    border-bottom: 1px solid #e4e4e4;
+  }
+  .label {
+    font-weight: bold;
+    margin-right: 13px;
+  }
+  .pay-types {
+    padding: 50px 0;
+    border-bottom: 1px solid #e4e4e4;
+    & > p,
+    & > div {
+      display: inline-block;
+    }
+    .pay-type {
+      width: 190px;
+      height: 40px;
+      line-height: 40px;
+      margin-right: 30px;
+      border: 1px solid #909090;
+      border-radius: 2px;
+      padding-left: 60px;
+      cursor: pointer;
+      position: relative;
+      &.is-active {
+        border-color: @primary-color;
+        &::after {
+          content: '';
+          display: block;
+          position: absolute;
+          right: 0;
+          bottom: 0;
+          border-bottom: 20px solid @primary-color;
+          border-left: 22px solid transparent;
+        }
+        .t-click {
+          display: block;
+        }
+      }
+      .select-icon {
+        color: #fff;
+        position: absolute;
+        bottom: 0;
+        right: 0;
+        z-index: 1;
+        line-height: 1;
+        font-size: 12px;
+      }
+      .t-icon{
+        width: 30px;
+        height: 30px;
+        padding: 6px;
+        left: 0;
+        position: absolute;
+      }
+      .t-click {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        display: none;
+        display: none;
+    }
+    }
+    .pay-icon {
+      position: absolute;
+      left: 4px;
+      top: 0;
+      height: 40px;
+      width: 34px;
+      display: inline-block;
+      background: url(~@/assets/images/refactor/mall/ali-icon.png) no-repeat center center;
+      background-size: 34px 34px;
+      &.wechat-icon {
+        background: url(~@/assets/images/refactor/mall/wechat-icon.png) no-repeat center center;
+        background-size: 28px 25px;
+      }
+    }
+  }
+  .pay-info {
+    padding-top: 24px;
+    position: relative;
+    & > p {
+      display: inline-block;
+      vertical-align: middle;
+    }
+    .price {
+      color: @primary-color;
+      font-size: 40px;
+      line-height: 50px;
+    }
+  }
+  .pay-qrcode {
+    padding: 60px 0 0 337px;
+    img {
+      width: 190px;
+      height: 190px;
+      margin-bottom: 7px;
+      display: block;
+    }
+  }
+  .pay-scan-tip {
+    width: 186px;
+    height: 165.5px;
+    background: url(@/assets/images/scan-tip.png) no-repeat center center;
+    background-size: contain;
+    position: absolute;
+    top: 113px;
+    left: 562px;
+    &.isEn {
+      background-image: url(@/assets/images/scan-tip-en.png);
+    }
+  }
+}
+</style>

+ 16 - 0
tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.web.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "compilerOptions": {
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    }
+  ]
+}

+ 8 - 0
tsconfig.node.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.node.json",
+  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
+  "compilerOptions": {
+    "composite": true,
+    "types": ["node"]
+  }
+}

+ 24 - 0
vite-project/.gitignore

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

+ 3 - 0
vite-project/.vscode/extensions.json

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

+ 18 - 0
vite-project/README.md

@@ -0,0 +1,18 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## 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 [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

+ 13 - 0
vite-project/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 20 - 0
vite-project/package.json

@@ -0,0 +1,20 @@
+{
+  "name": "vite-project",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vue-tsc && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.2.47"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "typescript": "^4.9.3",
+    "vite": "^4.2.0",
+    "vue-tsc": "^1.2.0"
+  }
+}

File diff suppressed because it is too large
+ 1 - 0
vite-project/public/vite.svg


+ 30 - 0
vite-project/src/App.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import HelloWorld from './components/HelloWorld.vue'
+</script>
+
+<template>
+  <div>
+    <a href="https://vitejs.dev" target="_blank">
+      <img src="/vite.svg" class="logo" alt="Vite logo" />
+    </a>
+    <a href="https://vuejs.org/" target="_blank">
+      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
+    </a>
+  </div>
+  <HelloWorld msg="Vite + Vue" />
+</template>
+
+<style scoped>
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vue:hover {
+  filter: drop-shadow(0 0 2em #42b883aa);
+}
+</style>

+ 1 - 0
vite-project/src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 38 - 0
vite-project/src/components/HelloWorld.vue

@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+defineProps<{ msg: string }>()
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Install
+    <a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
+    in your IDE for a better DX
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 5 - 0
vite-project/src/main.ts

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')

+ 80 - 0
vite-project/src/style.css

@@ -0,0 +1,80 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-text-size-adjust: 100%;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 1 - 0
vite-project/src/vite-env.d.ts

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

+ 18 - 0
vite-project/tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": ["ESNext", "DOM"],
+    "skipLibCheck": true,
+    "noEmit": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 9 - 0
vite-project/tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 7 - 0
vite-project/vite.config.ts

@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+})

+ 49 - 0
vite.config.ts

@@ -0,0 +1,49 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import vue from '@vitejs/plugin-vue'
+import path from "path";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),    
+    // require('unplugin-element-plus/webpack')(),
+    // ...
+    AutoImport({
+      resolvers: [ElementPlusResolver()],
+    }),
+    Components({
+      resolvers: [ElementPlusResolver()],
+    }),
+  ],
+  resolve: {
+    alias: {
+      // "@": path.resolve(__dirname, "./src/"),
+      "@": path.resolve(__dirname, "src"),
+    }
+  },
+  css: {
+    preprocessorOptions: {
+      less: {
+        modifyVars: {
+          hack: `true; @import (reference) "${path.resolve(__dirname, 'src/assets/config.less')}";`,
+        },
+        javascriptEnabled: true,
+      }
+    }
+  },
+  server: {
+    proxy: {
+      '/service': {
+        target: 'https://test.4dkankan.com/',
+        changeOrigin: true,
+        // rewrite: (path) => path.replace(/^\/api/, '')
+      }
+    }
+ },
+
+})

File diff suppressed because it is too large
+ 2692 - 0
yarn.lock