commit 0c937025b77127e96a25f57c06b14e433f2c2b2c Author: Xiang Silian Date: Thu Dec 25 09:56:11 2025 +0800 save code diff --git a/.env b/.env new file mode 100644 index 0000000..ea9d6f0 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +#后台接口地址 +VITE_API_URL=http://putontwo.aidigitalfield.com + diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..882865b --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,35 @@ +/* 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', + 'vue-global-api', + // 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript' + ], + parserOptions: { + ecmaVersion: 'latest' + }, + rules: { + 'vue/multi-word-component-names': 0, + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + + // 'newlines-between': 'always', + + alphabetize: { + order: 'asc', + caseInsensitive: true + } + } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c5971b --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local +auto-imports.d.ts +components.d.ts +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo +pnpm-lock.yaml + +helper.json +deploy.mjs diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..66e2335 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..93ea3e7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6bc602 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# puton +技术设计: https://twjgz6ufn9.feishu.cn/docx/QxYZdj5agoPZKWxzXaRcUZnknMX + +UI: https://www.figma.com/design/ow4g9BhwPcmTAYJiCNVbsN/AI%E7%BE%8E%E6%90%AD%E5%8A%A9%E6%89%8B?node-id=1650-2246&t=TPutnQiEWkaaUVNi-0 + + +Element Plus 自动引入图标的实现原理是从 iconify 中自动导入所需要的图标,而 iconify 是一个图标库,包含 Element Plus、Ant Design 等图标集,在具体使用时,语法为 ,不能直接使用 element plus 中支持的 <图标名 /> 语法。 +https://icon-sets.iconify.design/?query=edit +```html + + + + + +``` +This template should help get you started developing with Vue 3 in Vite. + +token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTkxMzA5NjcsInVzZXJuYW1lIjoiMTU3MzI4OTI4ODMifQ.2rxlU2yYD4Xqj92jOd9j0UxVrG-Aq6g6DEiE3o-7YRY + +https://antoniandre.github.io/splitpanes/example-another-view + +https://detail.vip.com/detail-1710617435-6920666561686120027.html + +老网站: http://puton.aidigitalfield.com/ + +接口文档: https://i8ar7va0x1.feishu.cn/docx/R6Nod8bcPohRh2xOYbAceof4nxd + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..29fa3ea --- /dev/null +++ b/env.d.ts @@ -0,0 +1,9 @@ +/// + +declare module '*.vue' { + import { App, defineComponent } from 'vue' + const component: ReturnType & { + install(app: App): void + } + export default component +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..8d4d4c7 --- /dev/null +++ b/index.html @@ -0,0 +1,52 @@ + + + + + + + PutOn美搭助手_AI穿搭平台_数字力场 + + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..85639cf --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "puton", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "bp": "pnpm build && node deploy.mjs", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@types/axios": "^0.14.0", + "@vueuse/core": "^10.11.0", + "@vueuse/integrations": "^10.11.0", + "axios": "^1.3.4", + "element-plus": "^2.7.6", + "eslint-plugin-import": "^2.29.1", + "lodash": "^4.17.21", + "node-ssh": "^13.2.0", + "normalize.css": "^8.0.1", + "pinia": "^2.1.7", + "splitpanes": "^3.1.5", + "swiper": "^11.1.4", + "universal-cookie": "^6", + "unplugin-icons": "^0.19.0", + "vue": "^3.4.29", + "vue-awesome-swiper": "^5.0.1", + "vue-global-api": "^0.4.1", + "vue-i18n": "9", + "vue-router": "^4.3.3", + "vue-virtual-scroller": "2.0.0-beta.8" + }, + "devDependencies": { + "@iconify-json/ep": "^1.1.15", + "@rushstack/eslint-patch": "^1.8.0", + "@tsconfig/node20": "^20.1.4", + "@types/lodash": "^4.17.6", + "@types/node": "^20.14.5", + "@types/splitpanes": "^2.2.6", + "@vitejs/plugin-vue": "^5.0.5", + "@vitejs/plugin-vue-jsx": "^4.0.0", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.23.0", + "npm-run-all2": "^6.2.0", + "prettier": "^3.2.5", + "sass": "^1.77.6", + "typescript": "~5.4.0", + "unplugin-auto-import": "^0.17.6", + "unplugin-vue-components": "^0.27.2", + "vite": "^5.3.1", + "vue-tsc": "^2.0.21" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3d2d77b Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..d5dcd2b --- /dev/null +++ b/src/App.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +: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; + 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; +} diff --git a/src/assets/imgs/Add.svg b/src/assets/imgs/Add.svg new file mode 100644 index 0000000..1c4ee37 --- /dev/null +++ b/src/assets/imgs/Add.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/imgs/arrow_down.png b/src/assets/imgs/arrow_down.png new file mode 100644 index 0000000..738fc1e Binary files /dev/null and b/src/assets/imgs/arrow_down.png differ diff --git a/src/assets/imgs/bg.png b/src/assets/imgs/bg.png new file mode 100644 index 0000000..768d0df Binary files /dev/null and b/src/assets/imgs/bg.png differ diff --git a/src/assets/imgs/close.png b/src/assets/imgs/close.png new file mode 100644 index 0000000..c739073 Binary files /dev/null and b/src/assets/imgs/close.png differ diff --git a/src/assets/imgs/cover.png b/src/assets/imgs/cover.png new file mode 100644 index 0000000..a28d806 Binary files /dev/null and b/src/assets/imgs/cover.png differ diff --git a/src/assets/imgs/hair_upload.png b/src/assets/imgs/hair_upload.png new file mode 100644 index 0000000..4f209bd Binary files /dev/null and b/src/assets/imgs/hair_upload.png differ diff --git a/src/assets/imgs/jf_logo.png b/src/assets/imgs/jf_logo.png new file mode 100644 index 0000000..7454599 Binary files /dev/null and b/src/assets/imgs/jf_logo.png differ diff --git a/src/assets/imgs/juyun.png b/src/assets/imgs/juyun.png new file mode 100644 index 0000000..ab31425 Binary files /dev/null and b/src/assets/imgs/juyun.png differ diff --git a/src/assets/imgs/logo.png b/src/assets/imgs/logo.png new file mode 100644 index 0000000..8e68110 Binary files /dev/null and b/src/assets/imgs/logo.png differ diff --git a/src/assets/imgs/multiple.png b/src/assets/imgs/multiple.png new file mode 100644 index 0000000..45302af Binary files /dev/null and b/src/assets/imgs/multiple.png differ diff --git a/src/assets/imgs/multipleActivated.png b/src/assets/imgs/multipleActivated.png new file mode 100644 index 0000000..6a0a6d5 Binary files /dev/null and b/src/assets/imgs/multipleActivated.png differ diff --git a/src/assets/imgs/noSelected.png b/src/assets/imgs/noSelected.png new file mode 100644 index 0000000..5ab3717 Binary files /dev/null and b/src/assets/imgs/noSelected.png differ diff --git a/src/assets/imgs/person.png b/src/assets/imgs/person.png new file mode 100644 index 0000000..43ead5e Binary files /dev/null and b/src/assets/imgs/person.png differ diff --git a/src/assets/imgs/search.png b/src/assets/imgs/search.png new file mode 100644 index 0000000..29f1b17 Binary files /dev/null and b/src/assets/imgs/search.png differ diff --git a/src/assets/imgs/selected.png b/src/assets/imgs/selected.png new file mode 100644 index 0000000..4e18993 Binary files /dev/null and b/src/assets/imgs/selected.png differ diff --git a/src/assets/imgs/service.png b/src/assets/imgs/service.png new file mode 100644 index 0000000..a70f235 Binary files /dev/null and b/src/assets/imgs/service.png differ diff --git a/src/assets/imgs/serviceEn.png b/src/assets/imgs/serviceEn.png new file mode 100644 index 0000000..6275d1f Binary files /dev/null and b/src/assets/imgs/serviceEn.png differ diff --git a/src/assets/imgs/single.png b/src/assets/imgs/single.png new file mode 100644 index 0000000..49be42d Binary files /dev/null and b/src/assets/imgs/single.png differ diff --git a/src/assets/imgs/singleActivated.png b/src/assets/imgs/singleActivated.png new file mode 100644 index 0000000..6d6c5d5 Binary files /dev/null and b/src/assets/imgs/singleActivated.png differ diff --git a/src/assets/imgs/upload.png b/src/assets/imgs/upload.png new file mode 100644 index 0000000..655b53b Binary files /dev/null and b/src/assets/imgs/upload.png differ diff --git a/src/assets/imgs/uploadEn.png b/src/assets/imgs/uploadEn.png new file mode 100644 index 0000000..47f28b7 Binary files /dev/null and b/src/assets/imgs/uploadEn.png differ diff --git a/src/assets/imgs/vector.png b/src/assets/imgs/vector.png new file mode 100644 index 0000000..dedc3c1 Binary files /dev/null and b/src/assets/imgs/vector.png differ diff --git a/src/assets/imgs/video_play.png b/src/assets/imgs/video_play.png new file mode 100644 index 0000000..3fa090f Binary files /dev/null and b/src/assets/imgs/video_play.png differ diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,35 @@ +@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; + padding: 3px; +} + +@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; + } +} diff --git a/src/auto-import.d.ts b/src/auto-import.d.ts new file mode 100644 index 0000000..5758cc8 --- /dev/null +++ b/src/auto-import.d.ts @@ -0,0 +1,160 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const ApiEnum: typeof import('./enum/ApiEnum')['ApiEnum'] + const CacheEnum: typeof import('./enum/CacheEnum')['CacheEnum'] + const EffectScope: typeof import('vue')['EffectScope'] + const ElLoading: typeof import('element-plus/es')['ElLoading'] + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] + const HttpCodeEnum: typeof import('./enum/HttpCodeEnum')['HttpCodeEnum'] + const MenuEnum: typeof import('./enum/MenuEnum')['MenuEnum'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const effectScope: typeof import('vue')['effectScope'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useLink: typeof import('vue-router')['useLink'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') + // @ts-ignore + export type { ApiEnum } from './enum/ApiEnum' + import('./enum/ApiEnum') + // @ts-ignore + export type { CacheEnum } from './enum/CacheEnum' + import('./enum/CacheEnum') + // @ts-ignore + export type { HttpCodeEnum } from './enum/HttpCodeEnum' + import('./enum/HttpCodeEnum') + // @ts-ignore + export type { MenuEnum } from './enum/MenuEnum' + import('./enum/MenuEnum') +} +// for vue template auto import +import { UnwrapRef } from 'vue' +declare module 'vue' { + interface GlobalComponents {} + interface ComponentCustomProperties { + readonly ApiEnum: UnwrapRef + readonly CacheEnum: UnwrapRef + readonly EffectScope: UnwrapRef + readonly ElMessage: UnwrapRef + readonly ElMessageBox: UnwrapRef + readonly HttpCodeEnum: UnwrapRef + readonly MenuEnum: UnwrapRef + readonly computed: UnwrapRef + readonly createApp: UnwrapRef + readonly customRef: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef + readonly effectScope: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly h: UnwrapRef + readonly inject: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef + readonly markRaw: UnwrapRef + readonly nextTick: UnwrapRef + readonly onActivated: UnwrapRef + readonly onBeforeMount: UnwrapRef + readonly onBeforeRouteLeave: UnwrapRef + readonly onBeforeRouteUpdate: UnwrapRef + readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onErrorCaptured: UnwrapRef + readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef + readonly onScopeDispose: UnwrapRef + readonly onServerPrefetch: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef + readonly provide: UnwrapRef + readonly reactive: UnwrapRef + readonly readonly: UnwrapRef + readonly ref: UnwrapRef + readonly resolveComponent: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef + readonly shallowRef: UnwrapRef + readonly toRaw: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef + readonly triggerRef: UnwrapRef + readonly unref: UnwrapRef + readonly useAttrs: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVars: UnwrapRef + readonly useLink: UnwrapRef + readonly useRoute: UnwrapRef + readonly useRouter: UnwrapRef + readonly useSlots: UnwrapRef + readonly watch: UnwrapRef + readonly watchEffect: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef + } +} diff --git a/src/components/ClothesBox.vue b/src/components/ClothesBox.vue new file mode 100644 index 0000000..7c21c8e --- /dev/null +++ b/src/components/ClothesBox.vue @@ -0,0 +1,505 @@ + + + + + diff --git a/src/components/ClothesPool.vue b/src/components/ClothesPool.vue new file mode 100644 index 0000000..d7cbbf6 --- /dev/null +++ b/src/components/ClothesPool.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/components/DropdownMenu.vue b/src/components/DropdownMenu.vue new file mode 100644 index 0000000..3df6db8 --- /dev/null +++ b/src/components/DropdownMenu.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/src/components/GenderSelect.vue b/src/components/GenderSelect.vue new file mode 100644 index 0000000..ceab555 --- /dev/null +++ b/src/components/GenderSelect.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/components/History.vue b/src/components/History.vue new file mode 100644 index 0000000..e9de4bf --- /dev/null +++ b/src/components/History.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/components/HistoryScroll.vue b/src/components/HistoryScroll.vue new file mode 100644 index 0000000..6963e00 --- /dev/null +++ b/src/components/HistoryScroll.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/src/components/Menu.vue b/src/components/Menu.vue new file mode 100644 index 0000000..a44fef0 --- /dev/null +++ b/src/components/Menu.vue @@ -0,0 +1,175 @@ + + + + + + + diff --git a/src/components/ModelItemCard.vue b/src/components/ModelItemCard.vue new file mode 100644 index 0000000..39aee70 --- /dev/null +++ b/src/components/ModelItemCard.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/src/components/MyButton.vue b/src/components/MyButton.vue new file mode 100644 index 0000000..7adcfa8 --- /dev/null +++ b/src/components/MyButton.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/MyCard.vue b/src/components/MyCard.vue new file mode 100644 index 0000000..3de802e --- /dev/null +++ b/src/components/MyCard.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/MyIcons.vue b/src/components/MyIcons.vue new file mode 100644 index 0000000..511991d --- /dev/null +++ b/src/components/MyIcons.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/components/PickAndUploadImage.vue b/src/components/PickAndUploadImage.vue new file mode 100644 index 0000000..78904fa --- /dev/null +++ b/src/components/PickAndUploadImage.vue @@ -0,0 +1,428 @@ + + + + + diff --git a/src/components/ScrollWithBar.vue b/src/components/ScrollWithBar.vue new file mode 100644 index 0000000..b3d5338 --- /dev/null +++ b/src/components/ScrollWithBar.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/components/SearchInput.vue b/src/components/SearchInput.vue new file mode 100644 index 0000000..c835e6e --- /dev/null +++ b/src/components/SearchInput.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/SingleCheckbox.vue b/src/components/SingleCheckbox.vue new file mode 100644 index 0000000..d4b396f --- /dev/null +++ b/src/components/SingleCheckbox.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/components/TheWelcome.vue b/src/components/TheWelcome.vue new file mode 100644 index 0000000..49d8f73 --- /dev/null +++ b/src/components/TheWelcome.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/components/VpShopLinkToGetPictures.vue b/src/components/VpShopLinkToGetPictures.vue new file mode 100644 index 0000000..f703448 --- /dev/null +++ b/src/components/VpShopLinkToGetPictures.vue @@ -0,0 +1,72 @@ + + + + + + + diff --git a/src/components/WelcomeItem.vue b/src/components/WelcomeItem.vue new file mode 100644 index 0000000..bcce88d --- /dev/null +++ b/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/components/icons/Group 50.svg b/src/components/icons/Group 50.svg new file mode 100644 index 0000000..bd84a3d --- /dev/null +++ b/src/components/icons/Group 50.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/icons/IconAdd.vue b/src/components/icons/IconAdd.vue new file mode 100644 index 0000000..91d461d --- /dev/null +++ b/src/components/icons/IconAdd.vue @@ -0,0 +1,23 @@ + diff --git a/src/components/icons/IconCommunity.vue b/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconDelete.vue b/src/components/icons/IconDelete.vue new file mode 100644 index 0000000..5acc889 --- /dev/null +++ b/src/components/icons/IconDelete.vue @@ -0,0 +1,27 @@ + diff --git a/src/components/icons/IconDocumentation.vue b/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconDrag.vue b/src/components/icons/IconDrag.vue new file mode 100644 index 0000000..60e6b94 --- /dev/null +++ b/src/components/icons/IconDrag.vue @@ -0,0 +1,13 @@ + diff --git a/src/components/icons/IconEcosystem.vue b/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconEdit.vue b/src/components/icons/IconEdit.vue new file mode 100644 index 0000000..da200ed --- /dev/null +++ b/src/components/icons/IconEdit.vue @@ -0,0 +1,8 @@ + diff --git a/src/components/icons/IconLink.vue b/src/components/icons/IconLink.vue new file mode 100644 index 0000000..ae6e3de --- /dev/null +++ b/src/components/icons/IconLink.vue @@ -0,0 +1,12 @@ + diff --git a/src/components/icons/IconSearch.vue b/src/components/icons/IconSearch.vue new file mode 100644 index 0000000..7faa620 --- /dev/null +++ b/src/components/icons/IconSearch.vue @@ -0,0 +1,15 @@ + diff --git a/src/components/icons/IconSupport.vue b/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconTooling.vue b/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/src/components/icons/IconUpload.vue b/src/components/icons/IconUpload.vue new file mode 100644 index 0000000..93adff7 --- /dev/null +++ b/src/components/icons/IconUpload.vue @@ -0,0 +1,36 @@ + diff --git a/src/components/icons/Rectangle 27.svg b/src/components/icons/Rectangle 27.svg new file mode 100644 index 0000000..5fbb2af --- /dev/null +++ b/src/components/icons/Rectangle 27.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..00e9f3e --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,82 @@ +import { MenuEnum } from '@/enum/MenuEnum' +import i18n from '@/i18n' + +const { t } = i18n.global + +export const MENU_CONFIG = computed(() => { + return [ + { + name: t(`nav.menus.all`), + value: MenuEnum.ALL, + status: true + }, + + { + name: t(`nav.menus.tops`), + value: MenuEnum.UPPER_OUTER_GARMENT, + status: true + }, + + { + name: t(`nav.menus.bottoms`), + value: MenuEnum.BOTTOMS, + status: true + }, + + { + name: t(`nav.menus.dresses`), + value: MenuEnum.DRESS, + status: true + }, + + { + name: t(`nav.menus.sets`), + value: MenuEnum.SUIT, + status: true + }, + + { + name: t(`nav.menus.hairstyles`), + value: MenuEnum.HAIRSTYLE, + status: true + }, + + { + name: t(`nav.menus.figure`), + value: MenuEnum.FIGURE, + status: true + }, + { + name: t(`nav.menus.underwear`), + value: MenuEnum.UNDERWEAR, + status: true + }, + + { + name: t(`nav.menus.coats`), + value: MenuEnum.COAT, + status: false + }, + { + name: t(`nav.menus.accessories`), + value: MenuEnum.ACCESSORIES, + status: false + }, + + { + name: t(`nav.menus.shoesAndSocks`), + value: MenuEnum.SHOES_AND_SOCKS, + status: false + }, + { + name: t(`nav.menus.backgrounds`), + value: MenuEnum.BACKGROUND, + status: false + }, + { + name: t(`nav.menus.lighting`), + value: MenuEnum.LIGHT, + status: false + } + ] +}) diff --git a/src/enum/ApiEnum.ts b/src/enum/ApiEnum.ts new file mode 100644 index 0000000..3189264 --- /dev/null +++ b/src/enum/ApiEnum.ts @@ -0,0 +1,7 @@ +export enum ApiEnum { + LOGIN = 'auth/login', + REGISTER = 'auth/register', + FORGOT_PASSWORD = 'auth/forgot-password', + CURRENT_USER = 'user/current', + BASE_CONFIG = 'config/base', +} diff --git a/src/enum/CacheEnum.ts b/src/enum/CacheEnum.ts new file mode 100644 index 0000000..a35d321 --- /dev/null +++ b/src/enum/CacheEnum.ts @@ -0,0 +1,5 @@ +export enum CacheEnum { + TOKEN_NAME = 'login_token', + REDIRECT_ROUTE_NAME = 'redirect_router_name', + HISTORY_MENU = 'history_menu', +} diff --git a/src/enum/HttpCodeEnum.ts b/src/enum/HttpCodeEnum.ts new file mode 100644 index 0000000..f99d541 --- /dev/null +++ b/src/enum/HttpCodeEnum.ts @@ -0,0 +1,50 @@ +export enum HttpCodeEnum { + CONTINUE = 100, + SWITCHING_PROTOCOLS = 101, + PROCESSING = 102, + EARLYHINTS = 103, + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NON_AUTHORITATIVE_INFORMATION = 203, + NO_CONTENT = 204, + RESET_CONTENT = 205, + PARTIAL_CONTENT = 206, + AMBIGUOUS = 300, + MOVED_PERMANENTLY = 301, + FOUND = 302, + SEE_OTHER = 303, + NOT_MODIFIED = 304, + TEMPORARY_REDIRECT = 307, + PERMANENT_REDIRECT = 308, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + PAYMENT_REQUIRED = 402, + FORBIDDEN = 403, + NOT_FOUND = 404, + METHOD_NOT_ALLOWED = 405, + NOT_ACCEPTABLE = 406, + PROXY_AUTHENTICATION_REQUIRED = 407, + REQUEST_TIMEOUT = 408, + CONFLICT = 409, + GONE = 410, + LENGTH_REQUIRED = 411, + PRECONDITION_FAILED = 412, + PAYLOAD_TOO_LARGE = 413, + URI_TOO_LONG = 414, + UNSUPPORTED_MEDIA_TYPE = 415, + REQUESTED_RANGE_NOT_SATISFIABLE = 416, + EXPECTATION_FAILED = 417, + I_AM_A_TEAPOT = 418, + MISDIRECTED = 421, + UNPROCESSABLE_ENTITY = 422, + FAILED_DEPENDENCY = 424, + PRECONDITION_REQUIRED = 428, + TOO_MANY_REQUESTS = 429, + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503, + GATEWAY_TIMEOUT = 504, + HTTP_VERSION_NOT_SUPPORTED = 505, +} diff --git a/src/enum/MenuEnum.ts b/src/enum/MenuEnum.ts new file mode 100644 index 0000000..d76f168 --- /dev/null +++ b/src/enum/MenuEnum.ts @@ -0,0 +1,27 @@ +export enum MenuEnum { + /** + * 服装库 + */ + CLOTHES_LIBRARY = 'CLOTHES_LIBRARY', + + /** + * 我的衣橱 + */ + MY_WARDROBE = 'MY_WARDROBE', + /** + * 全部 + */ + ALL = 'ALL', + UPPER_OUTER_GARMENT = 'UPPER_OUTER_GARMENT', + BOTTOMS = 'BOTTOMS', + ACCESSORIES = 'ACCESSORIES', + DRESS = 'DRESS', + SUIT = 'SUIT', + COAT = 'COAT', + HAIRSTYLE = 'HAIRSTYLE', + SHOES_AND_SOCKS = 'SHOES_AND_SOCKS', + BACKGROUND = 'BACKGROUND', + LIGHT = 'LIGHT', + FIGURE = 'FIGURE', + UNDERWEAR = 'UNDERWEAR' +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..3617d53 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,18 @@ +import { createI18n } from 'vue-i18n' +import en from './locale/en' +import zh from './locale/zh' + +const u18n = createI18n({ + locale: 'zh', + + fallbackLocale: 'zh', + + legacy: false, + + messages: { + en, + zh + } +}) + +export default u18n diff --git a/src/i18n/locale/en.ts b/src/i18n/locale/en.ts new file mode 100644 index 0000000..7eaa0f6 --- /dev/null +++ b/src/i18n/locale/en.ts @@ -0,0 +1,74 @@ +export default { + userInfo: { + name: `User {username}'s Virtual Fitting Room` + }, + + nav: { + menus: { + all: 'All', + tops: 'Tops', + bottoms: 'Bottoms', + dresses: 'Dresses', + sets: 'Sets', + coats: 'Coats', + accessories: 'Accessories', + hairstyles: 'Hairstyles', + figure: 'figure', + underwear: 'underwear', + shoesAndSocks: 'Shoes & Socks', + backgrounds: 'Backgrounds', + lighting: 'Lighting' + }, + + MENU_NAMES: [ + 'All', + 'Tops', + 'Bottoms', + 'Dresses', + 'Sets', + 'Coats', + 'Accessories', + 'Hairstyles', + 'Figure', + 'Underwear', + 'Shoes & Socks', + 'Backgrounds', + 'Lighting' + ], + + clothLib: 'Clothing Library', + myCloset: 'My Closet', + search: 'Search' + }, + + tips: { + addToCloset: 'Add to Closet', + select: 'Select', + delete: 'Delete', + buyLink: 'Purchase Link', + more: 'Add pics', + training: 'Training Your Uploads', + cancel: 'Remove from Closet', + upload: 'Upload', + placeholder: 'Enter Link for Automatic Image Retrieval', + pickImg: 'Retrieve Image', + sync: 'Sync to Clothing Library', + tryonVideo: 'Try-On Video', + tryonPic: 'HD Try-On', + tryonFast: 'Quick Try-On', + download: 'Download', + addToSet: 'Add to Set', + selectAll: 'Select All', + modifyFace: 'Facial Editing', + seek: 'For any queries or issues, kindly add our WeChat: feastfu', + warning: + 'The feature requires a paid subscription. Please add our product WeChat for consultation: feastfu', + inputBuyLink: 'Please enter the purchase link', + confirmText: 'Confirm', + cancelText: 'Cancel', + inputErrorMessage: 'Please enter a valid link address', + processing: `Processing {time} seconds remaining`, + preview: 'Preview', + uploadTip: '(1-8 sheets, the more the better the effect)' + } +} diff --git a/src/i18n/locale/zh.ts b/src/i18n/locale/zh.ts new file mode 100644 index 0000000..b55c613 --- /dev/null +++ b/src/i18n/locale/zh.ts @@ -0,0 +1,73 @@ +export default { + userInfo: { + name: '用户{username}的试衣间' + }, + + nav: { + menus: { + all: '全部', + tops: '上衣', + bottoms: '下衣', + dresses: '连衣裙', + sets: '套装', + coats: '外套', + accessories: '配饰', + hairstyles: '发型', + figure: '身材', + underwear: '内衣', + shoesAndSocks: '鞋袜', + backgrounds: '背景', + lighting: '灯光' + }, + + MENU_NAMES: [ + '全部', + '上衣', + '下衣', + '连衣裙', + '套装', + '外套', + '配饰', + '发型', + '身材', + '内衣', + '鞋袜', + '背景', + '灯光' + ], + + clothLib: '衣服库', + myCloset: '我的衣橱', + search: '搜索' + }, + + tips: { + addToCloset: '加入衣橱', + select: '选择', + delete: '删除', + buyLink: '购买链接', + more: '上传多图', + training: '模型训练中', + cancel: '取消加入', + upload: '上传', + placeholder: '输入链接自动抓取图片', + pickImg: '抓取图片', + sync: '同步到衣服库', + tryonVideo: '视频试穿', + tryonPic: '高清试穿', + tryonFast: '极速试穿', + download: '下载', + addToSet: '加入套装', + selectAll: '全选', + modifyFace: '面部修改', + seek: '咨询/投诉请添加产品微信:feastfu', + warning: '该功能需付费使用,请添加产品微信咨询:feastfu”', + inputBuyLink: '请输入购买链接', + confirmText: '确认', + cancelText: '取消', + inputErrorMessage: '请输入正确的链接地址', + processing: `加工{time}秒`, + preview: '预览', + uploadTip: '(1-8张发型图片,越多效果越好)' + } +} diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..629e2d3 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,54 @@ +import { MenuEnum } from '@/enum/MenuEnum' + +export type IClothType = + | 'topCloth' + | 'bottomCloth' + | 'dress' + | 'suit' + | 'hair' + | 'figure' + | 'underwear' + +export type IClotheInfo = { + suitList?: [IClotheInfo, IClotheInfo] + clothId: string + fileUrl: string + isHas: 'yes' | 'no' + clothType: IClothType + thumbnailFileUrl: string + fileStatus: 'disable' | 'enable' + buyLink: string | null +} + +export type IClotheList = IClotheInfo[] + +export type iGenderOptions = 'male' | 'female' + +export type IModelInfo = { + fileUrl: string + id: string + thumbnailFileUrl: string +} + +export type IMainNav = MenuEnum.CLOTHES_LIBRARY | MenuEnum.MY_WARDROBE + +export type ISubNav = + | MenuEnum.ALL + | MenuEnum.UPPER_OUTER_GARMENT + | MenuEnum.BOTTOMS + | MenuEnum.DRESS + | MenuEnum.SUIT + | MenuEnum.HAIRSTYLE + | MenuEnum.FIGURE + | MenuEnum.UNDERWEAR + +export type INavStatus = { + mainNav: IMainNav + subNav: ISubNav +} + +export type IRoomInfo = { + roomId: string | number + roomName: string + createTime?: string +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..9c469fe --- /dev/null +++ b/src/main.ts @@ -0,0 +1,16 @@ +import 'normalize.css' +import 'vue-global-api' +import { createPinia } from 'pinia' +import { createApp } from 'vue' + +import App from './App.vue' + +import i18n from '@/i18n' + +const app = createApp(App) + +app.use(createPinia()) + +app.use(i18n) + +app.mount('#app') diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue new file mode 100644 index 0000000..d7a0b95 --- /dev/null +++ b/src/pages/HomePage.vue @@ -0,0 +1,1485 @@ + + + + + + + diff --git a/src/plugins/axios/Axios.ts b/src/plugins/axios/Axios.ts new file mode 100644 index 0000000..fbd397e --- /dev/null +++ b/src/plugins/axios/Axios.ts @@ -0,0 +1,126 @@ +// import router from '@/plugins/router' +import axios from 'axios' +import { ElLoading, ElMessage } from 'element-plus' +import 'element-plus/dist/index.css' +interface IOptions { + loading?: boolean + message?: boolean + clearValidateError?: boolean +} +// const storage = useStorage() +export default class Axios { + private instance + private loading: any + private options: IOptions = { loading: true, message: true, clearValidateError: true } + constructor(config: any) { + this.instance = axios.create(config) + this.interceptors() + } + + public async request(config: any, options?: IOptions) { + this.options = Object.assign(this.options, options ?? {}) + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { + const response = await this.instance.request(config) + resolve(response.data) + } catch (error) { + reject(error) + } + }) as Promise + } + + private interceptors() { + this.interceptorsRequest() + this.interceptorsResponse() + } + + private interceptorsRequest() { + this.instance.interceptors.request.use( + (config: any) => { + // if (!this.loading && this.options.loading) { + // this.loading = ElLoading.service({ + // background: 'rgba(255,255,255,0.1)', + // fullscreen: true + // }) + // } + // if (this.options.clearValidateError) useErrorStore().resetError() + // config.headers.Accept = 'application/json' + // config.headers.Authorization = `Bearer ${storage.get(CacheEnum.TOKEN_NAME)}` + + return config + }, + (error: any) => { + return Promise.reject(error) + } + ) + } + + private interceptorsResponse() { + this.instance.interceptors.response.use( + (response) => { + if (response.data.code !== 200) { + ElMessage({ + type: 'error', + message: response.data.message + }) + } + + return response + }, + + (error) => { + if (error.code === 'ERR_BAD_REQUEST') { + // const [subDomain] = window.location.host.split('.') + + const { protocol, pathname, host } = window.location + const targetUrl = + protocol + + `${pathname}${pathname}` + + (host.includes('jf') ? 'jf' : '') + + 'login.aidigitalfield.com' + + if (error.response.status === 401) { + if (import.meta.env.MODE != 'development') { + window.location.href = targetUrl + } + } + } + // if (this.loading) { + // this.loading.close() + // this.loading = undefined + // } + // this.options = { loading: true, message: true, clearValidateError: true } + // const { + // response: { status, data } + // } = error + // const message = data.error ?? data.message + + // switch (status) { + // case HttpCodeEnum.UNAUTHORIZED: + // storage.remove(CacheEnum.TOKEN_NAME) + // router.push({ name: RouteEnum.LOGIN }) + // break + // case HttpCodeEnum.UNPROCESSABLE_ENTITY: + // useErrorStore().setErrors(error.response.data.errors ?? error.response.data) + // break + // case HttpCodeEnum.FORBIDDEN: + // ElMessage({ type: 'error', message: message ?? '没有操作权限' }) + // break + // case HttpCodeEnum.NOT_FOUND: + // ElMessage.error('请求资源不存在') + // router.push({ name: RouteEnum.HOME }) + // break + // case HttpCodeEnum.TOO_MANY_REQUESTS: + // ElMessage({ type: 'error', message: '请求过于频繁,请稍候再试' }) + // break + // default: + // if (message) { + // ElMessage({ type: 'error', message: message ?? '服务器错误' }) + // } + // } + return Promise.reject(error) + } + ) + } +} diff --git a/src/plugins/axios/index.ts b/src/plugins/axios/index.ts new file mode 100644 index 0000000..001e088 --- /dev/null +++ b/src/plugins/axios/index.ts @@ -0,0 +1,294 @@ +import { useCookies } from '@vueuse/integrations/useCookies' +import Axios from './Axios' +import { type IClothType, type iGenderOptions, type IClotheInfo } from '@/interface' + +const cookies = useCookies(['locale']) + +const token = cookies.get('token') + +const http = new Axios({ + baseURL: '', + timeout: 300 * 1000, + headers: { + 'X-Access-Token': token + } +}) + +const setup = () => {} + +type IGetClothes = { + sex: 'male' | 'female' + clothType?: IClothType +} + +type IResponse = { + result: any + code: number + message: string +} + +export const getClothes = (data: { isPerson: 'yes' | 'no' } & IGetClothes) => + http.request({ + url: '/api/cloth/list', + method: 'post', + data + }) + +export const getAllClothes = (data: { sex: 'male' | 'female' }) => + http.request({ + url: '/api/cloth/list', + method: 'post', + data: { isPerson: 'no', ...data } + }) + +export const getMyAllClothes = (data: any) => + http.request({ + url: '/api/cloth/list', + method: 'post', + data: { isPerson: 'no', ...data } + }) + +export const grabPicture = (data: any) => + http.request< + Omit & { result: Record<'id' | 'fileUrl' | 'thumbnailFileUrl', string>[] } + >({ + url: '/api/cloth/grabPicture', + method: 'post', + data: { isPerson: 'no', ...data } + }) + +export const uploadClothFile = (data: IGetClothes) => + http.request({ + url: '/api/cloth/uploadClothFile', + method: 'post', + data: { isPerson: 'yes', ...data } + }) + +type IGetSuitClothes = { isPerson: 'yes' | 'no'; sex: 'female' | 'male' } + +export const getSuitClothes = (data: IGetSuitClothes) => + http.request({ + url: '/api/cloth/list', + method: 'post', + data: { clothType: 'suit', ...data } + }) + +/**加入我的衣橱 */ +export const addToMyClothes = (data: { clothType: IClothType; clothId: string }) => + http.request({ + url: '/api/cloth/addWardrobe', + method: 'post', + data + }) + +export const unJoinFromMyClothes = (data: { clothId: string }) => + http.request({ + url: '/api/cloth/unJoin', + method: 'post', + data + }) + +export const getModelList = (data: { sex: iGenderOptions }) => + http.request({ + url: '/api/cloth/getModelList', + method: 'post', + data + }) + +export const createSuit = (data: { topClothId: string; bottomClothId: string }) => + http.request({ + url: '/api/cloth/addSuit', + method: 'post', + data + }) + +export const tryOn = (data: { + tryOnList: { clothIds: string[]; modelId: string }[] + isHr: boolean +}) => + http.request({ + url: '/api/cloth/tryOn', + method: 'post', + data + }) + +export const deleteCloth = (data: { id: string }) => + http.request({ + url: '/api/cloth/deleteCloth', + method: 'post', + data + }) + +export const deleteModel = (data: { id: string }) => + http.request({ + url: '/api/cloth/deleteCloth', + method: 'post', + data + }) + +/** + * 获取历史批量生成数据 + */ +export const getTryOnHistory = () => + http.request({ + url: '/api/cloth/getTryOnHistory', + method: 'post' + }) + +/** + * 获取用户信息 + */ +export const getUserInfo = () => + http.request({ + url: '/api/cloth/getLoginUserInfo', + method: 'post' + }) + +/** + * 获取最近的一次生成试穿状态 + */ +export const getCostTime = () => + http.request({ + url: '/api/cloth/getCostTime', + method: 'post' + }) + +export const uploadModel = (formData: FormData) => + http.request({ + url: '/api/cloth/uploadModel', + method: 'post', + headers: { + 'Content-Type': 'multipart/form-data' + }, + + data: formData + }) + +export const uploadAvatar = (formData: FormData) => + http.request({ + url: '/api/cloth/uploadAvatar', + method: 'post', + headers: { + 'Content-Type': 'multipart/form-data' + }, + + data: formData + }) + +export const addClothes = (data: { + clothFileList: string[] + clothType: 'topCloth' | 'bottomCloth' | 'dress' + isSync: 'yes' | 'no' + sex: iGenderOptions +}) => + http.request({ + url: '/api/cloth/uploadCloth', + method: 'post', + data + }) + +export const deleteHistory = (data: { historyIds: string[] }) => + http.request({ + url: '/api/cloth/batchDelHistory', + method: 'post', + data + }) + +//获取试衣间列表 +export const uploadLink = (data: { buyLink: string; clothId: string }) => + http.request({ + url: '/api/cloth/buyLink', + method: 'post', + data + }) + +export const getRoomList = (data: { sex: iGenderOptions }) => + http.request({ + url: '/api/cloth/getRoomList', + method: 'post', + data + }) + +export const addRoom = (data: { roomName: string; sex: iGenderOptions }) => + http.request({ + url: '/api/cloth/addRoom', + method: 'post', + data + }) + +export const deleteRoom = (data: { roomId: string | number }) => + http.request({ + url: '/api/cloth/delRoom', + method: 'post', + data + }) + +export const updateRoom = (data: { roomId: string | number; roomName: string }) => + http.request({ + url: '/api/cloth/updateRoom', + method: 'post', + data + }) + +export const getRoomClothList = (data: { roomId: string | number; sex: iGenderOptions }) => + http.request({ + url: '/api/cloth/getRoomClothList', + method: 'post', + data + }) + +export const addRoomCloth = (data: { roomId: string | number; clothId: IClotheInfo['clothId'] }) => + http.request({ + url: '/api/cloth/addRoomCloth', + method: 'post', + data + }) + +export const deleteRoomCloth = (data: { + roomId: string | number + clothId: IClotheInfo['clothId'] +}) => + http.request({ + url: '/api/cloth/delRoomCloth', + method: 'post', + data + }) + +export const loginOut = () => + http.request({ + url: '/api/digital/logout', + method: 'post' + }) + +export { http, setup } + +export default { + getAllClothes, + getMyAllClothes, + getSuitClothes, + addToMyClothes, + unJoinFromMyClothes, + getModelList, + createSuit, + tryOn, + getTryOnHistory, + getUserInfo, + uploadModel, + uploadAvatar, + getClothes, + deleteCloth, + grabPicture, + deleteModel, + addClothes, + getCostTime, + deleteHistory, + uploadLink, + getRoomList, + addRoom, + deleteRoom, + updateRoom, + getRoomClothList, + addRoomCloth, + deleteRoomCloth, + loginOut +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..7d56517 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,16 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomePage from '@/pages/HomePage.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + + routes: [ + { + path: '/', + name: 'home', + component: HomePage + } + ] +}) + +export default router diff --git a/src/service/api.ts b/src/service/api.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/stores/counter.ts b/src/stores/counter.ts new file mode 100644 index 0000000..7dec24a --- /dev/null +++ b/src/stores/counter.ts @@ -0,0 +1,11 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + return { count, doubleCount, increment } +}) diff --git a/src/stores/globalStore.ts b/src/stores/globalStore.ts new file mode 100644 index 0000000..9911719 --- /dev/null +++ b/src/stores/globalStore.ts @@ -0,0 +1,333 @@ +import { type UploadUserFile } from 'element-plus' +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { IClotheInfo, IModelInfo, INavStatus, IClothType, IRoomInfo } from '@/interface' + +import jfLogoImg from '@/assets/imgs/jf_logo.png' +import juyunImg from '@/assets/imgs/juyun.png' +import logoImg from '@/assets/imgs/logo.png' + +import { MenuEnum } from '@/enum/MenuEnum' + +export const useGlobalStore = defineStore('global', () => { + const gender = ref<'male' | 'female'>('female') + + const noticeVideo = ref( + 'https://digit-person.oss-cn-beijing.aliyuncs.com/digital_cloth/1720444463478.mp4' + ) + + const creatingImgMark = ref('1720084773937.png') + const createFailMark = ref('1720084729963.png') + + const creatingAndFailMarkList = ref([creatingImgMark.value, createFailMark.value]) + + const menu = reactive({ + mainNav: MenuEnum.MY_WARDROBE, + subNav: MenuEnum.ALL + }) + + type INav = { + isPerson: 'yes' | 'no' + clothType?: IClothType + } + + const nav = computed(() => { + const isPersonMaps = { + [MenuEnum.CLOTHES_LIBRARY]: 'no', + [MenuEnum.MY_WARDROBE]: 'yes' + } + + const clothTypeMaps = { + [MenuEnum.ALL]: null, + [MenuEnum.UPPER_OUTER_GARMENT]: 'topCloth', + [MenuEnum.BOTTOMS]: 'bottomCloth', + [MenuEnum.DRESS]: 'dress', + [MenuEnum.SUIT]: 'suit', + [MenuEnum.HAIRSTYLE]: 'hair', + [MenuEnum.FIGURE]: 'figure', + [MenuEnum.UNDERWEAR]: 'underwear' + } + + const isPerson = <'yes' | 'no'>isPersonMaps[menu.mainNav] + + const clothType = clothTypeMaps[menu.subNav] + + return Object.assign({ isPerson }, clothType && { clothType }) + }) + + const updateMenu = (val: INavStatus) => { + menu.mainNav = val.mainNav + menu.subNav = val.subNav + } + + const updateGender = (genderVal: 'male' | 'female') => { + if (genderVal !== gender.value) { + gender.value = genderVal + clearAllSelected() + } + } + + const selectedTopCloth = ref() + const selectedBottomCloth = ref() + const selectedDressCloth = ref() + const selectMutipleModelIds = ref([]) + const selectMutipleClothesIds = ref<(IModelInfo['id'] | IModelInfo['id'][])[]>() + const selectedModel = ref() + + const selectedRoomInfo = ref() + + const selectedHairCloth = ref() + + const selectedFigureCloth = ref() + + const selectedUnderwearCloth = ref() + + const updateRoomInfo = (roomInfo: IRoomInfo) => { + updateMenu({ + mainNav: MenuEnum.CLOTHES_LIBRARY, + subNav: MenuEnum.ALL + }) + + nextTick(() => { + console.log('更新一下') + selectedRoomInfo.value = roomInfo + }) + } + + const clearSelectedTopCloth = () => (selectedTopCloth.value = undefined) + const clearSelectedBottomCloth = () => (selectedBottomCloth.value = undefined) + const clearSecectedDressCloth = () => (selectedDressCloth.value = undefined) + const clearSecectedMutipleModelIds = () => { + selectMutipleModelIds.value = [] + } + const clearSecectedMutipleClothesIds = () => { + selectMutipleClothesIds.value = [] + } + + const clearSelectedModel = () => (selectedModel.value = undefined) + + const clearSelectedRoomInfo = () => { + selectedRoomInfo.value = undefined + } + + const clearSelectedHairCloth = () => { + selectedHairCloth.value = undefined + } + + const clearSelectedFigureCloth = () => { + selectedFigureCloth.value = undefined + } + + const ClearselectedUnderwearCloth = () => { + selectedUnderwearCloth.value = undefined + } + + const updateModel = (data: IModelInfo) => { + selectedModel.value = data + } + + const clearSelectedCloths = () => { + clearSelectedTopCloth() + clearSelectedBottomCloth() + clearSecectedDressCloth() + + clearSelectedHairCloth() + + clearSelectedFigureCloth() + + ClearselectedUnderwearCloth() + } + + const clearAllSelected = () => { + clearSelectedCloths() + + clearSelectedModel() + + clearSecectedMutipleModelIds() + } + + const fileList = ref([]) + + const updateFileList = (data: UploadUserFile[]) => { + fileList.value = data + } + + const updateSelectedCloth = (data: IClotheInfo) => { + if (data.clothType === 'topCloth') { + clearSecectedDressCloth() + clearSelectedHairCloth() + clearSelectedFigureCloth() + ClearselectedUnderwearCloth() + selectedTopCloth.value = data + } + + if (data.clothType === 'bottomCloth') { + clearSecectedDressCloth() + clearSelectedHairCloth() + clearSelectedFigureCloth() + ClearselectedUnderwearCloth() + selectedBottomCloth.value = data + } + + if (data.clothType === 'dress') { + clearSelectedCloths() + + selectedDressCloth.value = data + } + + if (data.clothType === 'suit') { + clearSelectedCloths() + + data?.suitList?.forEach((i) => updateSelectedCloth(i)) + } + + if (data.clothType === 'hair') { + clearSelectedCloths() + + selectedHairCloth.value = data + } + + if (data.clothType === 'figure') { + clearSelectedCloths() + selectedFigureCloth.value = data + } + + if (data.clothType === 'underwear') { + clearSelectedCloths() + selectedUnderwearCloth.value = data + } + } + + const updateSelectMutipleModelIds = (modelId: IModelInfo['id']) => { + const oldValue = selectMutipleModelIds.value + + selectMutipleModelIds.value = oldValue.includes(modelId) + ? oldValue.filter((i) => i != modelId) + : [...oldValue, modelId] + } + + const updateSelectMutileClothesIds = (clothId: IClotheInfo['clothId'], item: IClotheInfo) => { + const oldValue = selectMutipleClothesIds.value + + if (item.clothType === 'suit') { + const idArr = JSON.parse(item.fileUrl) as IClotheInfo[] + + console.log('AT-[ value &&&&&********** ]', idArr) + } + + let value: string | string[] + + if (item.clothType === 'suit') { + const idArr = JSON.parse(item.fileUrl) as IClotheInfo[] + value = idArr.map((i) => i.clothId) + } else { + value = clothId + } + + console.log('AT-[ value &&&&&********** ]', value) + + if (item.clothType != 'suit') { + selectMutipleClothesIds.value = oldValue?.includes(clothId) + ? oldValue.filter((i) => i != clothId) + : [...(oldValue || []), clothId] + } else { + const index = oldValue?.findIndex((i) => i.toString() == value.toString()) + + if (index != -1) { + selectMutipleClothesIds.value = oldValue?.filter((i, idx) => index != idx) + } else { + selectMutipleClothesIds.value = [...(oldValue || []), value] + } + } + } + + type IBrandInfo = { + logo: string + title: string + mainWebsite: string + style?: Record + } + + const brandInfoMaps = ref( + new Map<'jfputon' | 'fashionroom' | 'puton' | string, IBrandInfo>([ + [ + 'jfputon', + { + logo: jfLogoImg, + title: '精分数据', + mainWebsite: 'https://jf.aidigitalfield.com', + style: { + height: '90%' + } + } + ], + + [ + 'fashionroom', + { + logo: juyunImg, + title: '聚云美搭官网', + mainWebsite: 'https://www.marshotspot.com', + style: { + height: '90%' + } + } + ], + + [ + 'puton', + { + logo: logoImg, + title: 'PutOn美搭助手_AI穿搭平台_数字力场', + mainWebsite: 'https://www.aidigitalfield.com', + style: { + width: ' 94px' + } + } + ] + ]) + ) + + return { + menu, + updateMenu, + gender, + updateGender, + selectedTopCloth, + selectedBottomCloth, + selectedDressCloth, + updateSelectedCloth, + selectedModel, + updateModel, + clearSelectedTopCloth, + clearSelectedBottomCloth, + clearSecectedDressCloth, + clearSelectedCloths, + selectMutipleModelIds, + updateSelectMutipleModelIds, + clearSecectedMutipleModelIds, + clearAllSelected, + nav, + fileList, + updateFileList, + noticeVideo, + creatingImgMark, + createFailMark, + creatingAndFailMarkList, + selectMutipleClothesIds, + clearSecectedMutipleClothesIds, + updateSelectMutileClothesIds, + selectedRoomInfo, + clearSelectedRoomInfo, + updateRoomInfo, + selectedHairCloth, + clearSelectedHairCloth, + + selectedFigureCloth, + clearSelectedFigureCloth, + brandInfoMaps, + + selectedUnderwearCloth, + ClearselectedUnderwearCloth + } +}) diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..410c16e --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,20 @@ +export const getLocalImageSize = (file: File) => + new Promise<{ width: number; height: number }>((resolve) => { + const reader = new FileReader() + + reader.onload = function (e: any) { + const data = e.target.result + const image = new Image() + + image.onload = function () { + const width = image.width + const height = image.height + + resolve({ width, height }) + } + + image.src = data + } + + reader.readAsDataURL(file) + }) diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..f6c52a3 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.vue", + "node_modules/.pnpm/unplugin-auto-import@0.17.6_@vueuse+core@9.13.0_vue@3.4.31_typescript@5.4.5___rollup@4.18.0/node_modules/unplugin-auto-import/auto-imports.d.ts", + "node_modules/.pnpm/@vue+runtime-core@3.4.31/node_modules/@vue/runtime-core/dist/runtime-core.d.ts" + ], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "types": ["element-plus/global"] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..00b8a06 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,89 @@ +import { fileURLToPath, URL } from 'node:url' + +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import _ from 'lodash' +import AutoImport from 'unplugin-auto-import/vite' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import Components from 'unplugin-vue-components/vite' +import { defineConfig, loadEnv } from 'vite' + +// export function parseEnv(env: Record): ImportMetaEnv { +export function parseEnv(env: Record): any { + const envs: any = _.cloneDeep(env) + + Object.entries(env).forEach(([key, value]) => { + if (value == 'true' || value == 'false') envs[key] = value == 'true' ? true : false + else if (/^\d+$/.test(value)) envs[key] = Number(value) + else if (value == 'null' || value == '') envs[key] = null + else if (value == 'undefined') envs[key] = undefined + }) + return envs +} +// https://vitejs.dev/config/ +export default defineConfig(({ command, mode }) => { + const isBuild = command == 'build' + const env = parseEnv(loadEnv(mode, process.cwd())) + return { + plugins: [ + vue(), + vueJsx(), + + AutoImport({ + resolvers: [ + ElementPlusResolver(), + IconsResolver({ + prefix: 'Icon' + }) + ], + imports: ['vue', 'vue-router'], + //composables目录文件按需加载 + dirs: ['src/composables/**/*', 'src/enum/**/*', 'src/store/**/*'], + dts: 'src/auto-import.d.ts', // 会自动生成此文件 + vueTemplate: true, + defaultExportByFilename: true + }), + + Components({ + resolvers: [ + // Auto register icon components + // 自动注册图标组件 + IconsResolver({ + enabledCollections: ['ep'] + }), + ElementPlusResolver() + ], + extensions: ['vue', 'tsx'], + dirs: ['src/components', 'src/layouts'], + //组件名称包含目录,防止同名组件冲突 + directoryAsNamespace: true + }), + + Icons({ + autoInstall: true + }) + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + + server: { + port: 8899, + host: true, + proxy: { + '/api': { + target: env.VITE_API_URL, + changeOrigin: true + }, + '/captcha': { + target: env.VITE_API_URL, + changeOrigin: true + } + } + } + } +})