qinyan 1 year ago
parent
commit
bf79ed1d6c
84 changed files with 6177 additions and 1614 deletions
  1. 4 0
      .browserslistrc
  2. 0 15
      .eslintrc.cjs
  3. 35 0
      .eslintrc.js
  4. 13 16
      .gitignore
  5. 0 8
      .prettierrc.json
  6. 0 3
      .vscode/extensions.json
  7. 29 31
      README.md
  8. 11 0
      babel.config.js
  9. 0 1
      env.d.ts
  10. 0 13
      index.html
  11. 11 0
      linaria.config.js
  12. 78 25
      package.json
  13. BIN
      public/favicon.ico
  14. 17 0
      public/index.html
  15. 83 0
      scripts/deployAssetsToOss.js
  16. 39 0
      scripts/deployHtmlToServer.js
  17. 117 0
      scripts/svg-generator.js
  18. 83 0
      scripts/uploadSftp.js
  19. 23 0
      src/App.tsx
  20. 0 85
      src/App.vue
  21. 0 74
      src/assets/base.css
  22. 3 0
      src/assets/icons/components/IconNodeEdit.tsx
  23. 16 0
      src/assets/icons/createIcon.tsx
  24. 3 0
      src/assets/icons/index.ts
  25. 15 0
      src/assets/icons/inficon.less
  26. 1 0
      src/assets/icons/svg/node_edit.svg
  27. BIN
      src/assets/images/default.png
  28. 0 1
      src/assets/logo.svg
  29. 0 35
      src/assets/main.css
  30. 0 40
      src/components/HelloWorld.vue
  31. 8 0
      src/components/Layout/Copyright.tsx
  32. 0 86
      src/components/TheWelcome.vue
  33. 0 86
      src/components/WelcomeItem.vue
  34. 26 0
      src/components/clipboard/index.ts
  35. 0 7
      src/components/icons/IconCommunity.vue
  36. 0 7
      src/components/icons/IconDocumentation.vue
  37. 0 7
      src/components/icons/IconEcosystem.vue
  38. 0 7
      src/components/icons/IconSupport.vue
  39. 0 19
      src/components/icons/IconTooling.vue
  40. 0 11
      src/main.ts
  41. 24 0
      src/modules-ctx/collocationCtx/index.ts
  42. 22 0
      src/modules/_default/auth.ts
  43. 18 0
      src/modules/collocation/index.ts
  44. 17 0
      src/modules/collocation/module/actions/design.ts
  45. 3 0
      src/modules/collocation/module/actions/init.ts
  46. 6 0
      src/modules/collocation/module/actions/source.ts
  47. 3 0
      src/modules/collocation/module/helper.ts
  48. 10 0
      src/modules/collocation/module/http.ts
  49. 7 0
      src/modules/collocation/module/stores/source.ts
  50. 66 0
      src/pages/collocation/Editor/components/Canvas3d.tsx
  51. 92 0
      src/pages/collocation/Editor/components/Header/index.tsx
  52. 59 0
      src/pages/collocation/Editor/components/RightPanel/components/PanelCard.tsx
  53. 104 0
      src/pages/collocation/Editor/components/RightPanel/components/PanelItem.tsx
  54. 14 0
      src/pages/collocation/Editor/components/RightPanel/index.tsx
  55. 23 0
      src/pages/collocation/Editor/components/SourcePanel/index.tsx
  56. 32 0
      src/pages/collocation/Editor/index.tsx
  57. 198 0
      src/pages/collocation/Home/components/DesignListModel.tsx
  58. 87 0
      src/pages/collocation/Home/components/OperationModal.tsx
  59. 127 0
      src/pages/collocation/Home/components/Thumbnail.tsx
  60. 150 0
      src/pages/collocation/Home/index.tsx
  61. 43 0
      src/pages/collocation/components/AvatarDropDown.tsx
  62. 98 0
      src/pages/collocation/components/BasicLayout.tsx
  63. 106 0
      src/pages/collocation/components/FormItem.tsx
  64. 5 0
      src/pages/collocation/index.ts
  65. 60 0
      src/pages/collocation/router.ts
  66. 9 0
      src/pages/components/layouts/BlankLayout.tsx
  67. 9 0
      src/pages/components/layouts/PageView.tsx
  68. 5 0
      src/pages/components/layouts/index.js
  69. 5 0
      src/pages/login/index.ts
  70. 26 0
      src/pages/login/router.ts
  71. 0 23
      src/router/index.ts
  72. 49 0
      src/styles/global.less
  73. 2 0
      src/styles/index.ts
  74. 12 0
      src/styles/theme-antd.js
  75. 29 0
      src/styles/theme.less
  76. 290 0
      src/typings/asset.d.ts
  77. 16 0
      src/typings/pro.d.ts
  78. 0 15
      src/views/AboutView.vue
  79. 0 9
      src/views/HomeView.vue
  80. 33 10
      tsconfig.json
  81. 0 8
      tsconfig.node.json
  82. 0 15
      vite.config.ts
  83. 87 0
      vue.config.js
  84. 3616 957
      yarn.lock

+ 4 - 0
.browserslistrc

@@ -0,0 +1,4 @@
+> 1%
+last 2 versions
+not dead
+not ie 11

+ 0 - 15
.eslintrc.cjs

@@ -1,15 +0,0 @@
-/* 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'
-  }
-}

+ 35 - 0
.eslintrc.js

@@ -0,0 +1,35 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: [
+    "plugin:vue/vue3-essential",
+    "eslint:recommended",
+    "@vue/typescript/recommended",
+    "plugin:prettier/recommended",
+  ],
+  parserOptions: {
+    ecmaVersion: 2020,
+  },
+  rules: {
+    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
+    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
+    "@typescript-eslint/ban-ts-comment": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "prettier/prettier": "off",
+    "@typescript-eslint/no-var-requires": "off",
+    "@typescript-eslint/no-namespace": "off"
+  },
+  overrides: [
+    {
+      files: [
+        "**/__tests__/*.{j,t}s?(x)",
+        "**/tests/unit/**/*.spec.{j,t}s?(x)",
+      ],
+      env: {
+        jest: true,
+      },
+    },
+  ],
+};

+ 13 - 16
.gitignore

@@ -1,26 +1,23 @@
-# Logs
-logs
-*.log
+.DS_Store
+node_modules
+/dist
+/static
+.yalc
+yalc.lock
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
 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
+.vscode
 *.suo
 *.ntvs*
 *.njsproj

+ 0 - 8
.prettierrc.json

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

+ 0 - 3
.vscode/extensions.json

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

+ 29 - 31
README.md

@@ -1,46 +1,44 @@
-# Collocation
+# infish-project-template
 
-This template should help get you started developing with Vue 3 in Vite.
+## Project setup
 
-## 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.
+```
+yarn install
+
+cd ./src/modules-package/queditor
+npm link
+cd ./src/modules-package/queentree
+npm link
+cd ./
+npm link @queenjs-modules/queditor @queenjs-modules/queentree
+```
 
-## Customize configuration
+### Compiles and hot-reloads for development
 
-See [Vite Configuration Reference](https://vitejs.dev/config/).
+```
+yarn serve
+```
 
-## Project Setup
+### Compiles and minifies for production
 
-```sh
-npm install
+```
+yarn build
 ```
 
-### Compile and Hot-Reload for Development
+### Run your unit tests
 
-```sh
-npm run dev
+```
+yarn test:unit
 ```
 
-### Type-Check, Compile and Minify for Production
+### Lints and fixes files
 
-```sh
-npm run build
+```
+yarn lint
 ```
 
-### Lint with [ESLint](https://eslint.org/)
+### Customize configuration
 
-```sh
-npm run lint
-```
+See [Configuration Reference](https://cli.vuejs.org/config/).
+
+ "/public/index.html?base%3Dhttp%3A%2F%2F192.168.110.115%3A55296%2Fassetcenter%26user%3Dhttp%3A%2F%2F192.168.110.115%3A55295%2Fusercenter%26token%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTI0NTU0NDAsImlkIjoiNjM1YmFkOTgwNWZjNzk1Zjc2N2QxMDc5Iiwia2V5IjoiIiwibmFtZSI6InF1ZWVudHJlZSIsIm9yaWdfaWF0IjoxNjgwOTE5NDQwLCJwYXJlbnQiOiIiLCJwaG9uZSI6IjE1MjA4MzY0NjIxIiwicm9sZSI6IiIsInN0YXRlIjoxLCJ1c2VyVHlwZSI6Mn0.F6KPXSEHy5GWq1p8gmBSfJZWmXbUZ6v2ePAa2DfR1I8%26a%3D1680919440"

+ 11 - 0
babel.config.js

@@ -0,0 +1,11 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset", "@linaria"],
+  plugins: [
+    "@vue/babel-plugin-jsx",
+    [
+      "import",
+      { libraryName: "ant-design-vue", libraryDirectory: "es", style: true },
+      "antd",
+    ],
+  ],
+};

+ 0 - 1
env.d.ts

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

+ 0 - 13
index.html

@@ -1,13 +0,0 @@
-<!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>

+ 11 - 0
linaria.config.js

@@ -0,0 +1,11 @@
+module.exports = {
+  rules: [
+    {
+      action: require("@linaria/shaker").default,
+    },
+    {
+      test: /node_modules[\/\\](?!@queenjs)/,
+      action: "ignore",
+    },
+  ]
+}

+ 78 - 25
package.json

@@ -1,34 +1,87 @@
 {
-  "name": "collocation",
-  "version": "0.0.0",
+  "name": "queen.cloud",
+  "version": "0.0.1",
   "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/"
+    "serve": "vue-cli-service serve",
+    "serve:local": "cross-env --APP_MODE=local vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "build:local": "cross-env --APP_MODE=local vue-cli-service build",
+    "lint": "vue-cli-service lint",
+    "svg": "node ./scripts/svg-generator.js",
+    "uploadOss": "node scripts/deployAssetsToOss.js",
+    "uploadServer": "node scripts/deployHtmlToServer.js",
+    "deploy": "npm run build && npm run uploadOss && npm run uploadServer"
   },
   "dependencies": {
-    "vue": "^3.2.47",
-    "vue-router": "^4.1.6"
+    "@linaria/core": "^4.1.1",
+    "@queenjs-modules/auth": "^0.0.18",
+    "@queenjs-modules/queditor": "^0.0.9",
+    "@queenjs-modules/queentree": "^0.0.8",
+    "@queenjs/components": "^0.0.5",
+    "@queenjs/controllers": "^0.0.6",
+    "@queenjs/icons": "^0.0.20",
+    "@queenjs/theme": "^0.0.8",
+    "@queenjs/ui": "^0.0.2",
+    "@queenjs/use": "^0.0.4",
+    "@queenjs/utils": "^0.0.2",
+    "@simonwep/pickr": "^1.8.2",
+    "@types/file-saver": "^2.0.5",
+    "@types/qrcode": "^1.5.0",
+    "@vueuse/core": "^9.13.0",
+    "ali-oss": "^6.17.1",
+    "ant-design-vue": "^3.2.18",
+    "clipboard": "^2.0.11",
+    "co": "^4.6.0",
+    "core-js": "^3.8.3",
+    "echarts": "^5.4.0",
+    "file-saver": "^2.0.5",
+    "hotkeys-js": "^3.10.1",
+    "jszip": "^3.10.1",
+    "load-asset": "^1.2.0",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "nanoid": "^4.0.2",
+    "nats.ws": "^1.12.0",
+    "proto.gl": "^1.0.0",
+    "qrcode": "^1.5.1",
+    "queen3d": "^0.0.80",
+    "queenjs": "^1.0.0-beta.69",
+    "queentree": "^0.1.86-nocheck",
+    "rimraf": "^3.0.2",
+    "scp2": "^0.5.0",
+    "swiper": "^8.4.4",
+    "three": "^0.146.0",
+    "vue": "^3.2.45",
+    "vue-router": "^4.0.3",
+    "vue-types": "^4.2.1"
   },
   "devDependencies": {
-    "@rushstack/eslint-patch": "^1.2.0",
-    "@types/node": "^18.14.2",
-    "@vitejs/plugin-vue": "^4.0.0",
-    "@vitejs/plugin-vue-jsx": "^3.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",
-    "vite": "^4.1.4",
-    "vue-tsc": "^1.2.0"
+    "@linaria/babel-preset": "^4.1.2",
+    "@linaria/webpack-loader": "^4.1.2",
+    "@queenjs/webpack-loader": "^0.0.2",
+    "@types/color-convert": "^2.0.0",
+    "@types/lodash": "^4.14.186",
+    "@types/three": "^0.146.0",
+    "@typescript-eslint/eslint-plugin": "^5.4.0",
+    "@typescript-eslint/parser": "^5.4.0",
+    "@vue/babel-plugin-jsx": "^1.1.1",
+    "@vue/cli-plugin-babel": "~5.0.0",
+    "@vue/cli-plugin-eslint": "~5.0.0",
+    "@vue/cli-plugin-router": "~5.0.0",
+    "@vue/cli-plugin-typescript": "~5.0.0",
+    "@vue/cli-service": "~5.0.0",
+    "@vue/eslint-config-typescript": "^9.1.0",
+    "babel-plugin-import": "^1.13.5",
+    "cross-env": "^7.0.3",
+    "eslint": "^7.32.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-vue": "^8.0.3",
+    "less": "^4.1.3",
+    "less-loader": "^11.0.0",
+    "prettier": "^2.4.1",
+    "typescript": "^4.7.4",
+    "vue-cli-plugin-windicss": "^1.1.6"
   }
 }

BIN
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 83 - 0
scripts/deployAssetsToOss.js

@@ -0,0 +1,83 @@
+const OSS = require('ali-oss');
+
+const co = require('co');
+const path = require('path');
+const fs = require('fs');
+
+const cdnpath = `/cloud/v1/`;
+const client = new OSS({
+    region: 'oss-cn-chengdu',
+    accessKeyId: 'LTAI4GHqGdgY3b14jVRTzL2w',
+    accessKeySecret: 'AHubozYEzXzi4fBL0ttLJ5sN1HMKA6',
+    bucket: 'infishwaibao',
+});
+
+function GetSubFiles(dir) {
+    return new Promise((reslove, reject) => {
+        fs.readdir(dir, function (err, files) {
+            reslove(files);
+        });
+    });
+}
+
+async function GetTotalFiles(dir) {
+    let subfiles = await GetSubFiles(dir);
+
+    let files = [],
+        len = subfiles.length;
+    let ret = [];
+    for (let i = 0; i < len; i++) {
+        let f = subfiles[i];
+
+        let fpath = `${dir}/${f}`;
+        var stat = fs.lstatSync(fpath);
+        if (!stat.isDirectory()) {
+            ret.push(fpath);
+        } else {
+            let fsubs = await GetTotalFiles(fpath);
+            let size = fsubs.length;
+            for (let k = 0; k < size; k++) {
+                ret.push(fsubs[k]);
+            }
+        }
+    }
+    return ret;
+}
+
+GetTotalFiles('dist').then((files) => {
+    handleFile(files);
+});
+
+async function handleFile(files) {
+    let i = 0,
+        len = files.length;
+    for (; i < len; i++) {
+        let fpath = files[i];
+        if (path.extname(fpath) != '.map') {
+            await UploadOss(files[i]);
+        }
+    }
+}
+
+function UploadOss(src) {
+    return new Promise((resolve, reject) => {
+        co(function* () {
+            let fpath = src.substr(5); //去掉前面的dist
+            console.log('uploading ' + fpath);
+
+            var ret = yield client.put(cdnpath + fpath, src, { timeout: 2000000 });
+            console.log(ret.url);
+            let i = 0;
+            while (!ret) {
+                if (i++ > 3) {
+                    reject(`uploading ${src} error!`);
+                    return;
+                }
+                ret = yield client.put(cdnpath + fpath, dist, { timeout: 2000000 });
+            }
+            console.log(ret.url);
+
+            resolve(ret);
+        });
+    });
+}

+ 39 - 0
scripts/deployHtmlToServer.js

@@ -0,0 +1,39 @@
+var path = require('path');
+var fs = require('fs');
+var sftp = require('./uploadSftp');
+
+var rimraf = require('rimraf');
+var nativePath = `dist/temp`;
+
+//创建临时目录
+fs.mkdirSync(nativePath);
+
+// var indexHtml = fs.readFileSync('dist/index.html');
+// fs.writeFileSync(nativePath + path.sep + 'index.html', indexHtml);
+
+var shareHtml = fs.readFileSync('dist/share.html');
+fs.writeFileSync(nativePath + path.sep + 'share.html', shareHtml);
+
+var shareHtml = fs.readFileSync('dist/login.html');
+fs.writeFileSync(nativePath + path.sep + 'login.html', shareHtml);
+
+var consoleHtml = fs.readFileSync('dist/console.html');
+fs.writeFileSync(nativePath + path.sep + 'console.html', consoleHtml);
+
+var serverPath = `/var/www/3dqueencloud/`;
+
+var ftpUtils = new sftp({
+    remotePath: serverPath,
+    path: nativePath,
+    username: 'root',
+    password: 'MmxInfish@2020',
+    host: '124.71.139.24',
+    verbose: true,
+});
+
+ftpUtils.apply(function () {
+    //删除maps文件夹
+    rimraf.sync(nativePath);
+
+    console.log(`ftp upload success!`);
+});

+ 117 - 0
scripts/svg-generator.js

@@ -0,0 +1,117 @@
+const fs = require("fs");
+const { upperFirst } = require("lodash");
+const path = require("path");
+
+let pathName = path.join(__dirname, "../src/assets/icons/svg");
+fs.readdir(pathName, function (err, files) {
+  let icons = [];
+  (function iterator(i) {
+    const fileName = files[i];
+    if (i == files.length) {
+      fs.writeFileSync(
+        path.join(__dirname, "../src/assets/icons/index.ts"),
+        templet_svg(icons)
+      );
+      return;
+    }
+    let fileUrl = path.join(pathName, fileName);
+    fs.stat(fileUrl, function (err, data) {
+      if (data.isFile()) {
+        const value = formatSvg(fs.readFileSync(fileUrl, "utf-8"));
+        fs.writeFileSync(fileUrl, value);
+        const name = ("icon_" + fileName.slice(0, -4))
+          .split("_")
+          .map((s) => upperFirst(s))
+          .join("");
+        fs.writeFileSync(
+          path.join(__dirname, `../src/assets/icons/components/${name}.tsx`),
+          templet_icon({ name, value })
+        );
+        icons.push(name);
+      }
+      iterator(i + 1);
+    });
+  })(0);
+});
+
+const templet_svg = template`
+export * from "./createIcon";
+${(icons) =>
+  icons.map((icon) => `export * from "./components/${icon}";`).join("\n")}
+`;
+
+const templet_icon = template`
+import { createIcon } from '../createIcon';
+export const ${(props) => props.name} = createIcon(${(props) => props.value})
+`;
+
+function template(arr1, ...arr2) {
+  return function (props) {
+    let tpl_str = "";
+    for (let i = 0; i <= arr1.length; i++) {
+      tpl_str += (arr1[i] || "") + (arr2[i] ? arr2[i](props) : "");
+    }
+    return tpl_str;
+  };
+}
+
+const formatSvg = function (data) {
+  let classList = {};
+  let [, styles] = data.match(/<style>((.|\n|\r)+?)<\/style>/) || [];
+  let [, svgAttr] = data.match(/<svg((.|\n|\r)+?)>/) || [];
+
+  const attrs = [];
+  svgAttr.split(" ").forEach((attr) => {
+    if (!/^(xmlns|width|height|version|xmlns:xlink|isolation)=/.test(attr)) {
+      attrs.push(attr);
+    }
+  });
+  let fmtSvgStr = data.replace(/<svg((.|\n|\r)+?)>/, `<svg${attrs.join(" ")}>`);
+
+  if (styles) {
+    styles = styles.replace(/\s/g, "");
+    const attrs = styles.split(";}");
+    attrs.forEach((attr) => {
+      if (!attr) return;
+      let [cNames, values] = attr.split("{");
+      valuesObj = {};
+      values.split(";").forEach((value) => {
+        let [key, val] = value.split(":");
+        valuesObj[key] = val;
+      });
+      cNames.split(",").forEach((name) => {
+        name = name.slice(1);
+        classList[name] = Object.assign({}, classList[name], valuesObj);
+      });
+    });
+  }
+
+  fmtSvgStr = fmtSvgStr.replace(/<defs>(.|\n|\r)+?<\/defs>/, "");
+  for (let className in classList) {
+    fmtSvgStr = fmtSvgStr.replace(
+      new RegExp(`class="${className}"`, "g"),
+      fmtVals(classList[className])
+    );
+  }
+
+  fmtSvgStr = fmtSvgStr.replace(
+    / (stroke|fill|class|id|data\-name|p\-id)="(.+?)"/g,
+    (str, key, value) => {
+      if (["class", "id", "data-name", "p-id"].indexOf(key) != -1) return "";
+      if (value === "#000" || value === "#000000") {
+        return ` ${key}="currentColor"`;
+      }
+      return str;
+    }
+  );
+
+  function fmtVals(obj) {
+    let valArr = [];
+    for (let name in obj) {
+      valArr.push(`${name}="${obj[name]}"`);
+    }
+    return valArr.join(" ");
+  }
+
+  return fmtSvgStr;
+};

+ 83 - 0
scripts/uploadSftp.js

@@ -0,0 +1,83 @@
+'use strict';
+
+var ClientLib = require('scp2');
+
+var client = new ClientLib.Client();
+
+function UploadSftp(options) {
+  this.options = options;
+}
+
+UploadSftp.prototype.apply = function (callbck) {
+  var self = this;
+
+  var remotePath = self.options.remotePath;
+  var path = self.options.path;
+  var username = self.options.username;
+  var host = self.options.host;
+  var password = self.options.password;
+  var port = self.options.port || '22';
+  var verbose = self.options.verbose;
+
+  var startTime;
+  var endTime;
+
+  client.on('connect', function () {
+    // console.log('connected');
+  });
+
+  client.on('ready', function () {
+    // console.log('ready');
+    startTime = new Date();
+    console.log('[Start Uploading] ' + startTime);
+  });
+
+  client.on('transfer', function (buf, up, total) {});
+
+  client.on('write', function (p) {
+    if (verbose) {
+      console.log('Transfer ' + p.source + ' => ' + p.destination);
+    }
+  });
+
+  client.on('end', function () {
+    endTime = new Date();
+    console.log('[End Uploading] ' + new Date());
+  });
+
+  client.on('error', function (err) {
+    console.log('[Error] ' + err);
+  });
+
+  client.on('close', function () {
+    console.log(
+      'Transfer with SFTP Completed in [' +
+        (+endTime - +startTime) / 1000 +
+        '] seconds!',
+    );
+
+    callbck && callbck();
+  });
+
+  var srcPath = path;
+  var destPath =
+    username + ':' + password + '@' + host + ':' + port + ':' + remotePath;
+
+  uploadByDir(srcPath, destPath, client);
+};
+
+/**
+ * [uploadByDir: Upload Directory Directory & Cannot Get Detailed Uploading Info for Files]
+ * @param  {[String]} src    [description]
+ * @param  {[String]} dest   [description]
+ * @param  {[Object]} client [description]
+ */
+function uploadByDir(src, dest, client) {
+  ClientLib.scp(src, dest, client, function (err) {
+    if (err) {
+      console.log(err);
+    }
+  });
+}
+
+module.exports = UploadSftp;

+ 23 - 0
src/App.tsx

@@ -0,0 +1,23 @@
+import { queenApi } from "queenjs";
+import { Provider } from "queenjs/adapter/vue";
+import { createApp, defineComponent } from "vue";
+import { Router } from "vue-router";
+import "./styles";
+
+let setModuleHooks: any[] = [];
+
+const App = defineComponent(() => {
+  setModuleHooks.forEach((hook) => hook());
+  setModuleHooks = [];
+  return () => (
+    <Provider>
+      <router-view></router-view>
+    </Provider>
+  );
+});
+
+export function startApp(router: Router, hooks: any[] = []) {
+  setModuleHooks = hooks;
+  queenApi.router = router;
+  createApp(App).use(router).mount("#app");
+}

+ 0 - 85
src/App.vue

@@ -1,85 +0,0 @@
-<script setup lang="ts">
-import { RouterLink, RouterView } from 'vue-router'
-import HelloWorld from './components/HelloWorld.vue'
-</script>
-
-<template>
-  <header>
-    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
-
-    <div class="wrapper">
-      <HelloWorld msg="You did it!" />
-
-      <nav>
-        <RouterLink to="/">Home</RouterLink>
-        <RouterLink to="/about">About</RouterLink>
-      </nav>
-    </div>
-  </header>
-
-  <RouterView />
-</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;
-}
-
-@media (min-width: 1024px) {
-  header {
-    display: flex;
-    place-items: center;
-    padding-right: calc(var(--section-gap) / 2);
-  }
-
-  .logo {
-    margin: 0 2rem 0 0;
-  }
-
-  header .wrapper {
-    display: flex;
-    place-items: flex-start;
-    flex-wrap: wrap;
-  }
-
-  nav {
-    text-align: left;
-    margin-left: -1rem;
-    font-size: 1rem;
-
-    padding: 1rem 0;
-    margin-top: 1rem;
-  }
-}
-</style>

+ 0 - 74
src/assets/base.css

@@ -1,74 +0,0 @@
-/* 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;
-  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;
-}

+ 3 - 0
src/assets/icons/components/IconNodeEdit.tsx

@@ -0,0 +1,3 @@
+
+import { createIcon } from '../createIcon';
+export const IconNodeEdit = createIcon(<svg viewBox="0 0 30 30"><g transform="translate(-352 -218)"><g transform="translate(-413.138 -721)"><rect fill="none" width="30" height="30" rx="2" transform="translate(765.138 939)"/></g><g transform="translate(316.533 158.378)"><path fill="none" stroke="#4a4a4a" stroke-linecap="round" stroke-width="1.2px" stroke-linejoin="round" d="M43.932,75.232l-.047,3.379H47.4l7.6-7.743-3.379-3.379Z"/><line fill="none" stroke-linecap="round" stroke-width="1.2px" stroke="#647bd9" stroke-miterlimit="10" x2="5.467" transform="translate(51.581 78.47)"/><line fill="none" stroke="#4a4a4a" stroke-linecap="round" stroke-width="1.2px" stroke-miterlimit="10" x2="13.163" transform="translate(43.885 81.755)"/></g></g></svg>)

+ 16 - 0
src/assets/icons/createIcon.tsx

@@ -0,0 +1,16 @@
+import "./inficon.less";
+
+export function createIcon(Svg: any) {
+  return (props: any) => {
+    let className = "inficon";
+    if (props.class) {
+      className += " " + props.class;
+      delete props.class;
+    }
+    return (
+      <span class={className} {...props}>
+        {Svg}
+      </span>
+    );
+  };
+}

+ 3 - 0
src/assets/icons/index.ts

@@ -0,0 +1,3 @@
+
+export * from "./createIcon";
+export * from "./components/IconNodeEdit";

+ 15 - 0
src/assets/icons/inficon.less

@@ -0,0 +1,15 @@
+.inficon {
+  display: inline-block;
+  color: inherit;
+  font-style: normal;
+  line-height: 0;
+  text-align: center;
+  text-transform: none;
+  vertical-align: -0.125em;
+  text-rendering: optimizeLegibility;
+
+  svg {
+    width: 1em;
+    height: 1em;
+  }
+}

+ 1 - 0
src/assets/icons/svg/node_edit.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 30 30"><g transform="translate(-352 -218)"><g transform="translate(-413.138 -721)"><rect fill="none" width="30" height="30" rx="2" transform="translate(765.138 939)"/></g><g transform="translate(316.533 158.378)"><path fill="none" stroke="#4a4a4a" stroke-linecap="round" stroke-width="1.2px" stroke-linejoin="round" d="M43.932,75.232l-.047,3.379H47.4l7.6-7.743-3.379-3.379Z"/><line fill="none" stroke-linecap="round" stroke-width="1.2px" stroke="#647bd9" stroke-miterlimit="10" x2="5.467" transform="translate(51.581 78.47)"/><line fill="none" stroke="#4a4a4a" stroke-linecap="round" stroke-width="1.2px" stroke-miterlimit="10" x2="13.163" transform="translate(43.885 81.755)"/></g></g></svg>

BIN
src/assets/images/default.png


+ 0 - 1
src/assets/logo.svg

@@ -1 +0,0 @@
-<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>

+ 0 - 35
src/assets/main.css

@@ -1,35 +0,0 @@
-@import './base.css';
-
-#app {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 2rem;
-
-  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);
-  }
-}
-
-@media (min-width: 1024px) {
-  body {
-    display: flex;
-    place-items: center;
-  }
-
-  #app {
-    display: grid;
-    grid-template-columns: 1fr 1fr;
-    padding: 0 2rem;
-  }
-}

+ 0 - 40
src/components/HelloWorld.vue

@@ -1,40 +0,0 @@
-<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>

+ 8 - 0
src/components/Layout/Copyright.tsx

@@ -0,0 +1,8 @@
+export default function () {
+  return (
+    <div class="py-20px text-center">
+      ©2022 3dqueen.cloud 版权所有{" "}
+      <a href="https://beian.miit.gov.cn">蜀ICP备18008991号-3</a>
+    </div>
+  );
+}

+ 0 - 86
src/components/TheWelcome.vue

@@ -1,86 +0,0 @@
-<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>

+ 0 - 86
src/components/WelcomeItem.vue

@@ -1,86 +0,0 @@
-<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>

+ 26 - 0
src/components/clipboard/index.ts

@@ -0,0 +1,26 @@
+import Clipboard from "clipboard";
+import { queenApi } from "queenjs";
+
+export const clipboard = {
+  copy(text: string) {
+    return new Promise((resolve) => {
+      const btn = document.createElement("button");
+      btn.style.display = "none";
+      btn.setAttribute("data-clipboard-text", text);
+      document.body.appendChild(btn);
+      const clip = new Clipboard(btn);
+      clip.on("success", function (e) {
+        e.clearSelection();
+        document.body.removeChild(btn);
+        queenApi.messageSuccess("复制成功");
+        resolve(true);
+      });
+      clip.on("error", function () {
+        document.body.removeChild(btn);
+        queenApi.messageError("复制失败");
+        resolve(false);
+      });
+      btn.click();
+    });
+  },
+};

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

@@ -1,7 +0,0 @@
-<template>
-  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
-    <path
-      d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
-    />
-  </svg>
-</template>

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

@@ -1,7 +0,0 @@
-<template>
-  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
-    <path
-      d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
-    />
-  </svg>
-</template>

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

@@ -1,7 +0,0 @@
-<template>
-  <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
-    <path
-      d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
-    />
-  </svg>
-</template>

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

@@ -1,7 +0,0 @@
-<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>

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

@@ -1,19 +0,0 @@
-<!-- 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>

+ 0 - 11
src/main.ts

@@ -1,11 +0,0 @@
-import { createApp } from 'vue'
-import App from './App.vue'
-import router from './router'
-
-import './assets/main.css'
-
-const app = createApp(App)
-
-app.use(router)
-
-app.mount('#app')

+ 24 - 0
src/modules-ctx/collocationCtx/index.ts

@@ -0,0 +1,24 @@
+import { initCollocation } from "@/modules/collocation";
+import { initAuthDef } from "@/modules/_default/auth";
+import { ModuleRoot } from "queenjs";
+
+export class CollocationCtxModule extends ModuleRoot {
+  store = {};
+  onReady() {
+    const auth = initAuthDef();
+    auth.actions.on("getUserInfo:success", () => {
+      //
+    });
+
+    initCollocation({
+      config: {
+        httpConfig: {
+          baseURL: "https://www.3dqueen.cloud/cloud/v1/usercenter",
+        },
+      },
+    });
+  }
+}
+
+export const { initCollocationCtx, useCollocationCtx } =
+  CollocationCtxModule.hook("CollocationCtx");

+ 22 - 0
src/modules/_default/auth.ts

@@ -0,0 +1,22 @@
+import { initAuth } from "@queenjs-modules/auth";
+
+export function initAuthDef() {
+  const auth = initAuth({
+    helper: {},
+    config: {
+      singlePage: true,
+      key: "queentreesku3d",
+      loginPath: "/login.html",
+      loginJumpPath: "/",
+      logoutJumpPath: "/",
+      needCompanyLogin: false,
+      needRegister: true,
+      needResetPwd: true,
+      httpConfig: {
+        baseURL: "https://www.3dqueen.cloud/cloud/v1/usercenter",
+      },
+    },
+  });
+  auth.actions.initUser();
+  return auth;
+}

+ 18 - 0
src/modules/collocation/index.ts

@@ -0,0 +1,18 @@
+import { ModuleRoot } from "queenjs";
+import { designAction } from "./module/actions/design";
+import { initAction } from "./module/actions/init";
+import { sourceAction } from "./module/actions/source";
+import { helper } from "./module/helper";
+import { https } from "./module/http";
+import { sourceStore } from "./module/stores/source";
+
+export class CollocationModule extends ModuleRoot {
+  config = this.setConfig({});
+  https = this.createHttps([https]);
+  store = this.createStore(sourceStore);
+  helper = this.createHelper(helper);
+  actions = this.createActions([initAction, sourceAction, designAction]);
+}
+
+export const { initCollocation, useCollocation } =
+  CollocationModule.hook("Collocation");

+ 17 - 0
src/modules/collocation/module/actions/design.ts

@@ -0,0 +1,17 @@
+import { queenApi } from "queenjs";
+import { CollocationModule } from "../..";
+
+export const designAction = CollocationModule.action({
+  async delDesign(item) {
+    const result = await queenApi.showConfirm({
+      title: "删除提示",
+      content: `删除后不可恢复,是否删除${item.name}?`,
+      type: "danger",
+    });
+    if (!result) return;
+
+    const res = await this.https.delDesign(item._id);
+    if (res.errorNo != 200) return;
+    // design.DesignList.fresh();
+  },
+});

+ 3 - 0
src/modules/collocation/module/actions/init.ts

@@ -0,0 +1,3 @@
+import { CollocationModule } from "../..";
+
+export const initAction = CollocationModule.action("once", {});

+ 6 - 0
src/modules/collocation/module/actions/source.ts

@@ -0,0 +1,6 @@
+import { queenApi } from "queenjs";
+import { CollocationModule } from "../..";
+
+export const sourceAction = CollocationModule.action({
+  // 
+});

+ 3 - 0
src/modules/collocation/module/helper.ts

@@ -0,0 +1,3 @@
+import { CollocationModule } from "..";
+
+export const helper = CollocationModule.helper({});

+ 10 - 0
src/modules/collocation/module/http.ts

@@ -0,0 +1,10 @@
+import { CollocationModule } from "..";
+
+export const https = CollocationModule.http({
+  addDesign(data) {
+    return this.request("", { method: "POST", data });
+  },
+  delDesign(id: string) {
+    return this.request(`/delete/${id}`, { method: "POST" });
+  },
+});

+ 7 - 0
src/modules/collocation/module/stores/source.ts

@@ -0,0 +1,7 @@
+import { CollocationModule } from "../..";
+
+export const sourceStore = CollocationModule.store({
+  state: () => ({}),
+  getters: {},
+  actions: {},
+});

+ 66 - 0
src/pages/collocation/Editor/components/Canvas3d.tsx

@@ -0,0 +1,66 @@
+import { css } from "@linaria/core";
+import { Spin } from "ant-design-vue";
+import { defineComponent, reactive } from "vue";
+
+// const Canvas3d = defineComponent({
+//   setup() {
+//     const hooks = useScenePackPreview(match.assetPack, "");
+
+//     return () => (
+//       <Drop name="canvas3d" class={canvasStyle} type={["asset3d.*"]}>
+//         <canvas ref={hooks.canvasRef} />
+//       </Drop>
+//     );
+//   },
+// });
+
+export default defineComponent({
+  setup() {
+    const state = reactive({ loaded: false });
+
+    // match.initStyleEditor().then((loaded) => {
+    //   if (loaded) {
+    //     state.loaded = loaded;
+    //   }
+    // });
+
+    return () => (
+      <div class={rootstyles}>
+        <Spin />
+      </div>
+    );
+    // return () => (state.loaded ? <Canvas3d /> : <Spin />);
+  },
+});
+
+const rootstyles = css`
+  position: relative;
+  background-color: #dddddd;
+  .content_canvas {
+    width: 100%;
+    height: 100%;
+  }
+`;
+
+const canvasStyle = css`
+  transition: all 0.2s ease;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: #eee;
+
+  canvas {
+    width: 100%;
+    height: 100%;
+  }
+
+  .canvas3d-mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+    background-color: #fff;
+  }
+`;

+ 92 - 0
src/pages/collocation/Editor/components/Header/index.tsx

@@ -0,0 +1,92 @@
+import { css } from "@linaria/core";
+import { IconClose } from "@queenjs/icons";
+import { Space } from "ant-design-vue";
+import { queenApi } from "queenjs";
+import { defineComponent } from "vue";
+import { useRouter } from "vue-router";
+
+export default defineComponent({
+  setup(props) {
+    const router = useRouter();
+
+    async function close() {
+      if (history.state.canSave) {
+        const ok = await queenApi.showConfirm({
+          title: "提示",
+          content: "退出款式设计,当前数据将丢失,是否继续退出。",
+          okText: "确定",
+        });
+        if (!ok) return;
+        router.go(-1);
+      } else {
+        router.go(-1);
+      }
+    }
+
+    return () => {
+      return (
+        <div class={headerStyles}>
+          <span class="title">智能搭配中心</span>
+          {/* <TipIcon
+            icon="undo"
+            tip="撤销"
+            onClick={() => history.undo()}
+            disabled={!history.state.canUndo}
+          />
+          <TipIcon
+            icon="redo"
+            tip="重做"
+            onClick={() => history.redo()}
+            disabled={!history.state.canRedo}
+          /> */}
+          <div class="flex-1">
+            {/* <RadioGroup
+              class="radio_btns"
+              data={ctx.webui.state.editTypeGroup}
+              value={ctx.webui.state.editType}
+              onChange={(v: any) => (ctx.webui.state.editType = v)}
+            ></RadioGroup> */}
+          </div>
+          <Space>
+            <IconClose
+              class="icon_close"
+              type="close"
+              onClick={() => close()}
+            />
+          </Space>
+        </div>
+      );
+    };
+  },
+});
+
+const headerStyles = css`
+  grid-area: header;
+  height: 64px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #eaeaea;
+  padding: 0 20px;
+  background-color: #fff;
+  .title {
+    width: 140px;
+    margin-right: 64px;
+    font-size: 16px;
+    font-weight: bold;
+    color: #111111;
+  }
+
+  .icon_close {
+    padding: 10px;
+    font-size: 22px;
+    font-weight: bold;
+    cursor: pointer;
+    &:hover {
+      opacity: 0.8;
+    }
+    &:active {
+      opacity: 0.6;
+    }
+  }
+`;

+ 59 - 0
src/pages/collocation/Editor/components/RightPanel/components/PanelCard.tsx

@@ -0,0 +1,59 @@
+import { css } from "@linaria/core";
+import { Button } from "ant-design-vue";
+import { defineComponent } from "vue";
+import { bool, func, string } from "vue-types";
+
+export default defineComponent({
+  props: {
+    onSave: func(),
+    okText: string().def("保存"),
+    showFooter: bool().def(true),
+  },
+  setup(props, { slots }) {
+    return () => {
+      return (
+        <div class={PanelStyles}>
+          <div class="content">{slots.default?.()}</div>
+          {props.showFooter && (
+            <div class="footer">
+              <Button
+                size="large"
+                type="primary"
+                // loading={match.state.updating}
+                class="submit"
+                onClick={() => props.onSave?.()}
+              >
+                {props.okText}
+              </Button>
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+const PanelStyles = css`
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  .content {
+    flex: 1;
+    padding: 10px;
+    overflow-y: auto;
+  }
+  .footer {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-top: 1px solid #eaeaea;
+    padding: 18px 10px;
+    .cancel {
+      margin-right: 5px;
+      color: #999999;
+    }
+    .submit {
+      flex: 1;
+    }
+  }
+`;

+ 104 - 0
src/pages/collocation/Editor/components/RightPanel/components/PanelItem.tsx

@@ -0,0 +1,104 @@
+import { defineComponent } from "vue";
+import { bool, number, object } from "vue-types";
+import { css, cx } from "@linaria/core";
+import { Image, View } from "@queenjs/ui";
+import { IconDelete } from "@queenjs/icons";
+
+export default defineComponent({
+  props: {
+    active: bool().def(false),
+    ratio: number().def(1),
+    data: object().isRequired,
+  },
+  emits: ["click", "delete"],
+  setup(props, { emit, slots }) {
+    return () => {
+      return (
+        <div
+          class={cx(itemHoverStyle, props.active && "active")}
+          onClick={() => emit("click", props.data)}
+        >
+          <View ratio={props.ratio}>
+            <Image
+              class="item_thumbnail"
+              src={props.data?.thumbnail?.url}
+              size={120}
+            />
+          </View>
+          <IconDelete
+            class="btn_action"
+            onClick={(e: any) => {
+              e.stopPropagation();
+              emit("delete", props.data);
+            }}
+          />
+          {props.data.name && (
+            <div class="pickerItem_title" title={props.data.name}>
+              {props.data.name}
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+const itemHoverStyle = css`
+  position: relative;
+  border: 1px solid transparent;
+  border-radius: 0.04rem;
+  padding: 1px;
+  cursor: pointer;
+  overflow: hidden;
+  &.active {
+    border: 1px solid #5a7bef;
+  }
+  &:hover {
+    .btn_action,
+    .pickerItem_title {
+      transform: translateY(0);
+    }
+  }
+
+  .item_thumbnail {
+    width: 100%;
+    height: 100%;
+    border-radius: 0.04rem;
+    pointer-events: none;
+  }
+
+  .btn_action {
+    position: absolute;
+    right: 5px;
+    top: 5px;
+    border-radius: 2px;
+    padding: 0.03rem;
+    color: #fff;
+    font-size: 0.12rem;
+    background-color: rgba(0, 0, 0, 0.3);
+    transform: translateY(-150%);
+    transition: transform 0.3s ease-in-out;
+    &:hover {
+      background: rgba(0, 0, 0, 0.4);
+    }
+    &:active {
+      background: rgba(0, 0, 0, 0.5);
+    }
+  }
+
+  .pickerItem_title {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    padding: 0.04rem 0.16rem;
+    color: #fff;
+    font-size: 0.12rem;
+    background-color: rgba(0, 0, 0, 0.3);
+    transform: translateY(100%);
+    transition: transform ease-in-out 0.3s;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+  }
+`;

+ 14 - 0
src/pages/collocation/Editor/components/RightPanel/index.tsx

@@ -0,0 +1,14 @@
+import { css } from "@linaria/core";
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  setup() {
+    return () => {
+      return <div class={panelStyles}></div>;
+    };
+  },
+});
+
+const panelStyles = css`
+  border-left: 1px solid #eaeaea;
+`;

+ 23 - 0
src/pages/collocation/Editor/components/SourcePanel/index.tsx

@@ -0,0 +1,23 @@
+import { css } from "@linaria/core";
+import { defineComponent } from "vue";
+import { string } from "vue-types";
+
+export default defineComponent({
+  props: {
+    msg: string(),
+  },
+  setup(props) {
+    return () => (
+      <div class={rootStyle}>
+        <h1>{props.msg}</h1>
+      </div>
+    );
+  },
+});
+
+const rootStyle = css`
+  border-right: 1px solid #eaeaea;
+  h1 {
+    margin: 40px 0 0;
+  }
+`;

+ 32 - 0
src/pages/collocation/Editor/index.tsx

@@ -0,0 +1,32 @@
+import { css } from "@linaria/core";
+import { defineComponent } from "vue";
+import Canvas3d from "./components/Canvas3d";
+import Header from "./components/Header";
+import RightPanel from "./components/RightPanel";
+import SourcePanel from "./components/SourcePanel";
+
+export default defineComponent({
+  setup() {
+    return () => {
+      return (
+        <div class={modalStyles}>
+          <Header />
+          <SourcePanel />
+          <Canvas3d />
+          <RightPanel />
+        </div>
+      );
+    };
+  },
+});
+
+const modalStyles = css`
+  height: 100vh;
+  display: grid;
+  grid-template-areas:
+    "header header header"
+    "leftPanel content rightPanel";
+  grid-template-columns: 400px auto 300px;
+  grid-template-rows: 64px auto;
+  background-color: #fff;
+`;

+ 198 - 0
src/pages/collocation/Home/components/DesignListModel.tsx

@@ -0,0 +1,198 @@
+import { useCollocation } from "@/modules/collocation";
+import { css } from "@linaria/core";
+import { Image, List } from "@queenjs/ui";
+import { Button, Checkbox, Empty, Pagination, Space } from "ant-design-vue";
+import { useModal } from "queenjs";
+import { computed, defineComponent, onMounted, reactive } from "vue";
+import { object } from "vue-types";
+
+export default defineComponent({
+  props: {
+    value: object(),
+  },
+  setup(props) {
+    const modal = useModal();
+
+    const collocation = useCollocation();
+
+    const state = reactive({
+      curScope: "user",
+      curSelect: {
+        _id: props.value?._id,
+      } as any,
+    });
+
+    // const PageListController = computed(() => {
+    //   if (state.curScope == "platform") {
+    //     return ctx.queentree.actions.getPlatformPageList("shoes");
+    //   }
+    //   return ctx.queentree.actions.getTreePageList(
+    //     state.curScope as any,
+    //     "shoes",
+    //     ctx.user.getUserType()
+    //   );
+    // });
+
+    onMounted(() => {
+      // ctx.design.UserList.loadPage(1);
+      // PageListController.value.loadPage(1);
+    });
+
+    // const controller = {
+    //   user: ctx.design.UserList,
+    //   team: ctx.design.TeamList,
+    //   company: ctx.design.CompanyList,
+    // };
+
+    const submit = async () => {
+      // if (state.curSelect._id) {
+      //   let res = await PageListController.value.AssetController.getScenePack(
+      //     state.curSelect._id
+      //   );
+      //   modal.submit(res);
+      //   return false;
+      // }
+      modal.submit(null);
+    };
+
+    const selectDesign = (item: any) => {
+      state.curSelect = item;
+    };
+
+    return () => {
+      const dataSource: any = [];
+      return (
+        <div class={modalStyle}>
+          <div class="list_view">
+            <List
+              columns={5}
+              gap="24px"
+              data={dataSource}
+              // data={PageListController.value.state.list}
+            >
+              {{
+                item: (item: any) => (
+                  <DesignItem
+                    item={item}
+                    key={item._id}
+                    current={state.curSelect}
+                    onClick={selectDesign}
+                  />
+                ),
+                empty: () => {
+                  return <Empty class="py-50px" type="default" />;
+                },
+              }}
+            </List>
+            <div class="text-center mt-30px mb-30px">
+              <Pagination
+                key={state.curScope}
+                hideOnSinglePage
+                // pageSize={PageListController.value.state.size}
+                // hideOnSinglePage={true}
+                // current={PageListController.value.state.page}
+                // total={PageListController.value.state.total}
+                // onChange={(p) => PageListController.value.loadPage(p)}
+              />
+            </div>
+          </div>
+          <div class="text-center">
+            <Space>
+              <Button onClick={() => modal.cancel()}>取消</Button>
+              <Button type="primary" onClick={submit}>
+                确认
+              </Button>
+            </Space>
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const DesignItem = defineComponent({
+  props: ["item", "current", "onClick"],
+  setup(props) {
+    const thumbnailUrl = computed(() => {
+      const { item } = props;
+
+      const DefaultThumbnail = require("@/assets/images/default.png");
+      let thumbnailUrl = DefaultThumbnail;
+      if (item.thumbnail) {
+        if (typeof item.thumbnail == "string") thumbnailUrl = item.thumbnail;
+        else if (item.thumbnail.url) thumbnailUrl = item.thumbnail.url;
+      }
+
+      return thumbnailUrl;
+    });
+
+    return () => {
+      const { current, item } = props;
+
+      return (
+        <div class={DesignItemStyle}>
+          <div
+            class="card_main"
+            onClick={() => {
+              props.onClick && props.onClick(item);
+            }}
+          >
+            <div class="card_check">
+              <Checkbox checked={item._id == current._id} />
+            </div>
+            <div class="thumbnail_card">
+              <Image src={thumbnailUrl.value} size={300} class="card_img" />
+            </div>
+            <div class="card_footer">{item.name}</div>
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const modalStyle = css`
+  background-color: #fff;
+  overflow: hidden;
+  .source_radio {
+    text-align: center;
+  }
+  .list_view {
+    margin-top: 20px;
+  }
+`;
+const DesignItemStyle = css`
+  width: 100%;
+  background-color: rgba(255, 255, 255, 1);
+  border: 1px solid #f5f6f7;
+  cursor: pointer;
+  overflow: hidden;
+  .card_main {
+    position: relative;
+    .card_check {
+      position: absolute;
+      top: 5px;
+      right: 5px;
+    }
+    .thumbnail_card {
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .card_img {
+      width: 100%;
+      height: 150px;
+      object-fit: cover;
+    }
+  }
+  .card_footer {
+    border-top: 1px solid #f5f6f7;
+    padding: 15px;
+    color: #333;
+    width: 100%;
+    font-size: 16px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+`;

+ 87 - 0
src/pages/collocation/Home/components/OperationModal.tsx

@@ -0,0 +1,87 @@
+import { css } from "@linaria/core";
+import { Button, Form, Input } from "ant-design-vue";
+import { queenApi } from "queenjs";
+import { defineComponent, reactive, ref } from "vue";
+import Thumbnail from "./Thumbnail";
+
+const layout = {
+  labelCol: { span: 6 },
+  wrapperCol: { span: 16 },
+};
+
+export default defineComponent({
+  setup(props) {
+    const modal = queenApi.useDialog();
+
+    const formRef = ref();
+
+    const formState = reactive({
+      scenePack: {},
+      name: "",
+    });
+
+    const submit = () => {
+      formRef.value.validate().then(async (values: any) => {
+        console.log("values: ", values);
+      });
+      // modal.cancel();
+    };
+
+    return () => {
+      return (
+        <div class={modalStyle}>
+          <Form {...layout} ref={formRef} model={formState}>
+            <Form.Item
+              name="scenePack"
+              label="选择款式"
+              rules={[{ required: true, message: "款式不能为空" }]}
+            >
+              <Thumbnail
+                v-model={[formState.scenePack, "value"]}
+                // value={value}
+                // values={values}
+                // onChange={(v) => {
+                //   values.name = v.name || null;
+                //   emitChange(v);
+                // }}
+              />
+            </Form.Item>
+            <Form.Item
+              name="name"
+              label="款式名"
+              rules={[{ required: true, message: "款式不能为空" }]}
+            >
+              <Input
+                v-model={[formState.name, "value"]}
+                placeholder="请输入款式名"
+                // value={value == undefined ? values.scenePack?.name : value}
+                onChange={(e: any) => {
+                  // emitChange(e.target.value);
+                }}
+              />
+            </Form.Item>
+            <Form.Item class="text-center" wrapperCol={{ span: 24 }}>
+              <Button onClick={() => modal.cancel()}>取消</Button>
+              <Button type="primary" class="ml-10px" onClick={submit}>
+                确定
+              </Button>
+            </Form.Item>
+          </Form>
+        </div>
+      );
+    };
+  },
+});
+
+const modalStyle = css`
+  .content {
+    padding-top: 20px;
+    padding-bottom: 10px;
+    text-align: left;
+  }
+
+  .footer {
+    margin-bottom: 0;
+    text-align: right;
+  }
+`;

+ 127 - 0
src/pages/collocation/Home/components/Thumbnail.tsx

@@ -0,0 +1,127 @@
+import { useCollocation } from "@/modules/collocation";
+import { css } from "@linaria/core";
+import { IconAddImg } from "@queenjs/icons";
+import { Image } from "@queenjs/ui";
+import { Button } from "ant-design-vue";
+import { useModal } from "queenjs";
+import { defineComponent, reactive } from "vue";
+import { object } from "vue-types";
+import DesignListModel from "./DesignListModel";
+
+export default defineComponent({
+  props: {
+    value: object().def({}),
+    values: object().def({}),
+  },
+  emits: ["change"],
+  setup(props, { emit }) {
+    const collocation = useCollocation();
+    const modal = useModal();
+
+    // const thumbnail = values.thumbnail ? values.thumbnail : value.thumbnail;
+
+    const state = reactive({
+      thumbnail: "thumbnail",
+    }) as any;
+
+    const selectDesign = async () => {
+      collocation.showModal(<DesignListModel value={props.value} />, {
+        title: "选择模型",
+        width: "900px",
+        onOk: async (v: any) => {
+          if (v) {
+            state.thumbnail = v.thumbnail;
+            emit("change", v);
+          }
+          modal.cancel();
+        },
+        onCancel: () => modal.cancel(),
+      });
+    };
+    const changeThumbnail = async () => {
+      // let [file] = await ctx.webui.upload.selectAnyFile(false, "image/*");
+      // if (file) {
+      //   if (!["png", "jpg"].includes(file.ext)) {
+      //     ctx.webui.messageError("上传文件格式不正确,仅支持jpg/png格式");
+      //     return false;
+      //   }
+      // }
+      // let res = await ctx.webui.upload.uploadFile(file.file, "thumbnail");
+      // if (res) {
+      //   let { value } = props;
+      //   value.thumbnail = res;
+      //   state.thumbnail = res;
+      //   emit("change", value);
+      // }
+    };
+    return () => {
+      const { thumbnail } = state;
+      return (
+        <div>
+          {thumbnail && thumbnail.url ? (
+            <div class={DesignSelect}>
+              <Image src={thumbnail.url} size={98} />
+              <div class={"thumbnail_cover"}>
+                <Button type="text" onClick={selectDesign}>
+                  更换款式
+                </Button>
+                <Button type="text" onClick={changeThumbnail}>
+                  更换封面
+                </Button>
+              </div>
+            </div>
+          ) : (
+            <div class={DesignSelect} onClick={selectDesign}>
+              <IconAddImg class="text-30px mb-10px" />
+              <span class="text-12px">点击选择</span>
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+const DesignSelect = css`
+  position: relative;
+  width: 98px;
+  height: 98px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  padding: 10px;
+  border-radius: 4px;
+  color: @inf-primary-color;
+  background: #eef1fe;
+  cursor: pointer;
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+
+  &:hover {
+    .thumbnail_cover {
+      display: flex;
+      flex-flow: column;
+      justify-content: center;
+    }
+  }
+  .thumbnail_cover {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    padding: 10px;
+    display: none;
+    background-color: rgba(0, 0, 0, 0.2);
+    button.ant-btn {
+      padding: 0;
+      &:last-child {
+        margin-top: 10px;
+      }
+    }
+  }
+`;

+ 150 - 0
src/pages/collocation/Home/index.tsx

@@ -0,0 +1,150 @@
+import { useCollocation } from "@/modules/collocation";
+import { css } from "@linaria/core";
+import { Button, Space, Table } from "ant-design-vue";
+import { defineComponent, onMounted } from "vue";
+import { Image } from "@queenjs/ui";
+import OperationModal from "./components/OperationModal";
+import { queenApi } from "queenjs";
+
+export default defineComponent({
+  setup() {
+    const collocation = useCollocation();
+
+    const columns = [
+      {
+        title: "款式图片",
+        dataIndex: "thumbnail",
+        key: "thumbnail",
+        customRender: ({ text }: any) => (
+          <Image class="thumbnail" src={text?.url} size={120} />
+        ),
+      },
+      {
+        title: "款式名称",
+        dataIndex: "name",
+        key: "name",
+        width: 200,
+      },
+      {
+        title: "款式编号",
+        dataIndex: "code",
+        key: "code",
+      },
+      {
+        title: "款式类别",
+        dataIndex: "category",
+        key: "category",
+      },
+      {
+        title: "价格(元)",
+        dataIndex: "price",
+        key: "price",
+        customRender: ({ text }: any) => (text / 100 || 0).toFixed(2),
+      },
+      {
+        title: "操作",
+        key: "action",
+        dataIndex: "action",
+        customRender: ({ record }: any) => {
+          return (
+            <Space>
+              <Button type="link" onClick={() => showEdit(record)}>
+                编辑
+              </Button>
+              <Button
+                type="link"
+                onClick={() => {
+                  queenApi.router.push(`/design/edit/${record._id}`);
+                  // ctx.comm.mallJumpStyleEditor(record._id);
+                }}
+              >
+                定制内容
+              </Button>
+              <Button type="text" danger onClick={() => delDesign(record)}>
+                删除
+              </Button>
+            </Space>
+          );
+        },
+      },
+    ];
+
+    onMounted(() => {
+      // ctx.design.DesignList.loadPage(1, 20);
+      // ctx.design.SizeList.loadPage(1, 1000);
+    });
+
+    const showAddDesign = async () => {
+      const values: any = await collocation.showModal(<OperationModal />, {
+        title: "添加可定制款式",
+        width: "500px",
+      });
+      if (!values) return;
+      values.thumbnail = values.scenePack.thumbnail;
+      // values.price = parseInt(values.price * 100 + "");
+      // let res = await ctx.design.actions.addDesign(values);
+    };
+
+    const showEdit = async (record: any) => {
+      // let item = await ctx.design.actions.getDesignDTL(record._id);
+      // if (!item) return false;
+      // item.price = (item.price / 100).toFixed(2);
+      const values: any = await collocation.showModal(<OperationModal />, {
+        title: "编辑可定制款式",
+        width: "500px",
+      });
+      // if (!values) return;
+      // values.thumbnail = values.scenePack.thumbnail;
+      // values.price = parseInt(values.price * 100 + "");
+      // let res = await ctx.design.actions.updateDesign(values);
+    };
+
+    const delDesign = (item: any) => {
+      collocation.actions.delDesign(item);
+    };
+
+    return () => {
+      const dataSource: any = [{ name: "456", _id: "122" }];
+      return (
+        <div class={DesignStyle}>
+          <div class="mb-20px table_header">
+            <h3 class="font-bold">款式列表</h3>
+            <Button type="primary" onClick={showAddDesign}>
+              添加+
+            </Button>
+          </div>
+          <Table
+            size="small"
+            columns={columns}
+            dataSource={dataSource}
+            rowKey={(record) => record._id}
+            pagination={{
+              size: "default",
+              // pageSize: ctx.design.DesignList.state.size,
+              // current: ctx.design.DesignList.state.page,
+              // total: ctx.design.DesignList.state.total,
+              // onChange: (p) => ctx.design.DesignList.loadPage(p),
+            }}
+          />
+        </div>
+      );
+    };
+  },
+});
+
+const DesignStyle = css`
+  padding: 20px;
+  .table_header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .ant-table-thead > tr > th,
+  .ant-table-tbody > tr > td {
+    text-align: center;
+  }
+  .thumbnail {
+    width: 120px;
+    object-fit: contain;
+  }
+`;

+ 43 - 0
src/pages/collocation/components/AvatarDropDown.tsx

@@ -0,0 +1,43 @@
+import { useAuth } from "@queenjs-modules/auth";
+import { Avatar, Button, Dropdown, Menu } from "ant-design-vue";
+import { queenApi } from "queenjs";
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  setup() {
+    const auth = useAuth();
+
+    const goLogout = async () => {
+      await auth.actions.logout();
+    };
+
+    const login = () => {
+      queenApi.router.push("/login");
+    };
+
+    const menu = (
+      <Menu>
+        <Menu.Item onClick={goLogout}>
+          <div>退出登录</div>
+        </Menu.Item>
+      </Menu>
+    );
+
+    return () => {
+      const { userInfo } = auth.store;
+      return (
+        <>
+          {userInfo._id ? (
+            <Dropdown placement="bottom" overlay={menu}>
+              <Avatar size={36} src={userInfo.avatar} />
+            </Dropdown>
+          ) : (
+            <Button type="primary" size="small" onClick={() => login()}>
+              登录
+            </Button>
+          )}
+        </>
+      );
+    };
+  },
+});

+ 98 - 0
src/pages/collocation/components/BasicLayout.tsx

@@ -0,0 +1,98 @@
+import { css } from "@linaria/core";
+import { Layout, Menu } from "ant-design-vue";
+import { defineComponent, onMounted, reactive } from "vue";
+import { useRoute } from "vue-router";
+import { menuData } from "../router";
+import AvatarDropDown from "./AvatarDropDown";
+
+const { Header, Sider, Content } = Layout;
+
+export default defineComponent({
+  setup() {
+    const state = reactive({
+      menuKey: ["design"],
+    });
+
+    const menuChange = (keyPath: string[]) => {
+      state.menuKey = keyPath;
+    };
+
+    onMounted(() => {
+      const data: any = useRoute();
+      state.menuKey = [data.path];
+    });
+
+    return () => {
+      return (
+        <Layout class={HomeStyle}>
+          <Sider
+            class="overflow-hidden"
+            width="250px"
+            trigger={null}
+            collapsible
+          >
+            <h3 class="text-light-200 text-center py-20px">智能搭配</h3>
+            <Menu
+              theme="dark"
+              selectedKeys={state.menuKey}
+              mode="inline"
+              onSelect={(item: any) => {
+                menuChange(item.keyPath);
+              }}
+            >
+              {menuData.children.map((menu: any) => {
+                if (menu.children?.length) {
+                  return (
+                    <Menu.SubMenu title={menu.name}>
+                      {menu.children.map((submenu: any) => {
+                        if (submenu.hideInMenu) return null;
+                        return (
+                          <Menu.Item key={submenu.path}>
+                            <router-link to={submenu.path}>
+                              {submenu.name}
+                            </router-link>
+                          </Menu.Item>
+                        );
+                      })}
+                    </Menu.SubMenu>
+                  );
+                }
+                return (
+                  <Menu.Item key={menu.path}>
+                    <router-link to={menu.path}>{menu.name}</router-link>
+                  </Menu.Item>
+                );
+              })}
+            </Menu>
+          </Sider>
+          <Layout>
+            <Header class="layout_background text-right">
+              <AvatarDropDown />
+            </Header>
+            <Content
+              class="layout_background"
+              style={{
+                margin: "24px 16px",
+                minHeight: 280,
+              }}
+            >
+              <router-view />
+            </Content>
+          </Layout>
+        </Layout>
+      );
+    };
+  },
+});
+
+const HomeStyle = css`
+  width: 100%;
+  height: 100vh;
+  .ant-layout-content {
+    flex: 1;
+    overflow: auto;
+  }
+  .layout_background {
+    background-color: #fff;
+  }
+`;

+ 106 - 0
src/pages/collocation/components/FormItem.tsx

@@ -0,0 +1,106 @@
+import { css, cx } from "@linaria/core";
+import { Form } from "ant-design-vue";
+import { defineComponent, reactive } from "vue";
+import { bool, string } from "vue-types";
+
+export default defineComponent({
+  props: {
+    label: string(),
+    border: bool().def(true),
+    disabled: bool(),
+    ...Form.Item.props,
+  },
+  setup(props, { slots }) {
+    const state = reactive({
+      focused: false,
+    });
+
+    return () => {
+      const { disabled, border, ...restProps } = props;
+      return (
+        <Form.Item
+          class={cx(
+            formItemStyles,
+            border && "border",
+            disabled && "disabled",
+            state.focused ? "focus" : ""
+          )}
+          {...props}
+        >
+          {slots.default?.({
+            disabled,
+            onFocus: () => (state.focused = true),
+            onBlur: () => (state.focused = false),
+          })}
+        </Form.Item>
+      );
+    };
+  },
+});
+
+const formItemStyles = css`
+  transition: all 0.3s;
+  align-items: center;
+  &.border {
+    border-bottom: 1px solid #efefef;
+  }
+  /* disabled */
+  &.disabled {
+    label {
+      cursor: not-allowed;
+      color: rgba(0, 0, 0, 0.25) !important;
+    }
+    .ant-select-disabled.ant-select:not(.ant-select-customize-input)
+      .ant-select-selector,
+    .ant-input-affix-wrapper-disabled {
+      background-color: #fff !important;
+    }
+  }
+  &:not(.disabled) {
+    &:hover,
+    &.focus {
+      border-color: #5a7bef;
+    }
+    &.focus {
+      box-shadow: 0px 1px 0px rgb(90 122 239 / 20%),
+        0px 2px 0px rgb(90 122 239 / 10%) !important;
+    }
+  }
+
+  /* error  */
+  &.ant-form-item-has-error {
+    border-color: #ff4d4f !important;
+    &:hover,
+    &.focus {
+    }
+    &.focus {
+      box-shadow: 0px 1px 0px rgb(255 77 79 / 20%),
+        0px 2px 0px rgb(255 77 79 / 10%) !important;
+    }
+  }
+
+  &.ant-form-item-with-help {
+    margin-bottom: 24px;
+    .ant-form-item-explain {
+      position: absolute;
+      left: 0;
+      bottom: -26px;
+    }
+  }
+
+  .ant-form-item-label {
+    width: 70px;
+    label {
+      color: #666;
+    }
+  }
+
+  /* border style */
+  .ant-select,
+  .ant-input,
+  .ant-select-selector,
+  .ant-input-affix-wrapper {
+    border: none !important;
+    box-shadow: none !important;
+  }
+`;

+ 5 - 0
src/pages/collocation/index.ts

@@ -0,0 +1,5 @@
+import { startApp } from "@/App";
+import { initCollocationCtx } from "@/modules-ctx/collocationCtx";
+import router from "./router";
+
+startApp(router, [initCollocationCtx]);

+ 60 - 0
src/pages/collocation/router.ts

@@ -0,0 +1,60 @@
+import { createRouter, createWebHashHistory } from "vue-router";
+import BasicLayout from "./components/BasicLayout";
+import PageView from "../components/layouts/PageView";
+import BlankLayout from "../components/layouts/BlankLayout";
+
+const VUE_APP_ENV = process.env.VUE_APP_ENV;
+
+const routes = [
+  {
+    path: "/",
+    name: "index",
+    component: BasicLayout,
+    redirect: "/design",
+    children: [
+      {
+        path: "/design",
+        name: "款式管理",
+        component: PageView,
+        redirect: "/design/list",
+        children: [
+          {
+            path: "/design/list",
+            name: "款式列表",
+            component: () => import("./Home"),
+          },
+        ],
+      },
+    ],
+  },
+  {
+    path: "/editor",
+    name: "editor",
+    component: BlankLayout,
+    redirect: "/",
+    children: [
+      {
+        path: "/design/edit/:id",
+        name: "editor",
+        component: () => import("./Editor"),
+      },
+    ],
+  },
+];
+
+const router = createRouter({
+  history: createWebHashHistory(), // process.env.BASE_URL
+  routes,
+});
+
+export default router;
+
+export const menuData = GenerateRoutes(routes);
+
+function GenerateRoutes(data: any) {
+  const accessedRouters = data.filter((route: any) => {
+    return route.name == "index";
+  });
+
+  return accessedRouters[0];
+}

+ 9 - 0
src/pages/components/layouts/BlankLayout.tsx

@@ -0,0 +1,9 @@
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  setup() {
+    return () => {
+      return <router-view />;
+    };
+  },
+});

+ 9 - 0
src/pages/components/layouts/PageView.tsx

@@ -0,0 +1,9 @@
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  setup() {
+    return () => {
+      return <router-view />;
+    };
+  },
+});

+ 5 - 0
src/pages/components/layouts/index.js

@@ -0,0 +1,5 @@
+import BlankLayout from "./BlankLayout";
+import BasicLayout from "./BasicLayout";
+import PageView from "./PageView";
+
+export { BasicLayout, BlankLayout, PageView };

+ 5 - 0
src/pages/login/index.ts

@@ -0,0 +1,5 @@
+import { startApp } from "@/App";
+import { initAuthDef } from "@/modules/_default/auth";
+import router from "./router";
+
+startApp(router, [initAuthDef]);

+ 26 - 0
src/pages/login/router.ts

@@ -0,0 +1,26 @@
+import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
+
+const routes: Array<RouteRecordRaw> = [
+  {
+    path: "/",
+    name: "login",
+    component: () => import("@queenjs-modules/auth/components/Login"),
+  },
+  // {
+  //   path: "/forget",
+  //   name: "forget",
+  //   component: () => import("@queenjs-modules/auth/components/Forget"),
+  // },
+  // {
+  //   path: "/register",
+  //   name: "register",
+  //   component: () => import("@queenjs-modules/auth/components/Register"),
+  // },
+];
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+});
+
+export default router;

+ 0 - 23
src/router/index.ts

@@ -1,23 +0,0 @@
-import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
-
-const router = createRouter({
-  history: createWebHistory(import.meta.env.BASE_URL),
-  routes: [
-    {
-      path: '/',
-      name: 'home',
-      component: HomeView
-    },
-    {
-      path: '/about',
-      name: 'about',
-      // route level code-splitting
-      // this generates a separate chunk (About.[hash].js) for this route
-      // which is lazy-loaded when the route is visited.
-      component: () => import('../views/AboutView.vue')
-    }
-  ]
-})
-
-export default router

+ 49 - 0
src/styles/global.less

@@ -0,0 +1,49 @@
+@import "./theme";
+
+html,
+body {
+  color: @inf-text-color;
+  background-color: @inf-layout-bg;
+}
+
+#app {
+  min-height: 100vh;
+  min-width: 1280px;
+}
+
+.text-color {
+  color: @inf-text-color;
+}
+.text-sub-color {
+  color: @inf-text-sub-color;
+}
+.text-less-color {
+  color: @inf-text-less-color;
+}
+.primary-color {
+  color: @inf-primary-color;
+}
+
+@direction: top, bottom, left, right;
+each(@direction, {
+  .border-@{value} {
+    border-@{value}: 1px solid @inf-border-color;
+  }
+});
+
+.scrollbar {
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: @inf-primary-bg;
+    border-radius: 8px;
+  }
+  &::-webkit-scrollbar-thumb:hover {
+    background: @inf-primary-hover-bg;
+  }
+}

+ 2 - 0
src/styles/index.ts

@@ -0,0 +1,2 @@
+import "windi.css";
+import "./global.less";

+ 12 - 0
src/styles/theme-antd.js

@@ -0,0 +1,12 @@
+const { getThemeVariables } = require("ant-design-vue/dist/theme");
+
+module.exports = Object.assign(
+  getThemeVariables({
+    dark: false,
+  }),
+  {
+    "primary-color": "#6274DB",
+    "component-background": "#ffffff",
+    'tree-title-height': "32px"
+  }
+);

+ 29 - 0
src/styles/theme.less

@@ -0,0 +1,29 @@
+@import "@queenjs/theme/less/light";
+
+@inf-primary-color: #6274db;
+@inf-primary-hover-color: darken(@inf-primary-color, 5%);
+@inf-primary-active-color: saturate(darken(@inf-primary-color, 5%), 20%);
+@inf-primary-fade-color: fade(@inf-primary-color, 10%);
+
+@inf-text-color: #333333;
+@inf-text-active-color: darken(@inf-text-color, 10%);
+@inf-text-passive-color: lighten(@inf-text-color, 10%);
+@inf-text-error-fade-color: fade(@inf-text-error-color, 10%);
+@inf-text-sub-color: #666666;
+@inf-text-less-color: #999999;
+
+@inf-mask-bg: fade(#000000, 50%);
+
+@inf-border-color: #eaeaea;
+
+// @inf-layout-bg: #EEF0F4;
+
+@inf-header-bg: #242736;
+@inf-header-color: #e6f2ff;
+@inf-header-active-color: #89a9ff;
+
+@inf-primary-bg: #eff4ff;
+@inf-primary-hover-bg: #d8e4ff;
+
+@inf-header-height: 72px;
+@inf-input-padding-inline: 0;

+ 290 - 0
src/typings/asset.d.ts

@@ -0,0 +1,290 @@
+declare type RequiredPick<T, K extends keyof T> = Partial<T> & Pick<T, K>;
+
+declare interface Image {
+  url: string;
+  size: number;
+}
+
+declare type IOptions = Array<{
+  label: string;
+  value: any; //[din]
+}>;
+
+declare type ILibraryOptions = {
+  defaultOptions: {
+    options: IOptions;
+  };
+  scopeOptions?: {
+    scope: string;
+    options: IOptions;
+  };
+};
+
+declare interface OssType {
+  url: string;
+  size: number;
+}
+
+declare interface ILibrary {
+  id: string;
+  defines: IDefine[];
+  categories: any[];
+}
+
+declare interface IDefine {
+  id: string;
+  label: string;
+  collection: string;
+  type: 10 | 20 | 30 | 31 | 40; // 10 模型 20 图片 30 材质球 40 材质球组 50 环境球
+  categoryIds: any[];
+}
+
+declare interface ISource {
+  Mesh: PackageSource;
+  Material: IMaterial;
+  MaterialGroup: {
+    version: string;
+    colorCards: IMaterial[];
+  };
+  Picture: {
+    file: Image;
+  };
+  Env: {
+    config: any;
+    file: Image;
+    options: {
+      rotation: number;
+      exposure: number;
+    };
+    toneMap: {
+      method: number;
+      exposure: number;
+      brightness: number;
+      contrast: number;
+      saturation: number;
+    };
+  };
+  Scene: PackageSource;
+}
+
+interface IAssetBase<T extends keyof ISource> {
+  _id: string;
+  defineId: string;
+  taskId: string;
+
+  name: string;
+  cusNum: string;
+
+  thumbnail: Image;
+  categories: string[];
+  cusCategories: string[];
+  source: ISource[T];
+  assetState: 0 | 100 | 101 | 102 | 200; // 100 等待处理 101 正在处理 102 处理失败 200 处理成功
+  enable: boolean;
+
+  userId: string;
+  userInfo: {
+    name: string;
+    thumbnail: OssType;
+  };
+  ownerId: string;
+  ownerType: string;
+  assetType: string;
+
+  createTime: string;
+  updateTime: string;
+}
+
+declare type IAsset = IAssetBase<keyof ISource>;
+declare type IAssetMesh = IAssetBase<"Mesh">;
+declare type IAssetMat = IAssetBase<"Material">;
+declare type IAssetMatGroup = IAssetBase<"MaterialGroup">;
+declare type IAssetPic = IAssetBase<"Picture">;
+declare type IAssetEnv = IAssetBase<"Env">;
+declare type IAssetScene = IAssetBase<"Scene">;
+
+type MatFeature = {
+  factor: number;
+  color: number[];
+  texture: {
+    url: string;
+    size: number;
+  };
+  useTexture: boolean;
+  enable: boolean;
+};
+
+declare type IMaterial = {
+  id: string;
+  name: string;
+  cusNum: string;
+  thumbnail: OssType;
+  classType: "pbr" | "diamond" | "cuscolor";
+  type: "meta" | "spec";
+  uvMap: "box" | "uv";
+  cullFace: string;
+  diamond: Diamond;
+  normal: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  metalness: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  roughness: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  gloss: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  albedo: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  diffuse: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  specular: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  displace: Pick<MatFeature, "factor" | "texture" | "useTexture" | "enable">;
+  opacity: Pick<MatFeature, "factor" | "texture" | "useTexture" | "enable">;
+  uv: MaterailUv;
+  cusUv: MaterailUv;
+  userData: any;
+  from?: MatFrom;
+
+  colorMatch?: any;
+  generateType?: "colormatch" | "tech" | undefined;
+};
+
+declare class PackageSource {
+  version: string;
+  mats: PackageMaterial[];
+  geoms: PackageGeom[];
+  env3ds: PackageEnv3d[];
+  products: PackProduct[];
+  scenes: PackScene[];
+  userData: any;
+}
+
+declare class MaterailUv {
+  scale: number;
+  rotate: number;
+  offsetX: number;
+  offsetY: number;
+}
+
+declare class Diamond {
+  scaleX: number;
+  scaleY: number;
+  brightness: number;
+  color: number[];
+}
+
+declare type MatFrom = {
+  AssetConfId: string;
+  MatType: "single" | "group";
+  AssetId: string;
+};
+
+declare class PackageMaterial {
+  id: string;
+  name: string;
+  cusNum: string;
+  thumbnail: OssType;
+  type: "meta" | "spec";
+  uvMap: "box" | "uv";
+  cullFace: string;
+  diamond: Diamond;
+  normal: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  metalness: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  roughness: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  gloss: Pick<MatFeature, "factor" | "texture" | "useTexture">;
+  albedo: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  diffuse: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  specular: Pick<MatFeature, "color" | "texture" | "useTexture">;
+  displace: Pick<MatFeature, "factor" | "texture" | "useTexture" | "enable">;
+  opacity: Pick<MatFeature, "factor" | "texture" | "useTexture" | "enable">;
+  uv: MaterailUv;
+  cusUv: MaterailUv;
+  userData: any;
+  from?: MatFrom;
+}
+
+declare class Obb {
+  min: {
+    x: number;
+    y: number;
+    z: number;
+  };
+  max: {
+    x: number;
+    y: number;
+    z: number;
+  };
+}
+declare class PackageGeom {
+  id: string;
+  name: string;
+  thumbnail: OssType;
+  osgjs: OssType;
+  file?: OssType;
+  glb?: OssType;
+  shadow: OssType;
+  boundingBox: Obb;
+}
+declare class Env3dOption {
+  rotation: number;
+  exposure: number;
+}
+declare class ToneMap {
+  method: number;
+  exposure: number;
+  brightness: number;
+  contrast: number;
+  saturation: number;
+}
+
+declare class PackageEnv3d {
+  id: string;
+  name: string;
+  cusNum: string;
+  thumbnail: OssType;
+  createTime: string;
+  hdr: OssType;
+  config: any;
+  options: Env3dOption;
+  toneMap: ToneMap;
+  background: Evn3dBackground;
+  userData: any;
+}
+declare class PackProductCompMat {
+  id: string;
+  uvMap: OssType;
+  uvsize: {
+    width: number;
+    height: number;
+  };
+  name: string;
+  matId: string;
+  groupId: string;
+  index: number;
+  visible: boolean;
+  userData: any;
+  locked: boolean; //当前部件是否被锁定
+}
+declare class PackProduct {
+  id: string;
+  geomId: string;
+  name: string;
+  cusNum: string;
+  type: string;
+  thumbnail: OssType;
+  components: PackProductCompMat[];
+  userData: any;
+}
+declare class PackSceneProduct {
+  id: string;
+  prodId: string;
+  transform: {
+    pos: number[];
+    scale: number[];
+    rotation: number[];
+  };
+  visible: boolean;
+  noShadow: boolean;
+}
+declare class PackScene {
+  id: string;
+  name: string;
+  thumbnail: OssType;
+  envId: string;
+  lights: [];
+  products: PackSceneProduct[];
+  userData: any;
+}

+ 16 - 0
src/typings/pro.d.ts

@@ -0,0 +1,16 @@
+declare interface TableListParams<T> {
+  page: number;
+  size: number;
+  query?: T;
+}
+
+declare type TableListResult<T> = Promise<{
+  errorNo: number;
+  errorDesc: string;
+  result: {
+    list: T[];
+    page: number;
+    size: number;
+    total: number;
+  };
+}>;

+ 0 - 15
src/views/AboutView.vue

@@ -1,15 +0,0 @@
-<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>

+ 0 - 9
src/views/HomeView.vue

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

+ 33 - 10
tsconfig.json

@@ -1,16 +1,39 @@
 {
-  "extends": "@vue/tsconfig/tsconfig.web.json",
-  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
   "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "moduleResolution": "node",
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "forceConsistentCasingInFileNames": true,
+    "useDefineForClassFields": true,
+    "keyofStringsOnly": true,
+    "sourceMap": true,
     "baseUrl": ".",
+    "types": [
+      "webpack-env",
+    ],
     "paths": {
-      "@/*": ["./src/*"]
-    }
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "lib": [
+      "esnext",
+      "dom",
+      "dom.iterable",
+      "scripthost"
+    ]
   },
-
-  "references": [
-    {
-      "path": "./tsconfig.node.json"
-    }
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue"
+  ],
+  "exclude": [
+    "node_modules"
   ]
-}
+}

+ 0 - 8
tsconfig.node.json

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

+ 0 - 15
vite.config.ts

@@ -1,15 +0,0 @@
-import { fileURLToPath, URL } from 'node:url'
-
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import vueJsx from '@vitejs/plugin-vue-jsx'
-
-// https://vitejs.dev/config/
-export default defineConfig({
-  plugins: [vue(), vueJsx()],
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  }
-})

+ 87 - 0
vue.config.js

@@ -0,0 +1,87 @@
+const { defineConfig } = require("@vue/cli-service");
+const linariaLessLoader = require("@queenjs/webpack-loader/linaria-loader");
+const modifyVars = require("./src/styles/theme-antd");
+const path = require("path");
+
+// const publicPath =
+//   process.env.NODE_ENV === "production"
+//     ? `//infishwaibao.oss-cn-chengdu.aliyuncs.com/cloud/v1/`
+//     : "./";
+const publicPath = "./";
+
+module.exports = defineConfig({
+  pages: {
+    index: {
+      entry: "src/pages/collocation/index.ts",
+      title: "智能搭配",
+    },
+    login: {
+      entry: "src/pages/login/index.ts",
+      title: "登录",
+    },
+  },
+  outputDir: "static",
+  publicPath: publicPath,
+  transpileDependencies: true,
+  chainWebpack: (config) => {
+    const tsRule = config.module.rule("ts");
+    tsRule
+      .use("moduse-loader")
+      .loader("moduse/webpack-loader")
+      .options({
+        include: [
+          path.resolve(__dirname, "./src/modules"),
+          path.resolve(__dirname, "./src/modules-ctx"),
+          path.resolve(__dirname, "./src/modules-package"),
+          path.resolve(__dirname, "./node_modules/@queenjs-modules"),
+        ],
+      });
+
+    const tsxRule = config.module.rule("tsx");
+    tsxRule.uses.store.delete("thread-loader");
+    tsxRule
+      .use("@linaria/webpack-loader")
+      .loader("@linaria/webpack-loader")
+      .options({
+        // 将.css文件更名为.less
+        extension: ".linaria.less",
+        preprocessor: linariaLessLoader("@/styles/theme"),
+      })
+      .before("ts-loader");
+  },
+  css: {
+    loaderOptions: {
+      less: {
+        lessOptions: {
+          modifyVars,
+          javascriptEnabled: true,
+        },
+      },
+    },
+  },
+  pluginOptions: {
+    windicss: {
+      scan: {
+        dirs: ["node_modules/@queenjs-modules"],
+        fileExtensions: ["tsx"],
+      },
+      preflight: false,
+      onOptionsResolved: (options) => {
+        // make sure file @apply's get transformed
+        options.scanOptions.extraTransformTargets = {
+          css: [
+            (f) => {
+              if (/node_modules\\@queenjs-modules\\.+\.(css|less)/.test(f)) {
+                return true;
+              }
+              return false;
+            },
+          ],
+          detect: [],
+        };
+        return options;
+      },
+      // see https://github.com/windicss/vite-plugin-windicss/blob/main/packages/plugin-utils/src/options.ts
+    },
+  },
+});

File diff suppressed because it is too large
+ 3616 - 957
yarn.lock


Some files were not shown because too many files changed in this diff