save code

This commit is contained in:
xsl
2025-12-25 09:56:11 +08:00
commit 0c937025b7
91 changed files with 6506 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
#后台接口地址
VITE_API_URL=http://putontwo.aidigitalfield.com
+35
View File
@@ -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
}
}
]
}
}
+35
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+62
View File
@@ -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 等图标集,在具体使用时,语法为 <i-图标集英文缩写-图标名 />,不能直接使用 element plus 中支持的 <图标名 /> 语法。
https://icon-sets.iconify.design/?query=edit
```html
<i-ep-Edit />
<i-ep-edit-pen></i-ep-edit-pen>
<el-icon size="20" color="red">
<i-ep-delete></i-ep-delete>
</el-icon>
```
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
```
Vendored
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { App, defineComponent } from 'vue'
const component: ReturnType<typeof defineComponent> & {
install(app: App): void
}
export default component
}
+52
View File
@@ -0,0 +1,52 @@
<!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>PutOn美搭助手_AI穿搭平台_数字力场</title>
<style>
.el-image-viewer__actions,
.el-image-viewer__close {
background-color: rgba(0, 0, 0, 0.8) !important;
}
.custom-message-btn {
background-color: rgba(0, 0, 0, 0.8) !important;
border-radius: 0 !important;
border: none !important;
outline: none !important;
--el-button-active-color: 'none' !important;
}
.custom-cancel-message-btn {
background-color: rgba(0, 0, 0, 0.5) !important;
border-radius: 0 !important;
border: none !important;
outline: none !important;
color: #fff !important;
--el-button-active-color: 'none' !important;
}
.custom-message-box {
border-radius: 0 !important;
}
</style>
<meta
name="keywords"
content="数字力场、Puton、美搭、美搭助手、试穿、穿搭AI工具、服装搭配AI助手、时尚AI搭配工具、智能服装推荐软件、个人形象AI管理、AI穿衣搭配建议、服装AI风格指南、个性化AI穿搭建议、智能潮流穿搭助手、虚拟试衣间AI技术、AI时尚搭配平台、智能个性化服装推荐、AI服装匹配系统、服装智能推荐引擎、时尚AI智能推荐工具、个性AI穿搭建议平台、智能化服装搭配解决方案、AI时尚潮流分析、人工智能服装个性化推荐、智能化服装搭配技术、AI虚拟试衣间体验、服装智能AI搭配服务、个性化AI时尚搭配建议、时尚AI穿搭智能助手、AI服装风格智能分析、智能化个性化服装推荐、服装AI智能化搭配系统、智能AI时尚潮流推荐、AI服装搭配智能引擎、时尚AI虚拟试穿技术"
/>
<meta
name="description"
content="数字力场、Puton、美搭、美搭助手、试穿、穿搭AI工具、服装搭配AI助手、时尚AI搭配工具、智能服装推荐软件、个人形象AI管理、AI穿衣搭配建议、服装AI风格指南、个性化AI穿搭建议、智能潮流穿搭助手、虚拟试衣间AI技术、AI时尚搭配平台、智能个性化服装推荐、AI服装匹配系统、服装智能推荐引擎、时尚AI智能推荐工具、个性AI穿搭建议平台、智能化服装搭配解决方案、AI时尚潮流分析、人工智能服装个性化推荐、智能化服装搭配技术、AI虚拟试衣间体验、服装智能AI搭配服务、个性化AI时尚搭配建议、时尚AI穿搭智能助手、AI服装风格智能分析、智能化个性化服装推荐、服装AI智能化搭配系统、智能AI时尚潮流推荐、AI服装搭配智能引擎、时尚AI虚拟试穿技术"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+62
View File
@@ -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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+24
View File
@@ -0,0 +1,24 @@
<template>
<div class="bed">
<HomePage />
</div>
</template>
<script setup lang="ts">
import HomePage from '@/pages/HomePage.vue'
const globalBg = `url(https://digit-person.oss-cn-beijing.aliyuncs.com/digital_cloth/1720063985760.png)`
</script>
<style lang="scss" scoped>
.bed {
width: 100%;
min-height: 100vh;
height: 100vh;
background-image: v-bind(globalBg);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
// overflow: hidden;
}
</style>
+86
View File
@@ -0,0 +1,86 @@
/* 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;
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;
}
+12
View File
@@ -0,0 +1,12 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1743_871)">
<path d="M9.625 11H1.375C0.61875 11 0 10.3813 0 9.625V1.375C0 0.61875 0.61875 0 1.375 0H9.625C10.3813 0 11 0.61875 11 1.375V9.625C11 10.3813 10.3813 11 9.625 11ZM1.375 0.6875C0.9625 0.6875 0.6875 0.9625 0.6875 1.375V9.625C0.6875 10.0375 0.9625 10.3125 1.375 10.3125H9.625C10.0375 10.3125 10.3125 10.0375 10.3125 9.625V1.375C10.3125 0.9625 10.0375 0.6875 9.625 0.6875H1.375Z" fill="#2C2C2C"/>
<path d="M8.25 5.84375H2.75C2.54375 5.84375 2.40625 5.70625 2.40625 5.5C2.40625 5.29375 2.54375 5.15625 2.75 5.15625H8.25C8.45625 5.15625 8.59375 5.29375 8.59375 5.5C8.59375 5.70625 8.45625 5.84375 8.25 5.84375Z" fill="#2C2C2C"/>
<path d="M5.5 8.59375C5.29375 8.59375 5.15625 8.45625 5.15625 8.25V2.75C5.15625 2.54375 5.29375 2.40625 5.5 2.40625C5.70625 2.40625 5.84375 2.54375 5.84375 2.75V8.25C5.84375 8.45625 5.70625 8.59375 5.5 8.59375Z" fill="#2C2C2C"/>
</g>
<defs>
<clipPath id="clip0_1743_871">
<rect width="11" height="11" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

+35
View File
@@ -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;
}
}
+160
View File
@@ -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<typeof import('./enum/ApiEnum')['ApiEnum']>
readonly CacheEnum: UnwrapRef<typeof import('./enum/CacheEnum')['CacheEnum']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
readonly HttpCodeEnum: UnwrapRef<typeof import('./enum/HttpCodeEnum')['HttpCodeEnum']>
readonly MenuEnum: UnwrapRef<typeof import('./enum/MenuEnum')['MenuEnum']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}
+505
View File
@@ -0,0 +1,505 @@
<template>
<ScrollWithBar>
<div class="clothes-box" ref="containerRef" v-if="props.allMyClothes">
<el-row>
<el-col
:xs="12"
:sm="6"
:md="4"
:lg="3"
:xl="2"
v-for="item in _clothes"
:key="item.clothId"
>
<div class="item">
<template v-if="item.clothType === 'suit'">
<div class="suit">
<el-image
class="cover-img"
:src="item.suitList[0].thumbnailFileUrl"
fit="cover"
lazy
:scroll-container="containerRef"
/>
<div class="suit-bottomCloth">
<el-image
:src="item.suitList[1].thumbnailFileUrl"
fit="fill"
lazy
:scroll-container="containerRef"
/>
</div>
</div>
</template>
<template v-else>
<el-image
class="cover-img"
:src="item.thumbnailFileUrl"
fit="cover"
lazy
:scroll-container="containerRef"
/>
</template>
<template v-if="item.fileStatus === 'enable'">
<div class="mutiple">
<MyIcons
:type="
props.roomClothes?.some((i) => i.clothId === item.clothId)
? 'selected'
: 'noSelected'
"
cursor="pointer"
:size="16"
class="checkbox"
@click="mutipleRoomSelectClothing(item)"
/>
</div>
</template>
<template v-else>
<div class="disable-mask">
<span> {{ $t('tips.training') }} </span>
</div>
</template>
</div>
</el-col>
</el-row>
</div>
<div class="clothes-box" ref="containerRef" v-if="props.clothes">
<el-row>
<el-col
:xs="12"
:sm="6"
:md="4"
:lg="3"
:xl="2"
v-for="(item, index) in _clothes"
:key="index"
>
<div class="item">
<template v-if="item.clothType === 'suit'">
<div class="suit">
<el-image
class="cover-img"
:src="item.suitList[0].thumbnailFileUrl"
fit="cover"
lazy
:scroll-container="containerRef"
/>
<div class="suit-bottomCloth">
<el-image
:src="item.suitList[1].thumbnailFileUrl"
fit="fill"
lazy
:scroll-container="containerRef"
/>
</div>
</div>
</template>
<template v-else>
<el-image
class="cover-img"
:src="item.thumbnailFileUrl"
fit="cover"
lazy
:scroll-container="containerRef"
/>
</template>
<template v-if="item.fileStatus === 'enable'">
<template v-if="props.mutipleMode">
<div class="mutiple">
<MyIcons
:type="
(
item.clothType === 'suit'
? selectMutipleClothesIds?.some(
(i) =>
i.toString() ==
JSON.parse(item.thumbnailFileUrl)
.map((i: any) => i.clothId)
.toString()
)
: selectMutipleClothesIds?.includes(item.clothId)
)
? 'selected'
: 'noSelected'
"
cursor="pointer"
:size="16"
class="checkbox"
@click="mutipleSelectClothing(item)"
/>
</div>
</template>
<div class="tool-mask" v-else>
<div class="tool-header">
<template v-if="props.mine">
<span @click="onDelete(item)">{{ $t('tips.delete') }}</span>
<span @click="onLink(item)">{{ $t('tips.buyLink') }}</span>
<span @click="onUploadMultipleImg(item)">{{ $t('tips.more') }}</span>
</template>
<template v-else>
<span v-if="item.isHas === 'no'" @click="addToMyClothes(item)">{{
$t('tips.addToCloset')
}}</span>
<span v-else @click="unJoinFromMyClothes(item)">{{ $t('cancel') }}</span>
</template>
</div>
<div class="tool-footer">
<span @click="selectClothing(item)">{{ $t('tips.select') }}</span>
</div>
</div>
</template>
<template v-else>
<div class="disable-mask">
<span> {{ $t('tips.training') }} </span>
</div>
</template>
</div>
</el-col>
</el-row>
</div>
<div class="clothes-box" v-if="props.urls">
<el-row>
<el-col
:xs="4"
:sm="4"
:md="4"
:lg="4"
:xl="4"
v-for="(item, index) in urlsRaw"
:key="index"
>
<div
class="item history"
:class="{
noTop: index > 5,
haveLeft: (index + 1) % 6 == 1
}"
>
<el-image
class="cover-img"
:src="item.url"
fit="cover"
:preview-src-list="
creatingAndFailMarkList.some((mark) => item.url.includes(mark))
? []
: props.urls
.filter((i) => !creatingAndFailMarkList.some((mark) => i.url.includes(mark)))
.map((i) => i.url)
"
preview-teleported
:initial-index="index"
:infinite="false"
/>
<div class="tool-mask">
<div class="delete">
<span @click="onDeleteHistory(item)">{{ $t('tips.delete') }}</span>
</div>
<div
class="preview"
v-if="!creatingAndFailMarkList.some((mark) => item.url.includes(mark))"
>
<div>{{ $t('tips.preview') }}</div>
<el-image
class="previewSit"
:src="item.url"
:preview-src-list="
props.urls
.map((i) => i.url)
.filter((i) => !creatingAndFailMarkList.some((mark) => i.includes(mark)))
"
preview-teleported
:initial-index="index"
:infinite="false"
/>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
<template v-if="(props.clothes && !props.clothes.length) || (props.urls && !props.urls.length)">
<el-empty description="暂无数据" />
</template>
</ScrollWithBar>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import ScrollWithBar from '@/components/ScrollWithBar.vue'
import { type IClotheList, type IClothType, type IClotheInfo } from '@/interface'
import { useGlobalStore } from '@/stores/globalStore'
const { t } = useI18n()
const globalStore = useGlobalStore()
const { creatingAndFailMarkList, selectMutipleClothesIds } = storeToRefs(globalStore)
const { updateSelectMutileClothesIds } = globalStore
const containerRef = ref()
type IProps = {
clothes?: IClotheList
allMyClothes?: IClotheList
roomClothes?: IClotheList
urls?: { historyId: string; url: string }[]
paddingRight?: number
mine?: boolean
mutipleMode?: boolean
}
const props = defineProps<IProps>()
const urlsRaw = toRaw(props.urls)
const _clothes = computed(() =>
(props.clothes || props.allMyClothes)?.map((i) => {
let suitList = []
if (i.clothType === 'suit') {
suitList = JSON.parse(i.thumbnailFileUrl)
}
return { ...i, suitList }
})
)
const emit = defineEmits<{
addToMyClothes: [{ clothType: IClothType; clothId: string }]
unJoinFromMyClothes: [{ clothId: string }]
selectClothing: [data: IClotheInfo]
deleteClothing: [data: IClotheInfo]
onDeleteHistory: [data: any]
onUploadMultipleImg: [data: IClotheInfo]
onUploadLink: [data: { clothId: string; buyLink: string }]
mutipleSelectClothing: [data: IClotheInfo[]]
addOrRmRoomCloth: [data: IClotheInfo]
}>()
const addToMyClothes = (data: IClotheInfo) => emit('addToMyClothes', data)
const unJoinFromMyClothes = (data: IClotheInfo) => emit('unJoinFromMyClothes', data)
const selectClothing = (data: IClotheInfo) => emit('selectClothing', data)
const onDelete = (item: IClotheInfo) => emit('deleteClothing', item)
const onDeleteHistory = (item: { historyId: string; url: string }) => emit('onDeleteHistory', item)
const onLink = (item: IClotheInfo) => {
// console.log('AT-[ onLink &&&&&********** ]', item)
// alert('缺少添加购买链接接口和相关的字段')
if (!item.buyLink) {
ElMessageBox.prompt(t('tips.inputBuyLink'), {
confirmButtonText: t('tips.confirmText'),
cancelButtonText: t('tips.cancelText'),
confirmButtonClass: 'custom-message-btn',
cancelButtonClass: 'custom-cancel-message-btn',
customClass: 'custom-message-box',
inputValidator(val) {
try {
new URL(val)
return true
} catch (error) {
return false
}
},
inputErrorMessage: t('tips.inputErrorMessage')
}).then(async ({ value }) => {
emit('onUploadLink', { clothId: item.clothId, buyLink: value })
})
} else {
window.location.href = item.buyLink
}
}
const onUploadMultipleImg = (item: IClotheInfo) => emit('onUploadMultipleImg', item)
const mutipleSelectClothing = (item: IClotheInfo) => {
updateSelectMutileClothesIds(item.clothId, item)
// updateSelectedCloth(item)
}
const mutipleRoomSelectClothing = (item: any) => {
// console.log('AT-[ item &&&&&********** ]', item)
emit('addOrRmRoomCloth', item)
}
const paddingRight = `${props.paddingRight === undefined ? 10 : props.paddingRight}px`
</script>
<style scoped lang="scss">
$border: 0.5px solid rgba(0, 0, 0, 1);
.clothes-box {
padding-right: v-bind(paddingRight);
.item {
border: $border;
border-left: none;
border-top: none;
display: block;
position: relative;
padding-top: 150%;
overflow: hidden;
.cover-img {
position: absolute;
width: 100%;
left: 0;
top: 50%;
transform: translateY(-50%);
display: block;
> img {
display: block;
font-size: 0;
}
}
.suit-bottomCloth {
position: absolute;
left: 50%;
right: 0;
width: 50%;
transform: translateY(-100%);
height: 50%;
}
&:hover {
.tool-mask {
display: flex;
}
}
.tool-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: none;
// display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
color: #fff;
> div {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
> span {
cursor: pointer;
position: relative;
}
}
.tool-header {
height: 65%;
flex-direction: column;
display: inline-flex;
align-items: center;
justify-content: space-evenly;
}
.delete {
flex: 1;
}
.preview {
width: 100%;
flex: 1;
position: relative;
.previewSit {
opacity: 0;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 2;
background-color: pink;
}
}
.tool-footer {
flex: 1;
background-color: rgba(0, 0, 0, 0.5);
position: relative;
overflow: hidden;
}
}
.mutiple {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
.checkbox {
position: absolute;
right: 5px;
top: 10px;
z-index: 5;
}
}
.disable-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
}
}
.history {
// border: $border;
box-sizing: border-box;
border: $border;
border-left: none;
}
.noTop {
border-top: none;
}
.haveLeft {
border-left: $border;
}
.havetop {
border-top: $border;
}
}
</style>
+73
View File
@@ -0,0 +1,73 @@
<template>
<div class="clothes-pool">
<div class="tip">
{{ $t('tips.select') }} <span>{{ selectedRoomInfo?.roomName }}</span>
</div>
<div class="pool-box">
<ClothesBox
@addOrRmRoomCloth="props.addOrRmRoomCloth"
:roomClothes="props.roomClothes"
:allMyClothes="props.allMyClothes"
mutipleMode
/>
</div>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import type { IClotheList, IClotheInfo } from '@/interface'
// import api from '@/plugins/axios'
import { useGlobalStore } from '@/stores/globalStore'
// const emit = defineEmits<{
// addOrRmRoomCloth: [data: IClotheInfo]
// }>()
const props = defineProps<{
allMyClothes: IClotheList
roomClothes: IClotheList
addOrRmRoomCloth: (item: IClotheInfo) => Promise<void>
}>()
const globalStore = useGlobalStore()
const { selectedRoomInfo } = storeToRefs(globalStore)
</script>
<style scoped lang="scss">
.clothes-pool {
padding-left: 10px;
// padding-right: 20px;
border-right: 0.5px solid #000;
height: 100%;
display: block;
overflow: hidden;
.tip {
font-size: 12px;
color: #000;
position: relative;
display: inline-block;
margin-bottom: 20px;
&::after {
content: '';
position: absolute;
left: 50%;
bottom: -5px;
width: 9px;
height: 1px;
background-color: #000;
transform: translateX(-50%);
}
}
.pool-box {
height: calc(100% - 38px);
overflow: hidden;
border-top: 0.5px solid #000;
border-left: 0.5px solid #000;
}
}
</style>
+259
View File
@@ -0,0 +1,259 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import type { IRoomInfo } from '@/interface'
import IconAdd from '@/components/icons/IconAdd.vue'
import IconDelete from '@/components/icons/IconDelete.vue'
import IconEdit from '@/components/icons/IconEdit.vue'
import IconLink from '@/components/icons/IconLink.vue'
import api from '@/plugins/axios'
import { useGlobalStore } from '@/stores/globalStore'
const globalStore = useGlobalStore()
const { updateRoomInfo } = globalStore
const { selectedRoomInfo, gender } = storeToRefs(globalStore)
const props = defineProps<{
roomList: IRoomInfo[]
freshRoomList: () => Promise<void>
getRoomListLoading: boolean
}>()
const input = ref('')
const inputRoomRef = ref()
const tempRoomName = ref('')
const status = ref(false)
// const activeRoomItem = ref<IRoomItem>()
type IRoomItem = Omit<IRoomInfo, 'createTime'> & { selected: boolean; edit?: boolean }
type IState = IRoomItem[]
const state = ref<IState>([])
watchEffect(() => {
state.value = props.roomList.map((i) => Object.assign({ ...i, selected: false }))
})
const handleEdit = (item: IRoomItem) => {
state.value.map((val) => {
val.edit = false
if (val.roomId === item.roomId) {
val.edit = true
}
return val
})
}
const editInput = (value: string, index: number) => {
state.value[index].roomName = value
}
const handleSelect = (item: IRoomItem) => {
// activeRoomItem.value = item
updateRoomInfo(item)
}
const updateRoom = async (item: IRoomItem) => {
if (status.value) {
return
}
status.value = true
if (item.roomName) {
await api.updateRoom({ roomId: item.roomId, roomName: item.roomName })
}
await props.freshRoomList()
status.value = false
}
const handleDelete = async (item: IRoomItem) => {
await api.deleteRoom({ roomId: item.roomId })
props.freshRoomList()
}
const add = async () => {
if (!input.value) {
return
}
await api.addRoom({ roomName: input.value, sex: gender.value })
inputRoomRef.value.clear()
props.freshRoomList()
}
</script>
<template>
<section>
<div class="dropdown-menu" v-loading="props.getRoomListLoading">
<div class="dropdown-item" v-for="(item, i) in state" :key="i">
<div class="dropdown-item-name">
<div class="botBox">
<span
:class="{
bot: item.roomId != selectedRoomInfo?.roomId,
activeBot: item.roomId === selectedRoomInfo?.roomId
}"
></span>
</div>
<span
:class="{ text: true, activeText: item.roomId === selectedRoomInfo?.roomId }"
@click="handleSelect(item)"
>
<template v-if="!item.edit">{{ item.roomName }}</template>
<template v-else>
<el-input
:model-value="(tempRoomName = item.roomName)"
@input="(val: string) => editInput(val, i)"
autofocus
style="width: 140px"
size="small"
class="edit-input"
maxlength="9"
@blur="() => updateRoom(item)"
@keyup.enter="() => updateRoom(item)"
/>
</template>
</span>
</div>
<div class="dropdown-item-operating">
<el-icon class="dropdown-item-icon" size="16">
<IconLink />
</el-icon>
<el-icon @click="handleEdit(item)" class="dropdown-item-icon" size="13">
<IconEdit />
</el-icon>
<el-icon @click="handleDelete(item)" class="dropdown-item-icon" size="14">
<IconDelete />
</el-icon>
</div>
</div>
</div>
<div class="add_fittingRoom">
<el-input
v-model="input"
ref="inputRoomRef"
style="width: 240px; border: 1px solid #000000"
placeholder="添加专题试衣间"
@blur="add"
@keyup.enter="add"
maxlength="9"
>
<template #suffix>
<div class="add_icon" @click="add"><IconAdd /></div>
</template>
</el-input>
</div>
</section>
</template>
<style lang="scss" scoped>
.edit-input {
& :deep(.el-input__wrapper) {
// --el-button-active-color: 'none' !important;
// border-radius: 0 !important;
// box-shadow: none !important;
box-shadow: 0 0 0 1px #000 inset !important;
box-shadow: #000 !important;
border-radius: 0 !important;
border: none !important;
outline: none !important;
}
}
.dropdown-menu {
display: flex;
flex-direction: column;
}
.add_fittingRoom {
& :deep(.el-input__wrapper) {
border-radius: 0 !important;
box-shadow: none !important;
border-radius: 0 !important;
border: none !important;
outline: none !important;
}
// margin-top: 120px;
}
.dropdown-item {
display: flex;
align-items: center;
font-size: 14px;
justify-content: space-between;
padding: 8px 0;
}
.dropdown-item-name {
flex: 1;
display: flex;
align-items: center;
.botBox {
width: 10px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 8px;
}
.bot {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 50%;
background: #000;
}
.text {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.activeText {
color: #776ce6;
}
.activeBot {
display: inline-block;
width: 9px;
height: 9px;
border-radius: 50%;
background: #776ce6;
}
}
.add_icon {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dropdown-item-operating {
flex: 0 0 60px;
display: flex;
justify-content: space-between;
align-items: center;
.dropdown-item-icon {
cursor: pointer;
}
}
</style>
+167
View File
@@ -0,0 +1,167 @@
<template>
<div class="baby-select">
<div class="select-box">
<div class="baby-select-header" @click.stop="handleClickHeader">
{{ items.selected }}
<img class="arrow" :class="{ rotate: isShowSelect }" :src="arrowDownIcon" alt="" />
</div>
<div class="hide-select" v-if="isShowSelect" ref="target" @click.stop="handelSelect">
{{ items.option }} <img class="arrow" :src="arrowDownIcon" alt="" />
</div>
</div>
<div class="select-main" type="input" @click="() => open()">
<MyIcons type="preson" :size="50" class="person" />
</div>
</div>
</template>
<script setup lang="ts">
import { useFileDialog, onClickOutside } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import arrowDownIcon from '@/assets/imgs/arrow_down.png'
import MyIcons from '@/components/MyIcons.vue'
import { useGlobalStore } from '@/stores/globalStore'
const globalStore = useGlobalStore()
const { updateGender } = globalStore
const { gender } = storeToRefs(globalStore)
enum IEnumMaps {
BOY = 'male',
GIRL = 'female'
}
type IEmitTypes = {
onChange: [gender: 'male' | 'female']
onFileChange: [formData: FormData]
}
const emit = defineEmits<IEmitTypes>()
const target = ref(null)
const isShowSelect = ref(false)
type IItems = { selected: 'GIRL'; option: 'BOY' } | { selected: 'BOY'; option: 'GIRL' }
const items = ref<IItems>(
gender.value === 'female'
? { selected: 'GIRL', option: 'BOY' }
: { selected: 'BOY', option: 'GIRL' }
)
const {
files,
open,
onChange: onFileChange
} = useFileDialog({ accept: 'image/*', multiple: false })
onFileChange(() => {
console.log('AT-[ files &&&&&********** ]', files.value)
if (files?.value) {
const file = files?.value[0]
const formData = new FormData()
formData.append('file', file)
emit('onFileChange', formData)
}
})
onClickOutside(target, () => {
isShowSelect.value = false
})
const handelSelect = () => {
isShowSelect.value = false
items.value =
items.value.selected === 'BOY'
? { selected: 'GIRL', option: 'BOY' }
: { selected: 'BOY', option: 'GIRL' }
updateGender(IEnumMaps[items.value.selected])
emit('onChange', IEnumMaps[items.value.selected])
}
const handleClickHeader = () => {
isShowSelect.value = !isShowSelect.value
}
emit('onChange', IEnumMaps[items.value.selected])
</script>
<style lang="scss" scoped>
$border1: 0.5px solid #000;
.bordeer1 {
border: $border1;
}
.baby-select {
width: 78px;
// height: 102px;
// width: 100%;
height: calc(100% - 1px);
background-color: #fff;
position: relative;
border: $border1;
cursor: pointer;
user-select: none;
margin: 0 auto;
.select-box {
.baby-select-header {
height: 27px;
width: 100%;
background: #000;
display: flex;
align-items: center;
justify-content: space-around;
font-size: 16px;
color: #fff;
}
.arrow {
width: 15px;
height: 15px;
transform: rotate(-180deg);
transition: all 0.3s;
}
.rotate {
transform: rotate(0deg);
}
.hide-select {
@extend .baby-select-header;
transition: all 0.3s;
background-color: #7b68ee;
border: $border1;
border-left: none;
border-right: none;
display: flex;
& .arrow {
opacity: 0;
}
}
}
.select-main {
height: calc(100% - 27px);
}
.person {
width: 50px;
position: absolute;
right: 5px;
bottom: 10px;
}
}
</style>
+244
View File
@@ -0,0 +1,244 @@
<template>
<div class="clothes-box" v-if="isShow">
<el-row>
<el-col :xs="4" :sm="4" :md="4" :lg="4" :xl="4" v-for="(item, index) in urlsRaw" :key="index">
<div
class="item history"
:class="{
noTop: index > 5,
haveLeft: (index + 1) % 6 == 1
}"
style="overflow: auto"
>
<el-image class="cover-img" v-lazy="item.url" :src="item.url" :lazy="true" />
<div class="tool-mask" v-if="false">
<div class="delete">
<span @click="onDeleteHistory(item)">{{ $t('tips.delete') }}</span>
</div>
<div
class="preview"
v-if="!creatingAndFailMarkList.some((mark) => item.url.includes(mark))"
>
<div>{{ $t('tips.preview') }}</div>
<el-image
class="previewSit"
v-lazy="item.url"
:lazy="true"
:src="item.url"
:preview-src-list="
(props.urls || [])
.map((i) => i.url)
.filter((i) => !creatingAndFailMarkList.some((mark) => i.includes(mark)))
"
preview-teleported
:initial-index="index"
:infinite="false"
/>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
// import { emit } from 'vite'
import { type IClotheList } from '@/interface'
import { useGlobalStore } from '@/stores/globalStore'
const isShow = ref(false)
onMounted(() => {
console.log('AT-[ onMounted &&&&&********** ]')
setTimeout(() => {
isShow.value = true
}, 500)
})
// const { t } = useI18n()
const globalStore = useGlobalStore()
type IProps = {
clothes?: IClotheList
allMyClothes?: IClotheList
roomClothes?: IClotheList
urls?: { historyId: string; url: string }[]
paddingRight?: number
mine?: boolean
mutipleMode?: boolean
}
const props = defineProps<IProps>()
const urlsRaw = toRaw(props.urls)
const emit = defineEmits<{
onDeleteHistory: [data: { historyId: string; url: string }]
}>()
const onDeleteHistory = (item: { historyId: string; url: string }) => emit('onDeleteHistory', item)
const { creatingAndFailMarkList, selectMutipleClothesIds } = storeToRefs(globalStore)
const { updateSelectMutileClothesIds } = globalStore
</script>
<style scoped lang="scss">
$border: 0.5px solid rgba(0, 0, 0, 1);
.clothes-box {
padding-right: v-bind(paddingRight);
.item {
border: $border;
border-left: none;
border-top: none;
display: block;
position: relative;
padding-top: 150%;
overflow: hidden;
.cover-img {
position: absolute;
width: 100%;
left: 0;
top: 50%;
transform: translateY(-50%);
display: block;
> img {
display: block;
font-size: 0;
}
}
.suit-bottomCloth {
position: absolute;
left: 50%;
right: 0;
width: 50%;
transform: translateY(-100%);
height: 50%;
}
&:hover {
.tool-mask {
display: flex;
}
}
.tool-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: none;
// display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
color: #fff;
> div {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
> span {
cursor: pointer;
position: relative;
}
}
.tool-header {
height: 65%;
flex-direction: column;
display: inline-flex;
align-items: center;
justify-content: space-evenly;
}
.delete {
flex: 1;
}
.preview {
width: 100%;
flex: 1;
position: relative;
.previewSit {
opacity: 0;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 2;
background-color: pink;
}
}
.tool-footer {
flex: 1;
background-color: rgba(0, 0, 0, 0.5);
position: relative;
overflow: hidden;
}
}
.mutiple {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
.checkbox {
position: absolute;
right: 5px;
top: 10px;
z-index: 5;
}
}
.disable-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
}
}
.history {
// border: $border;
box-sizing: border-box;
border: $border;
border-left: none;
}
.noTop {
border-top: none;
}
.haveLeft {
border-left: $border;
}
}
</style>
+323
View File
@@ -0,0 +1,323 @@
<template>
<div class="history-scroll" v-infinite-scroll="load" :infinite-scroll-distance="50">
<template v-if="data?.length">
<el-checkbox-group @change="checkboxChange" :model-value="props.checked">
<div v-for="(it, idx) in data" :key="idx" class="block-date">
<div class="history-tip">
<div class="createTime">{{ it.createTime }}</div>
<div class="checkbox-btn">
<el-checkbox :value="idx" :label="$t('tips.selectAll')" size="small" />
</div>
</div>
<el-row>
<el-col
:xs="4"
:sm="4"
:md="4"
:lg="4"
:xl="4"
v-for="(item, index) in it.clothList"
:key="item.historyId"
>
<div
class="item history"
:class="{
noTop: index > 5,
haveLeft: (index + 1) % 6 == 1,
havetop: index + 1 <= 6
}"
>
<el-image class="cover-img" :src="item.url" fit="cover" :lazy="true" />
<div class="tool-mask">
<div class="delete">
<span @click="onDeleteHistory(item)">{{ $t('tips.delete') }}</span>
</div>
<div
class="preview"
v-if="!creatingAndFailMarkList.some((mark) => item.url.includes(mark))"
>
<div>{{ $t('tips.preview') }}</div>
<el-image
class="previewSit"
:src="item.url"
:preview-src-list="
it.clothList
.map((i) => i.url)
.filter((i) => !creatingAndFailMarkList.some((mark) => i.includes(mark)))
"
preview-teleported
:initial-index="
it.clothList
.map((i) => i.url)
.filter((i) => !creatingAndFailMarkList.some((mark) => i.includes(mark)))
.findIndex((i) => i == item.url)
"
:infinite="false"
/>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</el-checkbox-group>
</template>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '@/stores/globalStore'
const globalStore = useGlobalStore()
type IProps = {
historyList?: { createTime: string; clothList: { historyId: string; url: string }[] }[]
checked: number[]
paddingRight?: number
}
const props = defineProps<IProps>()
const emit = defineEmits<{
onDeleteHistory: [data: any]
onHistoryCheckListCHange: [value: number[]]
}>()
const historyCheckListModel = defineModel()
const checkboxChange = (value: any) => {
emit('onHistoryCheckListCHange', value)
}
watchEffect(() => {
console.log(
'AT-[ historyCheckListModel xxxxxxxxxxxxxxxx ------------------------------ ]',
historyCheckListModel.value
)
})
const data = ref<IProps['historyList']>([])
const count = ref(0)
const { creatingAndFailMarkList } = storeToRefs(globalStore)
watchEffect(() => {
data.value = props.historyList?.slice(0, count.value)
})
const onDeleteHistory = (item: { historyId: string; url: string }) => emit('onDeleteHistory', item)
const load = () => {
console.log('AT-[ props.historyList?.length &&&&&********** ]', props.historyList?.length)
console.log('AT-[ count.value &&&&&********** ]', count.value)
if (props.historyList) {
if (count.value >= props.historyList.length) {
return
}
count.value = count.value + 5
data.value = props.historyList?.slice(0, count.value)
}
}
</script>
<style lang="scss" scoped>
$border: 0.5px solid rgba(0, 0, 0, 1);
.history-scroll {
// background-color: red;
overflow-y: scroll;
// height: 60%;
height: 100%;
scrollbar-width: 0;
.block-date {
margin: 0 10px;
.history-tip {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 10px;
color: #7a7a7a;
& :deep(.el-checkbox) {
color: #87909e;
}
& :deep(.is-checked) {
.el-checkbox__inner {
background-color: #87909e;
}
}
& :deep(.el-checkbox__label) {
color: #87909e;
font-size: 12px;
font-weight: normal;
padding-left: 4px;
}
& :deep(.el-checkbox__inner) {
border-color: #87909e;
background-color: #87909e;
border-radius: 0;
}
}
}
.item {
border: $border;
border-left: none;
border-top: none;
display: block;
position: relative;
padding-top: 150%;
overflow: hidden;
.cover-img {
position: absolute;
width: 100%;
left: 0;
top: 50%;
transform: translateY(-50%);
display: block;
> img {
display: block;
font-size: 0;
}
}
.suit-bottomCloth {
position: absolute;
left: 50%;
right: 0;
width: 50%;
transform: translateY(-100%);
height: 50%;
}
&:hover {
.tool-mask {
display: flex;
}
}
.tool-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: none;
// display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
color: #fff;
> div {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
> span {
cursor: pointer;
position: relative;
}
}
.tool-header {
height: 65%;
flex-direction: column;
display: inline-flex;
align-items: center;
justify-content: space-evenly;
}
.delete {
flex: 1;
}
.preview {
width: 100%;
flex: 1;
position: relative;
.previewSit {
opacity: 0;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 2;
background-color: pink;
}
}
.tool-footer {
flex: 1;
background-color: rgba(0, 0, 0, 0.5);
position: relative;
overflow: hidden;
}
}
.mutiple {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
.checkbox {
position: absolute;
right: 5px;
top: 10px;
z-index: 5;
}
}
.disable-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
}
}
.history {
// border: $border;
box-sizing: border-box;
border: $border;
border-left: none;
}
.noTop {
border-top: none;
}
.haveLeft {
border-left: $border;
}
}
</style>
+175
View File
@@ -0,0 +1,175 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import type { INavStatus, IMainNav, ISubNav, IRoomInfo } from '@/interface'
import { MENU_CONFIG } from '@/constants'
import { MenuEnum } from '@/enum/MenuEnum'
import { useGlobalStore } from '@/stores/globalStore'
const globalStore = useGlobalStore()
const { menu } = storeToRefs(globalStore)
const visible = ref(false)
const props = defineProps<{
alertHandle: () => void
roomList: IRoomInfo[]
freshRoomList: () => void
getRoomListLoading: boolean
roomMode: boolean
}>()
type IEmitTypes = {
onMenuChange: [navStatus: INavStatus]
}
// const navStatus = reactive<INavStatus>({
// mainNav: MenuEnum.CLOTHES_LIBRARY,
// subNav: MenuEnum.ALL
// })
const navStatus = reactive<INavStatus>({ ...menu.value })
watchEffect(() => {
navStatus.mainNav = menu.value.mainNav
navStatus.subNav = menu.value.subNav
})
const emit = defineEmits<IEmitTypes>()
const selectMainNav = (mainNav: IMainNav) => {
if (navStatus.mainNav != mainNav) {
navStatus.mainNav = mainNav
emit('onMenuChange', navStatus)
}
}
const selectSubNav = (value: any) => {
// console.log('AT-[ selectSubNav &&&&&********** ]', value)
// const allSubNavVals: ISubNav[] = [
// MenuEnum.ALL,
// MenuEnum.UPPER_OUTER_GARMENT,
// MenuEnum.BOTTOMS,
// MenuEnum.DRESS,
// MenuEnum.SUIT
// ]
const allSubNavVals = MENU_CONFIG.value.filter((i) => i.status).map((i) => i.value)
if (allSubNavVals.includes(value) && navStatus.subNav != value) {
navStatus.subNav = value
emit('onMenuChange', navStatus)
}
}
const visibleChange = (val: any) => {
visible.value = val
}
</script>
<template>
<div class="menu">
<div
:class="{
item: true,
selected: props.roomMode ? false : navStatus.mainNav === MenuEnum.CLOTHES_LIBRARY
}"
@click="selectMainNav(MenuEnum.CLOTHES_LIBRARY)"
>
<!-- 衣服库 -->
{{ $t('nav.clothLib') }}
</div>
<div class="item" @click="selectMainNav(MenuEnum.MY_WARDROBE)">
<el-dropdown placement="bottom-start" trigger="hover" @visible-change="visibleChange">
<span
:class="{ selected: props.roomMode ? false : navStatus.mainNav === MenuEnum.MY_WARDROBE }"
>
<!-- 我的衣橱 -->
{{ $t('nav.myCloset') }}
</span>
<template v-slot:dropdown>
<div v-if="visible" class="dropdownContainer">
<DropdownMenu
:roomList="props.roomList"
:freshRoomList="props.freshRoomList"
:getRoomListLoading="props.getRoomListLoading"
/>
</div>
</template>
</el-dropdown>
/
</div>
<div
v-for="item in MENU_CONFIG"
:key="item.name"
:class="{
item: true,
disabledContent: !item.status,
selected: navStatus.subNav === item.value
}"
@click="item.status ? selectSubNav(item.value) : props.alertHandle()"
>
{{ item.name }}
</div>
</div>
</template>
<style lang="scss" scoped>
.menu {
display: flex;
font-size: 13px;
padding-bottom: 10px;
margin-left: 16px;
flex-wrap: wrap;
position: relative;
.item {
padding: 10px 4px 0;
cursor: pointer;
line-height: 24px;
height: 24px;
user-select: none;
span {
line-height: 24px;
height: 24px;
display: inline-block;
color: #000000;
&:focus-visible {
outline: none;
}
}
}
.selected {
//background: red;
position: relative;
&::after {
content: ' ';
position: absolute;
height: 1.5px;
background: #000;
width: 10px;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
}
}
.disabledContent {
color: #88909d;
}
}
</style>
<style lang="scss">
.dropdownContainer {
width: 240px;
border: 1px solid #000000;
padding: 20px 25px 20px 30px;
}
.el-popper__arrow {
display: none;
}
</style>
+183
View File
@@ -0,0 +1,183 @@
<template>
<div class="model-item-card" :class="{ active: props.active }" ref="containerRef">
<el-image :src="thumbnailFileUrl" fit="cover" style="width: 78px; height: 117px" />
<div class="tool-bar" v-show="!props.mutipleMode">
<div class="header">
<span @click="onDelete">{{ $t('tips.delete') }}</span>
<span @click="onModify">{{ $t('tips.modifyFace') }}</span>
</div>
<div class="footer">
<span @click="selectModel">{{ $t('tips.select') }}</span>
</div>
</div>
<div class="mutiple" v-if="props.mutipleMode">
<MyIcons
:type="checkboxIconName"
cursor="pointer"
:size="16"
class="checkbox"
@click="mutipleSelectModel"
/>
</div>
</div>
</template>
<script setup lang="ts">
type IProps = {
size?: number
mutipleMode?: boolean
checkboxStatus?: boolean
active: boolean
modelInfo: IModelInfo
}
import { ref } from 'vue'
import MyIcons from '@/components/MyIcons.vue'
import { type IModelInfo } from '@/interface'
const containerRef = ref(null)
const props = defineProps<IProps>()
// const { size = 15, active = false, modelInfo } = props
const checkboxIconName = computed(() =>
props.mutipleMode && props.checkboxStatus ? 'selected' : 'noSelected'
)
const { thumbnailFileUrl } = props.modelInfo
const width = `${props.size}px`
const emit = defineEmits<{
selectModel: [modelInfo: IModelInfo]
deletetModel: [modelInfo: IModelInfo]
modifyModel: [modelInfo: IModelInfo]
onMutipleSelectModel: [modelInfo: IModelInfo, checkboxStatus: boolean]
}>()
const selectModel = () => emit('selectModel', props.modelInfo)
const mutipleSelectModel = () => emit('onMutipleSelectModel', props.modelInfo, props.checkboxStatus)
const onDelete = () => {
emit('deletetModel', props.modelInfo)
}
const onModify = () => {
emit('modifyModel', props.modelInfo)
}
</script>
<style scoped lang="scss">
$border1px: 0.5px solid #000;
* {
box-sizing: border-box;
}
.model-item-card {
width: v-bind(width);
position: relative;
overflow: hidden;
box-sizing: border-box;
&.active {
border: $border1px;
filter: grayscale(0) !important;
& :deep(.el-image) {
display: block;
font-size: 0;
filter: grayscale(0%);
}
}
&:hover {
filter: grayscale(0);
border: $border1px;
.tool-bar {
display: flex;
}
& :deep(.el-image) {
filter: grayscale(0);
opacity: 1;
}
}
& :deep(.el-image) {
display: block;
font-size: 0;
filter: grayscale(100%);
opacity: 0.5;
}
.tool-bar {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
height: 100%;
flex-direction: column;
box-sizing: border-box;
display: none;
background-color: rgba(0, 0, 0, 0.5);
> .header,
.footer {
flex: 1;
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
> span {
cursor: pointer;
}
}
.header {
// background-color: rgba(0, 0, 0, 0.5);
font-size: 10px;
display: inline-flex;
flex-direction: column;
justify-content: space-evenly;
}
.footer {
background-color: rgba(0, 0, 0, 0.5);
font-size: 12px;
// background-color: red;
}
}
.mutiple {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 8;
.checkbox {
position: absolute;
right: 5px;
top: 10px;
z-index: 5;
}
}
}
</style>
+44
View File
@@ -0,0 +1,44 @@
<template>
<div class="my-button">{{ text }}</div>
</template>
<script setup lang="ts">
import { computed, reactive } from 'vue'
type IProps = {
text: string
type?: 'light' | 'dark'
}
const { text, type = 'light' } = defineProps<IProps>()
const lightStyle = reactive({ bgColor: 'rgba(255, 255, 255, 0.8)', fontColor: '#000' })
const darkStyle = reactive({ bgColor: 'rgba(0, 0, 0, 0.8)', fontColor: '#fff' })
const style = computed(() => (type === 'light' ? lightStyle : darkStyle))
const { fontColor, bgColor } = style.value
</script>
<style scoped lang="scss">
.my-button {
width: 90px;
height: 36px;
border: 0.5px solid #000;
box-sizing: border-box;
font-size: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: v-bind(fontColor);
background-color: v-bind(bgColor);
user-select: none;
&:hover {
opacity: 0.9;
font-weight: bolder;
}
}
</style>
+9
View File
@@ -0,0 +1,9 @@
<template>
<div class="my-card">my-card</div>
</template>
<style scoped lang="scss">
.my-card {
border: 1px solid #000;
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div class="my-icons">
<img :src="icon" alt="" class="icon" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import arrowDownIcon from '@/assets/imgs/arrow_down.png'
import closeIcon from '@/assets/imgs/close.png'
import hairIcon from '@/assets/imgs/hair_upload.png'
import multipleBarIcon from '@/assets/imgs/multiple.png'
import multipleActivatedBarIcon from '@/assets/imgs/multipleActivated.png'
import noSelectedIcon from '@/assets/imgs/noSelected.png'
import personIcon from '@/assets/imgs/person.png'
import searchIcon from '@/assets/imgs/search.png'
import selectedIcon from '@/assets/imgs/selected.png'
import singleBarIcon from '@/assets/imgs/single.png'
import singleActivatedBarIcon from '@/assets/imgs/singleActivated.png'
import uploadIcon from '@/assets/imgs/upload.png'
import uploadEnIcon from '@/assets/imgs/uploadEn.png'
import videoPlayIcon from '@/assets/imgs/video_play.png'
import i18n from '@/i18n'
const { locale } = i18n.global
type IProps = {
type:
| 'single'
| 'singleActivated'
| 'multiple'
| 'multipleActivated'
| 'arrow'
| 'preson'
| 'selected'
| 'noSelected'
| 'close'
| 'video'
| 'upload'
| 'search'
size?: number
cursor?: string
}
const props = defineProps<IProps>()
const { type, size, cursor = 'default' } = toRefs(props)
const mapsRef = ref(
new Map([
['single', singleBarIcon],
['singleActivated', singleActivatedBarIcon],
['multiple', multipleBarIcon],
['multipleActivated', multipleActivatedBarIcon],
['arrow', arrowDownIcon],
['preson', personIcon],
['selected', selectedIcon],
['noSelected', noSelectedIcon],
['close', closeIcon],
['video', videoPlayIcon],
['upload', uploadIcon],
['search', searchIcon],
['hair', hairIcon]
])
)
watchEffect(() => {
if (props.type === 'upload') {
mapsRef.value.set('upload', locale.value == 'zh' ? uploadIcon : uploadEnIcon)
}
})
const icon = computed(() => mapsRef.value.get(type.value))
const height = `${size.value || 15}px`
</script>
<style scoped lang="scss">
.my-icons {
display: inline;
.icon {
height: v-bind(height);
cursor: v-bind(cursor);
}
}
</style>
+428
View File
@@ -0,0 +1,428 @@
<template>
<ScrollWithBar>
<div class="pick-And-upload-image">
<div class="tip">
{{ $t('tips.upload') }} <span>{{ subNavText }}</span>
</div>
<el-upload
:fileList="fileList"
:auto-upload="true"
:headers="{ 'X-Access-Token': cookies.get('token') }"
action="/api/cloth/uploadClothFile"
multiple
accept="image/*"
class="custom-uploader"
list-type="picture-card"
:on-success="uploadSuccess"
:before-upload="beforeUpload"
ref="uploadRef"
:limit="20"
>
<template #trigger>
<div class="trigger-icon">
<MyIcons :size="89" :type="nav.clothType == 'hair' ? 'hair' : 'upload'" />
</div>
</template>
<template #file="{ file }">
<img class="thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
<el-icon><CircleClose /></el-icon>
</span>
</span>
</template>
</el-upload>
<!-- <p class="prompt">(1-8效果越多越好)</p> -->
<p class="prompt" v-if="nav.clothType == 'hair'">{{ $t('tips.uploadTip') }}</p>
<el-input
:placeholder="$t('tips.placeholder')"
size="large"
class="link-input"
v-model="linkText"
>
<template #prefix>
<IconLink />
</template>
<template #suffix>
<div class="grabButton" @click="grabImage">{{ $t('tips.pickImg') }}</div>
</template>
</el-input>
<div class="grab-img-list">
<el-row>
<el-col
v-for="(item, index) in grapImgList"
:key="index"
:lg="2"
:md="4"
:sm="6"
:xl="2"
:xs="12"
>
<div class="grap-image" @click="handleSelect(item)">
<el-image :src="item.fileUrl" class="cover-img" fit="cover" lazy />
<span
class="cover-icon"
:class="{ active: fileList.some((i) => i.name === item.id) }"
>
<el-icon
class="icon-item"
v-if="fileList.some((i) => i.name === item.id)"
color="#fff"
>
<SuccessFilled />
</el-icon>
</span>
</div>
</el-col>
</el-row>
</div>
<div class="upload-btn">
<el-checkbox
size="small"
v-model="isSyncClothesLibrary"
@change="handleSyncToClothingLibrary"
>{{ $t('tips.sync') }}</el-checkbox
>
<MyButton
style="width: 151px; height: 36px; margin-left: 5px"
:text="$t('tips.upload')"
@click="handleUpload"
></MyButton>
</div>
</div>
</ScrollWithBar>
</template>
<script setup lang="ts">
import { CircleClose, SuccessFilled } from '@element-plus/icons-vue'
import { useCookies } from '@vueuse/integrations/useCookies'
import { ElMessageBox, ElLoading, type UploadUserFile, type UploadProps } from 'element-plus'
import { storeToRefs } from 'pinia'
import IconLink from '@/components/icons/IconLink.vue'
import MyButton from '@/components/MyButton.vue'
import ScrollWithBar from '@/components/ScrollWithBar.vue'
import { MENU_CONFIG } from '@/constants'
import api from '@/plugins/axios'
import { useGlobalStore } from '@/stores/globalStore'
import { getLocalImageSize } from '@/utils'
const cookies = useCookies(['locale'])
const fileList = ref<UploadUserFile[]>([])
const grapImgList = ref<Record<'fileUrl' | 'id' | 'thumbnailFileUrl', string>[]>([])
const linkText = ref<string>()
const isSyncClothesLibrary = ref<boolean>(true)
const globalStore = useGlobalStore()
const { menu, nav, gender } = storeToRefs(globalStore)
const emit = defineEmits<{
onUploadSuccess: []
}>()
type IStatus = {
imgUploading: boolean
}
const status = reactive<IStatus>({
imgUploading: false
})
const uploadRef = ref()
const handleRemove = (file: any) => {
const name = file?.response?.result?.id || file?.name
const newFileList = fileList.value.filter((i) => i.name != name)
fileList.value = newFileList
}
const subNavText = computed(
() => MENU_CONFIG.value.find((item) => item.value === menu.value.subNav)?.name
)
const reset = () => {
// isSyncClothesLibrary.value = false
fileList.value = []
linkText.value = ''
grapImgList.value = []
}
// 切换tab 清空数据
watch(subNavText, () => reset())
const grabImage = async () => {
linkText.value && (await isValidURL(linkText.value))
const loading = ElLoading.service({
lock: true,
text: '抓取中',
background: 'rgba(0, 0, 0, 0.7)'
})
const { result } = await api.grabPicture({ grabUrl: linkText.value })
grapImgList.value = result
loading.close()
}
const successListMarkRef = ref<any[]>([])
const uploadSuccess = (response: any, uploadFile: any, uploadFiles: any) => {
successListMarkRef.value.push(uploadFile)
const { id: name, fileUrl: url } = response.result
fileList.value.push({ name, url })
const allUids = uploadFiles.map((i: any) => i.uid)
const successedUids = successListMarkRef.value.map((i) => i.uid)
status.imgUploading = !allUids.every((uid: number) => successedUids.includes(uid))
}
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
if (!rawFile.type.startsWith('image/')) {
ElMessage.error('请选择正确的图片')
return Promise.reject()
}
const size = await getLocalImageSize(rawFile)
// console.log('AT-[ size xxxxxx &&&&&********** ]', size)
if (size.width < 500) {
ElMessage.error('为确保效果,请上传500像素以上的图片')
return Promise.reject()
}
status.imgUploading = true
return Promise.resolve()
}
const handleSelect = async (item: any) => {
console.log('AT-[ handleSelect &&&&&********** ]')
// 判断图片是否已存在
const existingFile = fileList.value.find((file) => item.fileUrl === file.url)
if (!existingFile) {
// 手动上传
const imageUrl = item.fileUrl
const response = await fetch(imageUrl)
const blob = await response.blob()
const file = new File([blob], item.id, { type: blob.type })
uploadRef.value.handleStart(file)
// 添加到列表
fileList.value.push({
name: item.id,
url: item.fileUrl
})
}
}
const handleSyncToClothingLibrary = (val: any) => {
isSyncClothesLibrary.value = val
}
const handleUpload = async () => {
if (!status.imgUploading && !fileList.value.length) {
ElMessage.warning('请先选择图片')
return
}
const loading = ElLoading.service({
lock: true,
text: '上传中',
background: 'rgba(0, 0, 0, 0.7)'
})
if (status.imgUploading) {
while (status.imgUploading) {
await new Promise((r) => setTimeout(r, 2000))
}
}
const clothFileList = fileList.value?.map((file) => file.name)
const clothType = nav.value.clothType as 'topCloth' | 'bottomCloth' | 'dress'
await api.addClothes({
clothFileList,
clothType,
isSync: isSyncClothesLibrary.value ? 'yes' : 'no',
sex: gender.value
})
emit('onUploadSuccess')
loading.close()
}
// expose
/**
* 输入链接校验
*/
const isValidURL = (url: string) => {
try {
const parsedURL = new URL(url)
const hostname = parsedURL.hostname
const domain = hostname.split('.').slice(-2).join('.')
return domain === 'vip.com'
} catch {
ElMessageBox({
type: 'error',
message: '请输入正确的链接'
})
return false
}
}
defineExpose({ reset })
</script>
<style scoped lang="scss">
.pick-And-upload-image {
padding-left: 20px;
padding-right: 20px;
padding-bottom: 20px;
border-right: 0.5px solid #000;
.tip {
font-size: 12px;
color: #000;
position: relative;
display: inline-block;
margin-bottom: 20px;
&::after {
content: '';
position: absolute;
left: 50%;
bottom: -5px;
width: 9px;
height: 1px;
background-color: #000;
transform: translateX(-50%);
}
}
& :deep(.el-upload--picture-card) {
--el-upload-picture-card-size: 89px;
}
& :deep(.el-upload-list) {
--el-upload-list-picture-card-size: 89px;
}
& :deep(.el-upload-list__item) {
border-radius: 0;
border: 0.5px solid #000;
}
.trigger-icon {
width: 89px;
height: 89px;
}
.thumbnail {
width: 89px;
height: 89px;
object-fit: cover;
}
:deep(.el-input__wrapper) {
border-radius: 0 !important;
box-shadow: none !important;
// background-color: red;
// border-width: 0.5px;
border: none;
outline: none;
}
.prompt {
font-size: 12px;
color: #000;
}
.link-input {
max-width: 580px;
width: 100%;
border: 0.5px solid #000000;
margin-top: 10px;
.grabButton {
cursor: pointer;
color: #000000;
font-size: 12px;
}
}
.grab-img-list {
margin-top: 10px;
height: auto;
.grap-image {
position: relative;
border: 0.5px solid #000;
.cover-icon {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: flex-end;
opacity: 1;
transition: opacity 0.3s ease;
display: none;
.icon-item {
margin-bottom: 10px;
}
&.active {
display: flex;
}
}
&:hover .cover-icon {
opacity: 1;
display: flex;
}
}
}
.upload-btn {
text-align: center;
& :deep(.el-checkbox__label) {
color: #87909e;
font-size: 12px;
font-weight: normal;
padding-left: 4px;
}
& :deep(.el-checkbox__inner) {
border-color: #87909e;
background-color: #87909e;
border-radius: 0;
}
}
}
</style>
+57
View File
@@ -0,0 +1,57 @@
<template>
<swiper
class="swiper"
:modules="modules"
direction="vertical"
slides-per-view="auto"
:free-mode="true"
:scrollbar="true"
:mousewheel="true"
:auto-height="true"
>
<swiper-slide class="slide">
<slot></slot>
</swiper-slide>
</swiper>
</template>
<script setup lang="ts">
import { Pagination, FreeMode, Scrollbar, Mousewheel } from 'swiper/modules'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import 'swiper/css/pagination'
import 'swiper/css/free-mode'
import 'swiper/css/scrollbar'
const modules = [Pagination, FreeMode, Scrollbar, Mousewheel]
const height = '10000000000px'
</script>
<style lang="scss" scoped>
:deep(.swiper-scrollbar) {
width: 0.5px;
right: 0;
}
:deep(.swiper-scrollbar-drag) {
width: 0.5px;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: v-bind(height);
background-color: black;
}
}
.swiper {
height: 100%;
.slide {
height: auto;
}
}
</style>
+22
View File
@@ -0,0 +1,22 @@
<template>
<div class="search-input">
<MyIcons type="search" :size="15" style="display: inline-flex" />
<span>{{ $t('nav.search') }}</span>
</div>
</template>
<style scoped lang="scss">
.search-input {
width: 123px;
height: 22px;
border: 1px solid #000;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
font-size: 10px;
box-sizing: border-box;
// background-color: red;
}
</style>
+74
View File
@@ -0,0 +1,74 @@
<script lang="ts" setup>
import { useToggle } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '@/stores/globalStore'
const globalStore = useGlobalStore()
const { menu } = storeToRefs(globalStore)
const emit = defineEmits(['change'])
const props = defineProps<{
checked?: boolean
}>()
console.log(props.checked, '饿', menu.value)
const [value, toggle] = useToggle(props.checked)
const handleToggle = () => {
toggle()
emit('change', value.value)
}
</script>
<template>
<div class="singleCheckbox" @click="handleToggle">
<div v-if="!value">
<svg
fill="none"
height="15"
viewBox="0 0 15 15"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<rect
fill="#D9D9D9"
height="14.5"
stroke="black"
stroke-width="0.5"
width="14.5"
x="0.25"
y="0.25"
/>
</svg>
</div>
<div v-else>
<svg
fill="none"
height="15"
viewBox="0 0 18 15"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<rect
fill="#D9D9D9"
height="14.5"
stroke="black"
stroke-width="0.5"
width="14.5"
x="0.25"
y="0.25"
/>
<path
d="M2.91675 5.41666L6.66675 10.8333L17.0834 0.416656"
stroke="black"
stroke-width="0.5"
/>
</svg>
</div>
</div>
</template>
<style lang="scss" scoped>
.singleCheckbox {
width: 16px;
position: relative;
top: 2px;
}
</style>
+88
View File
@@ -0,0 +1,88 @@
<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>
Vues
<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" rel="noopener"
>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,0 +1,72 @@
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus'
import { ref } from 'vue'
import IconLink from '@/components/icons/IconLink.vue'
const emit = defineEmits(['grabImage'])
const input = ref('')
const grabImage = () => {
// console.log(input.value)
// input.value && emit('grabImage', input.value)
const isOk = isValidURL(input.value)
if (isOk) {
emit('grabImage', input.value)
} else {
ElMessageBox({
type: 'error',
message: '请输入正确的链接'
})
}
}
const isValidURL = (url: string) => {
try {
const parsedURL = new URL(url)
const hostname = parsedURL.hostname
const domain = hostname.split('.').slice(-2).join('.')
return domain === 'vip.com'
} catch {
return false
}
}
</script>
<template>
<div class="vpShop-link">
<el-input
v-model="input"
placeholder="输入唯品会链接自动抓取图片"
size="large"
style="max-width: 580px; width: 100%; border: 1px solid #000000"
>
<template #prefix>
<IconLink />
</template>
<template #suffix>
<div class="grabButton" @click="grabImage">抓取图片</div>
</template>
</el-input>
</div>
</template>
<style lang="scss" scoped>
.vpShop-link {
:deep(.el-input__wrapper) {
border-radius: 0 !important;
box-shadow: none !important;
}
.grabButton {
cursor: pointer;
color: #000000;
}
}
</style>
<style lang="scss">
.grabButton {
cursor: pointer;
color: #000000;
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<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 lang="scss">
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.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>
+4
View File
@@ -0,0 +1,4 @@
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.25" y="0.25" width="14.5" height="14.5" fill="#D9D9D9" stroke="black" stroke-width="0.5"/>
<path d="M2.91675 5.41666L6.66675 10.8333L17.0834 0.416656" stroke="black" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 301 B

+23
View File
@@ -0,0 +1,23 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1743_871)">
<path
d="M9.625 11H1.375C0.61875 11 0 10.3813 0 9.625V1.375C0 0.61875 0.61875 0 1.375 0H9.625C10.3813 0 11 0.61875 11 1.375V9.625C11 10.3813 10.3813 11 9.625 11ZM1.375 0.6875C0.9625 0.6875 0.6875 0.9625 0.6875 1.375V9.625C0.6875 10.0375 0.9625 10.3125 1.375 10.3125H9.625C10.0375 10.3125 10.3125 10.0375 10.3125 9.625V1.375C10.3125 0.9625 10.0375 0.6875 9.625 0.6875H1.375Z"
fill="#2C2C2C"
/>
<path
d="M8.25 5.84375H2.75C2.54375 5.84375 2.40625 5.70625 2.40625 5.5C2.40625 5.29375 2.54375 5.15625 2.75 5.15625H8.25C8.45625 5.15625 8.59375 5.29375 8.59375 5.5C8.59375 5.70625 8.45625 5.84375 8.25 5.84375Z"
fill="#2C2C2C"
/>
<path
d="M5.5 8.59375C5.29375 8.59375 5.15625 8.45625 5.15625 8.25V2.75C5.15625 2.54375 5.29375 2.40625 5.5 2.40625C5.70625 2.40625 5.84375 2.54375 5.84375 2.75V8.25C5.84375 8.45625 5.70625 8.59375 5.5 8.59375Z"
fill="#2C2C2C"
/>
</g>
<defs>
<clipPath id="clip0_1743_871">
<rect width="11" height="11" fill="white" />
</clipPath>
</defs>
</svg>
</template>
+7
View File
@@ -0,0 +1,7 @@
<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>
+27
View File
@@ -0,0 +1,27 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1991_465)">
<path
d="M6.53009 8.25046C6.33977 8.25046 6.1864 8.09709 6.1864 7.90676V4.1243C6.1864 3.93398 6.33977 3.78061 6.53009 3.78061C6.72042 3.78061 6.87379 3.93398 6.87379 4.1243V7.90492C6.87379 8.09524 6.72042 8.25046 6.53009 8.25046Z"
fill="#2C2C2C"
/>
<path
d="M4.46796 8.25046C4.27764 8.25046 4.12427 8.09709 4.12427 7.90676V4.1243C4.12427 3.93398 4.27764 3.78061 4.46796 3.78061C4.65828 3.78061 4.81165 3.93398 4.81165 4.1243V7.90492C4.81165 8.09524 4.65828 8.25046 4.46796 8.25046Z"
fill="#2C2C2C"
/>
<path
d="M9.96892 2.40584H8.25046V1.71845C8.25046 1.14933 7.7922 0.687378 7.22677 0.687378H3.78061C3.21148 0.687378 2.74953 1.14933 2.74953 1.71845V2.40584H1.03107C0.840746 2.40584 0.687378 2.55921 0.687378 2.74953C0.687378 2.93986 0.840746 3.09322 1.03107 3.09322H9.96892C10.1592 3.09322 10.3126 2.93986 10.3126 2.74953C10.3126 2.55921 10.1592 2.40584 9.96892 2.40584ZM3.43692 1.71845C3.43692 1.52998 3.59213 1.37476 3.78061 1.37476H7.22677C7.41525 1.37476 7.56307 1.52628 7.56307 1.71845V2.40584H3.43692V1.71845Z"
fill="#2C2C2C"
/>
<path
d="M7.90673 10.3126H3.09504C2.52592 10.3126 2.06396 9.85066 2.06396 9.28154V4.12061C2.06396 3.93028 2.21733 3.77692 2.40766 3.77692C2.59798 3.77692 2.75135 3.93028 2.75135 4.12061V9.28154C2.75135 9.47186 2.90657 9.62523 3.09504 9.62523H7.90858C8.09891 9.62523 8.25227 9.47186 8.25227 9.28154V4.13354C8.25227 3.94322 8.40564 3.78985 8.59597 3.78985C8.78629 3.78985 8.93966 3.94322 8.93966 4.13354V9.28154C8.93781 9.84881 8.47586 10.3126 7.90673 10.3126Z"
fill="#2C2C2C"
/>
</g>
<defs>
<clipPath id="clip0_1991_465">
<rect width="11" height="11" fill="white" />
</clipPath>
</defs>
</svg>
</template>
@@ -0,0 +1,7 @@
<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>
+13
View File
@@ -0,0 +1,13 @@
<template>
<svg width="16" height="4" viewBox="0 0 16 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="0.00012207" width="4" height="4" fill="black" />
<rect y="0.00012207" width="4" height="4" fill="black" />
<rect y="0.00012207" width="4" height="4" fill="black" />
<rect x="6" y="0.00012207" width="4" height="4" fill="black" />
<rect x="6" y="0.00012207" width="4" height="4" fill="black" />
<rect x="6" y="0.00012207" width="4" height="4" fill="black" />
<rect x="12" y="0.00012207" width="4" height="4" fill="black" />
<rect x="12" y="0.00012207" width="4" height="4" fill="black" />
<rect x="12" y="0.00012207" width="4" height="4" fill="black" />
</svg>
</template>
+7
View File
@@ -0,0 +1,7 @@
<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>
+8
View File
@@ -0,0 +1,8 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.26743 0.246544C8.29914 0.213713 8.33707 0.187525 8.379 0.16951C8.42094 0.151494 8.46605 0.142011 8.51169 0.141615C8.55734 0.141218 8.6026 0.149915 8.64485 0.167199C8.68709 0.184483 8.72547 0.210008 8.75775 0.242284C8.79002 0.274559 8.81555 0.31294 8.83283 0.355185C8.85012 0.397431 8.85881 0.442696 8.85842 0.488338C8.85802 0.533981 8.84854 0.579088 8.83052 0.621027C8.81251 0.662966 8.78632 0.700897 8.75349 0.732607L3.89252 5.59369C3.82805 5.65816 3.74061 5.69438 3.64943 5.69438C3.55826 5.69438 3.47081 5.65816 3.40634 5.59369C3.34187 5.52922 3.30565 5.44178 3.30565 5.3506C3.30565 5.25943 3.34187 5.17198 3.40634 5.10751L8.26743 0.246659V0.246544ZM8.16671 3.81249C8.16671 3.72132 8.20292 3.63389 8.26739 3.56942C8.33185 3.50496 8.41929 3.46874 8.51046 3.46874C8.60162 3.46874 8.68906 3.50496 8.75352 3.56942C8.81799 3.63389 8.85421 3.72132 8.85421 3.81249V7.59374C8.85421 8.28983 8.28988 8.85416 7.59379 8.85416H1.40629C0.710197 8.85416 0.145874 8.28983 0.145874 7.59374V1.40624C0.145874 0.710148 0.710197 0.145826 1.40629 0.145826H5.07296C5.16412 0.145826 5.25156 0.182042 5.31602 0.246508C5.38049 0.310973 5.41671 0.398408 5.41671 0.489576C5.41671 0.580744 5.38049 0.668178 5.31602 0.732644C5.25156 0.797109 5.16412 0.833326 5.07296 0.833326H1.40629C1.25434 0.833326 1.10862 0.893686 1.00118 1.00113C0.893735 1.10857 0.833374 1.2543 0.833374 1.40624V7.59374C0.833374 7.74569 0.893735 7.89141 1.00118 7.99885C1.10862 8.1063 1.25434 8.16666 1.40629 8.16666H7.59379C7.74574 8.16666 7.89146 8.1063 7.9989 7.99885C8.10635 7.89141 8.16671 7.74569 8.16671 7.59374V3.81249Z"
fill="black"
/>
</svg>
</template>
+12
View File
@@ -0,0 +1,12 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.85732 5.76748V2.16777C1.85732 1.99053 2.00234 1.84658 2.18174 1.84658H7.00283C7.18223 1.84658 7.32725 1.99053 7.32725 2.16777V5.76855C7.32725 5.9458 7.18223 6.08975 7.00283 6.08975H5.35928C5.19814 6.08975 5.06709 6.2208 5.06709 6.38193V6.44102C5.06709 6.60215 5.19814 6.7332 5.35928 6.7332H7.32617C7.68389 6.7332 7.975 6.44531 7.975 6.08975V1.84658C7.975 1.49102 7.68496 1.20312 7.32617 1.20312H1.53291C1.35352 1.20312 1.2085 1.34707 1.2085 1.52432V6.08867C1.2085 6.44424 1.49854 6.73213 1.85732 6.73213H2.18174C2.34287 6.73213 2.47393 6.60107 2.47393 6.43994V6.38086C2.47393 6.21973 2.34287 6.08867 2.18174 6.08867C2.00234 6.08975 1.85732 5.9458 1.85732 5.76748Z"
fill="black"
/>
<path
d="M9.15342 4.26788H8.829C8.68613 4.26788 8.56904 4.3839 8.56904 4.52784V4.65138C8.56904 4.79425 8.68506 4.91134 8.829 4.91134C9.0084 4.91134 9.15342 5.05529 9.15342 5.23253V8.83331C9.15342 9.01056 9.0084 9.1545 8.829 9.1545H3.97568C3.79629 9.1545 3.65127 9.01056 3.65127 8.83331V5.23253C3.65127 5.05529 3.79629 4.91134 3.97568 4.91134H5.68369C5.82656 4.91134 5.94365 4.79532 5.94365 4.65138V4.52784C5.94365 4.38497 5.82764 4.26788 5.68369 4.26788H3.65127C3.29355 4.26788 3.00244 4.55577 3.00244 4.91134V9.1545C3.00244 9.51007 3.29248 9.79796 3.65127 9.79796H9.15342C9.51113 9.79796 9.80225 9.51007 9.80225 9.1545V4.91134C9.80225 4.55577 9.51221 4.26788 9.15342 4.26788Z"
fill="black"
/>
</svg>
</template>
+15
View File
@@ -0,0 +1,15 @@
<template>
<svg width="123" height="21" viewBox="0 0 123 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="122" height="20" fill="white" stroke="black"/>
<g clip-path="url(#clip0_1650_2387)">
<path d="M21.0001 17.39L16.5551 12.94C17.5712 11.7647 18.0917 10.2411 18.0072 8.68974C17.9228 7.13842 17.2398 5.68034 16.1021 4.62232C14.9644 3.5643 13.4607 2.98884 11.9073 3.01701C10.3539 3.04518 8.87205 3.6748 7.77347 4.77338C6.67489 5.87196 6.04528 7.35384 6.0171 8.90722C5.98893 10.4606 6.56439 11.9643 7.62241 13.102C8.68043 14.2397 10.1385 14.9227 11.6898 15.0071C13.2412 15.0916 14.7648 14.5711 15.9401 13.555L20.3901 18L21.0001 17.39ZM6.8401 9.00001C6.83911 7.97684 7.14174 6.97638 7.70967 6.12531C8.27759 5.27423 9.08528 4.61081 10.0305 4.21903C10.9757 3.82726 12.0158 3.72475 13.0193 3.92448C14.0228 4.12421 14.9445 4.6172 15.6676 5.34104C16.3907 6.06488 16.8828 6.98701 17.0816 7.99069C17.2804 8.99437 17.1768 10.0345 16.7842 10.9793C16.3915 11.9241 15.7273 12.7311 14.8756 13.2982C14.024 13.8653 13.0233 14.167 12.0001 14.165C10.6311 14.1637 9.31867 13.6189 8.35112 12.6504C7.38358 11.682 6.8401 10.369 6.8401 9.00001Z" fill="black"/>
</g>
<path d="M97.55 5.97L98.1 6.47C97.59 6.77 96.93 7.03 96.37 7.21C96.33 7.07 96.23 6.85 96.14 6.7C96.65 6.51 97.22 6.23 97.55 5.97ZM96 6.49L96.66 6.7V9.92H96V6.49ZM96 9.58H100.52V7.04H99.25V6.43H101.2V10.2H96V9.58ZM96.29 8.01H97.75V8.58H96.29V8.01ZM99.29 8.01H100.82V8.58H99.29V8.01ZM98.2 5.61H98.88V11.25H98.2V5.61ZM95.73 11.11H100.64V11.75H95.73V11.11ZM100.43 11.11H100.57L100.7 11.08L101.17 11.31C100.22 13.44 97.94 14.42 95.49 14.82C95.43 14.63 95.29 14.34 95.16 14.18C97.45 13.88 99.64 13 100.43 11.22V11.11ZM97.04 11.68C97.85 13.02 99.6 13.88 101.7 14.21C101.53 14.37 101.33 14.66 101.23 14.87C99.08 14.46 97.32 13.48 96.38 11.93L97.04 11.68ZM92.28 10.92C93.14 10.7 94.42 10.32 95.66 9.96L95.75 10.64C94.61 11 93.41 11.38 92.48 11.65L92.28 10.92ZM92.41 7.62H95.65V8.32H92.41V7.62ZM93.8 5.61H94.53V13.94C94.53 14.35 94.43 14.56 94.17 14.67C93.91 14.79 93.51 14.81 92.84 14.8C92.82 14.62 92.71 14.32 92.62 14.12C93.07 14.13 93.49 14.13 93.61 14.13C93.75 14.13 93.8 14.08 93.8 13.94V5.61ZM107.6 12.05H108.35V14.82H107.6V12.05ZM107.6 5.6H108.34V8.4H107.6V5.6ZM107.23 8.58L107.93 8.92C107.35 9.46 106.64 10.04 106.06 10.44L105.51 10.13C106.07 9.72 106.82 9.06 107.23 8.58ZM109.39 9.23L110.07 9.6C108.94 10.52 107.4 11.58 106.18 12.26L105.65 11.9C106.87 11.21 108.45 10.08 109.39 9.23ZM104.74 10.1L105.24 9.67C105.99 10.04 106.94 10.61 107.43 11.03L106.91 11.51C106.45 11.09 105.51 10.49 104.74 10.1ZM109.28 10.89L109.78 10.49C110.66 11.01 111.77 11.78 112.33 12.31L111.8 12.77C111.27 12.24 110.17 11.42 109.28 10.89ZM109.31 13.03L109.88 12.64C110.74 13.06 111.84 13.73 112.41 14.22L111.79 14.67C111.27 14.18 110.19 13.48 109.31 13.03ZM103.65 11.74C105.48 11.74 108.5 11.7 111.35 11.64L111.31 12.24C108.54 12.32 105.59 12.41 103.69 12.43L103.65 11.74ZM105.89 12.63L106.6 12.89C105.93 13.58 104.9 14.27 104.03 14.7C103.9 14.56 103.62 14.3 103.44 14.17C104.33 13.8 105.29 13.22 105.89 12.63ZM103.76 6.49H112.21V7.15H103.76V6.49ZM103.73 8.11H112.3V9.99H111.56V8.76H104.44V9.99H103.73V8.11Z" fill="black"/>
<defs>
<clipPath id="clip0_1650_2387">
<rect width="15" height="15" fill="white" transform="translate(6 3)"/>
</clipPath>
</defs>
</svg>
</template>
+7
View File
@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
+19
View File
@@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.25" y="0.25" width="14.5" height="14.5" fill="#D9D9D9" stroke="black" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 205 B

+82
View File
@@ -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
}
]
})
+7
View File
@@ -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',
}
+5
View File
@@ -0,0 +1,5 @@
export enum CacheEnum {
TOKEN_NAME = 'login_token',
REDIRECT_ROUTE_NAME = 'redirect_router_name',
HISTORY_MENU = 'history_menu',
}
+50
View File
@@ -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,
}
+27
View File
@@ -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'
}
+18
View File
@@ -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
+74
View File
@@ -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)'
}
}
+73
View File
@@ -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张发型图片,越多效果越好)'
}
}
+54
View File
@@ -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
}
+16
View File
@@ -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')
File diff suppressed because it is too large Load Diff
+126
View File
@@ -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<T>(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<T>(config)
resolve(response.data)
} catch (error) {
reject(error)
}
}) as Promise<T>
}
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)
}
)
}
}
+294
View File
@@ -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<IResponse>({
url: '/api/cloth/list',
method: 'post',
data
})
export const getAllClothes = (data: { sex: 'male' | 'female' }) =>
http.request<IResponse>({
url: '/api/cloth/list',
method: 'post',
data: { isPerson: 'no', ...data }
})
export const getMyAllClothes = (data: any) =>
http.request<IResponse>({
url: '/api/cloth/list',
method: 'post',
data: { isPerson: 'no', ...data }
})
export const grabPicture = (data: any) =>
http.request<
Omit<IResponse, 'result'> & { result: Record<'id' | 'fileUrl' | 'thumbnailFileUrl', string>[] }
>({
url: '/api/cloth/grabPicture',
method: 'post',
data: { isPerson: 'no', ...data }
})
export const uploadClothFile = (data: IGetClothes) =>
http.request<IResponse>({
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<IResponse>({
url: '/api/cloth/list',
method: 'post',
data: { clothType: 'suit', ...data }
})
/**加入我的衣橱 */
export const addToMyClothes = (data: { clothType: IClothType; clothId: string }) =>
http.request<IResponse>({
url: '/api/cloth/addWardrobe',
method: 'post',
data
})
export const unJoinFromMyClothes = (data: { clothId: string }) =>
http.request<IResponse>({
url: '/api/cloth/unJoin',
method: 'post',
data
})
export const getModelList = (data: { sex: iGenderOptions }) =>
http.request<IResponse>({
url: '/api/cloth/getModelList',
method: 'post',
data
})
export const createSuit = (data: { topClothId: string; bottomClothId: string }) =>
http.request<IResponse>({
url: '/api/cloth/addSuit',
method: 'post',
data
})
export const tryOn = (data: {
tryOnList: { clothIds: string[]; modelId: string }[]
isHr: boolean
}) =>
http.request<IResponse>({
url: '/api/cloth/tryOn',
method: 'post',
data
})
export const deleteCloth = (data: { id: string }) =>
http.request<IResponse>({
url: '/api/cloth/deleteCloth',
method: 'post',
data
})
export const deleteModel = (data: { id: string }) =>
http.request<IResponse>({
url: '/api/cloth/deleteCloth',
method: 'post',
data
})
/**
* 获取历史批量生成数据
*/
export const getTryOnHistory = () =>
http.request<IResponse>({
url: '/api/cloth/getTryOnHistory',
method: 'post'
})
/**
* 获取用户信息
*/
export const getUserInfo = () =>
http.request<IResponse>({
url: '/api/cloth/getLoginUserInfo',
method: 'post'
})
/**
* 获取最近的一次生成试穿状态
*/
export const getCostTime = () =>
http.request<IResponse>({
url: '/api/cloth/getCostTime',
method: 'post'
})
export const uploadModel = (formData: FormData) =>
http.request<IResponse>({
url: '/api/cloth/uploadModel',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: formData
})
export const uploadAvatar = (formData: FormData) =>
http.request<IResponse>({
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<IResponse>({
url: '/api/cloth/uploadCloth',
method: 'post',
data
})
export const deleteHistory = (data: { historyIds: string[] }) =>
http.request<IResponse>({
url: '/api/cloth/batchDelHistory',
method: 'post',
data
})
//获取试衣间列表
export const uploadLink = (data: { buyLink: string; clothId: string }) =>
http.request<IResponse>({
url: '/api/cloth/buyLink',
method: 'post',
data
})
export const getRoomList = (data: { sex: iGenderOptions }) =>
http.request<IResponse>({
url: '/api/cloth/getRoomList',
method: 'post',
data
})
export const addRoom = (data: { roomName: string; sex: iGenderOptions }) =>
http.request<IResponse>({
url: '/api/cloth/addRoom',
method: 'post',
data
})
export const deleteRoom = (data: { roomId: string | number }) =>
http.request<IResponse>({
url: '/api/cloth/delRoom',
method: 'post',
data
})
export const updateRoom = (data: { roomId: string | number; roomName: string }) =>
http.request<IResponse>({
url: '/api/cloth/updateRoom',
method: 'post',
data
})
export const getRoomClothList = (data: { roomId: string | number; sex: iGenderOptions }) =>
http.request<IResponse>({
url: '/api/cloth/getRoomClothList',
method: 'post',
data
})
export const addRoomCloth = (data: { roomId: string | number; clothId: IClotheInfo['clothId'] }) =>
http.request<IResponse>({
url: '/api/cloth/addRoomCloth',
method: 'post',
data
})
export const deleteRoomCloth = (data: {
roomId: string | number
clothId: IClotheInfo['clothId']
}) =>
http.request<IResponse>({
url: '/api/cloth/delRoomCloth',
method: 'post',
data
})
export const loginOut = () =>
http.request<IResponse>({
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
}
+16
View File
@@ -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
View File
+11
View File
@@ -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 }
})
+333
View File
@@ -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<string>(
'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<string[]>([creatingImgMark.value, createFailMark.value])
const menu = reactive<INavStatus>({
mainNav: MenuEnum.MY_WARDROBE,
subNav: MenuEnum.ALL
})
type INav = {
isPerson: 'yes' | 'no'
clothType?: IClothType
}
const nav = computed<INav>(() => {
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 = <IClothType>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<IClotheInfo>()
const selectedBottomCloth = ref<IClotheInfo>()
const selectedDressCloth = ref<IClotheInfo>()
const selectMutipleModelIds = ref<IModelInfo['id'][]>([])
const selectMutipleClothesIds = ref<(IModelInfo['id'] | IModelInfo['id'][])[]>()
const selectedModel = ref<IModelInfo>()
const selectedRoomInfo = ref<IRoomInfo>()
const selectedHairCloth = ref<IClotheInfo>()
const selectedFigureCloth = ref<IClotheInfo>()
const selectedUnderwearCloth = ref<IClotheInfo>()
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<UploadUserFile[]>([])
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<string, string>
}
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
}
})
+20
View File
@@ -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)
})
+21
View File
@@ -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"]
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
+19
View File
@@ -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"]
}
}
+89
View File
@@ -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<string, any>): ImportMetaEnv {
export function parseEnv(env: Record<string, any>): 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
}
}
}
}
})