This commit is contained in:
X1627315083
2025-10-09 09:35:36 +08:00
parent cc8c064d9c
commit bc8e03180e
107 changed files with 0 additions and 0 deletions

40
src/App.vue Normal file
View File

@@ -0,0 +1,40 @@
<template>
<RouterView />
</template>
<style lang="less">
#app{
font-size: 1.6rem;
box-sizing: border-box;
overscroll-behavior: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
*{
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
touch-action: pan-x pan-y; /* 允许单指平移(滚动)但禁用捏合缩放 */
}
// touch-action: none; /* 完全禁用浏览器默认触摸行为 */
// -webkit-user-select: none;
// -moz-user-select: none;
// -ms-user-select: none;
// user-select: none;
// -webkit-touch-callout: none;
@media (min-width: 1024px) {
width: 100vw;
height: 100vh;
position:relative
}
/* touch-action: none; */
}
</style>

39
src/api/workshop.ts Normal file
View File

@@ -0,0 +1,39 @@
import request from '@/utils/request'
//创建用户
export function userCreate(params: Object) {
return request({
url: '/api/user/create',
method: 'get',
params
})
}
//记录用户选择的信息
export function userSave(params: Object) {
return request({
url: '/api/user/save',
method: 'get',
params,
meta: {
// responseAll: true // 返回所有的信息包括状态码和message和data
}
} as any)
}
//获取题目
export function getQueryPage(data: Object) {
return request({
url: '/api/outfits/queryPage',
method: 'post',
data,
// repeatRequest: true // 配置为true则可以同一时间多次调用
}as any)
}
//查报告
export function getCalculateReport() {
return request({
url: '/api/outfits/calculateReport',
method: 'get',
})
}

74
src/assets/base.css Normal file
View File

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

10
src/assets/css/style.css Normal file
View File

@@ -0,0 +1,10 @@
* {
padding: 0;
margin: 0;
list-style: none;
box-sizing: border-box;
}
html,
body {
height: 100%;
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.77201 14.9283L8.9297 15.7417C8.57304 16.0861 7.99632 16.0861 7.64345 15.7417L0.267492 8.62285C-0.0891642 8.27845 -0.0891642 7.72155 0.267492 7.38081L7.64345 0.258301C8.00011 -0.0861003 8.57683 -0.0861003 8.9297 0.258301L9.77201 1.07167C10.1325 1.41974 10.1249 1.98763 9.75683 2.32837L5.1848 6.53446H16.0894C16.594 6.53446 17 6.92649 17 7.41378V8.58622C17 9.07351 16.594 9.46554 16.0894 9.46554H5.1848L9.75683 13.6716C10.1287 14.0124 10.1363 14.5803 9.77201 14.9283Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

1
src/assets/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 276 B

31
src/assets/main.css Normal file
View File

@@ -0,0 +1,31 @@
@import './base.css';
#app {
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
/* @media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
} */

View File

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

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
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">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>

View File

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

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>

View File

@@ -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>

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>

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>

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>

5
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

32
src/main.ts Normal file
View File

@@ -0,0 +1,32 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// import ElementPlus from 'element-plus'
import store from './stores/index'
import 'normalize.css/normalize.css'
import './assets/css/style.css'
import flexible from "./utils/flexible.js";
//引入element-plus相关样式
import 'element-plus/dist/index.css'
import './assets/main.css'
import "./router/router-config" // 路由守卫,做动态路由的地方
var lastTouchEnd = 0;
document.addEventListener('touchend', function(event) {
var now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
const app = createApp(App)
// app.use(ElementPlus)
app.use(router)
app.use(store)
flexible();
app.mount('#app')

80
src/router/index.ts Normal file
View File

@@ -0,0 +1,80 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory('/'),
// history: createWebHistory(import.meta.env.VITE_APP_URL),
routes: [
{
path: '/',
redirect: "/activities",
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
},{
path: '/activities',
name: 'Activities',
component: () => import('../views/Activities/index.vue'),
children: [
// {
// path: '',
// name: 'activitiesWorkshop',
// redirect: "/activities/workshop",
// },
{
path: "workshop",
name: "Workshop",
component: () => import('../views/Activities/workshop/index.vue'),
children: [
{
path: '',
name: 'adtivitiesWorkshopLogin',
redirect: "/activities/workshop/login",
},{
path: "login",
name: "login",
component: () => import('../views/Activities/workshop/views/login.vue'),
},{
path: "edit",
name: "edit",
component: () => import('../views/Activities/workshop/views/edit.vue'),
},{
path: "success",
name: "success",
component: () => import('../views/Activities/workshop/views/success.vue'),
},
]
},
{
path: "workshop2",
name: "Workshop2",
component: () => import('../views/Activities/workshop2/index.vue'),
children: [
{
path: '',
name: 'adtivitiesWorkshop2Login',
redirect: "/activities/workshop2/login",
},{
path: "login",
name: "login2",
component: () => import('../views/Activities/workshop2/views/login.vue'),
},{
path: "edit",
name: "edit2",
component: () => import('../views/Activities/workshop2/views/edit.vue'),
},{
path: "success",
name: "success2",
component: () => import('../views/Activities/workshop2/views/success.vue'),
},
]
},
]
},
]
})
export default router

View File

@@ -0,0 +1,14 @@
import router from './index'
// 白名单页面直接进入
const whiteList = ['/login']
console.log(whiteList)
router.beforeEach((to, from, next) => {
next()
})
router.afterEach(() => {
// finish progress bar
// NProgress.done()
})

7
src/stores/index.ts Normal file
View File

@@ -0,0 +1,7 @@
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-persistedstate-plugin'
// 创建store实例
const store = createPinia()
// 使用持久化插件(全局持久化)
store.use(createPersistedState())
export default store

View File

@@ -0,0 +1,26 @@
// 每一个存储的模块命名规则use开头store结尾
import { defineStore } from 'pinia'
export const useUserInfoStore = defineStore({
id: 'userInfo', // 必须指明唯一的pinia仓库的id
state: () => {
return {
num: 0,
name: '张三',
token: ''
}
},
getters: {
doubleCount: (state) => state.num * 2
},
actions: {
changeNum() {
this.num++
},
loginOut() {
// 处理退出登录的一些逻辑
return new Promise((rez) => {
rez('111')
})
}
}
})

View File

@@ -0,0 +1,31 @@
// 每一个存储的模块命名规则use开头store结尾
import { defineStore } from 'pinia'
export const useWorkshopStore = defineStore({
id: 'workshop', // 必须指明唯一的pinia仓库的id
state: () => {
return {
userData:{
title: '123123',
name: '123123',
describe: '',
id:'',
},
queryList:[]
}
},
getters: {
// doubleCount: (state) => state.num * 2
},
actions: {
setLogin(data:any) {
this.userData.title= data.title
this.userData.name= data.name
this.userData.describe= data.describe
this.userData.id= data.id
console.log(this.userData)
},
setQueryList(list:any) {
this.queryList = list
},
}
})

48
src/utils/flexible.js Normal file
View File

@@ -0,0 +1,48 @@
import { getUniversalZoomLevel } from '@/utils/tools'
let flexible = (designWidth, maxWidth,minWidth) =>{
var doc = document, win = window, docEl = doc.documentElement, remStyle = document.createElement("style"), tid;
designWidth = designWidth || 1920;
// maxWidth = maxWidth || 1920;
// minWidth = minWidth || 500;
// minWidth = minWidth || 1024;
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
var height = docEl.getBoundingClientRect().height;
width = getUniversalZoomLevel() * width
height = getUniversalZoomLevel() * height
// width > maxWidth && (width = maxWidth);
// width < minWidth && (width = minWidth);
if(width >= 1024){
designWidth = 1920
}else{
designWidth = 375
}
var rem = Math.round(width * 10 / designWidth);
docEl.style.fontSize = rem+'px'
remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
}
//要等 wiewport 设置好后才能执行 refreshRem不然 refreshRem 会执行2次
refreshRem();
win.addEventListener("resize", function() {
clearTimeout(tid); //防止执行两次
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) { // 浏览器后退的时候重新计算
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === "complete") {
doc.body.style.fontSize = "16px";
} else {
doc.addEventListener("DOMContentLoaded", function(e) {
doc.body.style.fontSize = "16px";
}, false);
}
};
export default flexible

12
src/utils/local.ts Normal file
View File

@@ -0,0 +1,12 @@
function getLocal(key = 'token') {
return localStorage.getItem(key)
}
// 删除
function removeLocal(key = 'token') {
window.localStorage.removeItem(key)
}
// 保存
function setLocal(value: any, key = 'token') {
window.localStorage.setItem(key, value)
}
export { getLocal, removeLocal, setLocal }

173
src/utils/request.ts Normal file
View File

@@ -0,0 +1,173 @@
import axios from 'axios'
// import { Message, Loading, MessageBox } from 'element-ui'
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
import { useUserInfoStore } from '@/stores/modules/userInfo'
const store = useUserInfoStore()
import { getLocal } from '@/utils/local'
// 创建axios实例
console.log(import.meta.env.VITE_APP_URL)
const service = axios.create({
baseURL: import.meta.env.VITE_APP_URL, // api的base_url
// baseURL: import.meta.env.VITE_APP_URL, // api的base_url
timeout: 5000 // 请求超时时间
})
axios.defaults.headers.post["Content-Type"] = "application/json";
axios.defaults.headers.post['lang'] = 'en'; //配置语言请求头
axios.defaults.withCredentials = true; //跨域携带cookie
// request拦截器
service.interceptors.request.use(
(config: any) => {
removePending(config)
// 如果repeatRequest不配置那么默认该请求就取消重复接口请求
!config.repeatRequest && addPending(config)
// 打开loading
if (config.loading) {
LoadingInstance._count++
if (LoadingInstance._count === 1) {
openLoading(config.loadingDom)
}
}
// 如果登录了有token则请求携带token
// Do something before request is sent
if (store.token) {
config.headers.Authorization = getLocal('token') // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
// config.headers['X-Token'] = getLocal('token') // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
}
return config
},
(error) => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// respone拦截器
service.interceptors.response.use(
// response => response,
/**
* 下面的注释为通过response自定义code来标示请求状态当code返回如下情况为权限有问题登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/
(response: any) => {
// 已完成请求的删除请求中数组
removePending(response.config)
// 关闭loading
if (response.config.loading) {
closeLoading()
}
const res = response.data
// 处理异常的情况
if (res.errCode != 0) {
ElMessage({
message: res.errMsg,
type: 'error',
duration: 5 * 1000
})
// 403:非法的token; 50012:其他客户端登录了; 401:Token 过期了;
if (res.errCode === 403 || res.errCode === 50012 || res.errCode === 401) {
ElMessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.loginOut().then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
})
}
return Promise.reject(new Error('error'))
} else {
// 默认只返回data不返回状态码和message
// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码message和data)
const isbackAll = response.config.meta && response.config.meta.responseAll
if (isbackAll) {
return res
} else {
return res.data
}
}
},
(error) => {
error.config && removePending(error.config)
// 关闭loading
if (error.config?.loading) {
closeLoading()
}
console.log('err' + error) // for debug
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
// --------------------------------取消接口重复请求的函数-----------------------------------
// axios.js
const pendingMap = new Map()
/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/
function getPendingKey(config: any) {
const { url, method, params } = config
let { data } = config
if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}
/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/
function addPending(config: any) {
const pendingKey = getPendingKey(config)
config.cancelToken =
config.cancelToken ||
new axios.CancelToken((cancel) => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel)
}
})
}
/**
* 删除重复的请求
* @param {*} config
*/
function removePending(config: any) {
const pendingKey = getPendingKey(config)
if (pendingMap.has(pendingKey)) {
const cancelToken = pendingMap.get(pendingKey)
cancelToken(pendingKey)
pendingMap.delete(pendingKey)
}
}
// ----------------------------------loading的函数-------------------------------
const LoadingInstance: { _target: any; _count: number } = {
_target: null, // 保存Loading实例
_count: 0
}
function openLoading(loadingDom: any) {
LoadingInstance._target = ElLoading.service({
lock: true,
text: '数据正在加载中',
spinner: 'el-icon-loading',
background: 'rgba(25, 32, 53, 1)',
target: loadingDom || 'body'
})
}
function closeLoading() {
if (LoadingInstance._count > 0) LoadingInstance._count--
if (LoadingInstance._count === 0) {
LoadingInstance._target.close()
LoadingInstance._target = null
}
}
export default service

48
src/utils/tools.ts Normal file
View File

@@ -0,0 +1,48 @@
function getUniversalZoomLevel() {
// 现代浏览器方案
if (window.visualViewport) {
return window.visualViewport.scale;
}
// 备用方案1
if (window.devicePixelRatio) {
return window.devicePixelRatio;
}
// 备用方案2不精确
return window.outerWidth / window.innerWidth;
}
const getMousePosition = (e:any,bor:any) => {
// if(e?.stopPropagation)e.stopPropagation()
// if(e?.preventDefault)e.preventDefault();
let event:any
if(bor){
const touch = e.changedTouches[0] as any;
event = {
offsetX:touch.clientX - e.target.getBoundingClientRect().left,
offsetY: touch.clientY - e.target.getBoundingClientRect().top,
clientX:touch.clientX,
clientY:touch.clientY,
screenX:touch.screenX,
screenY:touch.screenY,
target:e.target,
}
// if(dom){
// event.offsetX = touch.clientX - dom.getBoundingClientRect().left
// event.offsetY = touch.clientY - dom.getBoundingClientRect().top
// }
}else{
event = {
offsetX:e.offsetX,
offsetY:e.offsetY,
clientX:e.clientX,
clientY:e.clientY,
screenX:e.screenX,
screenY:e.screenY,
target:e.target,
}
}
return event
}
export {
getUniversalZoomLevel,
getMousePosition,
}

15
src/views/AboutView.vue Normal file
View File

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

View File

@@ -0,0 +1,22 @@
<!-- <script setup lang="ts">
</script> -->
<template>
<div class="activities">
<RouterView />
</div>
</template>
<style lang="less" scoped>
.activities {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
font-family: poppinsMedium;
}
// @media (min-width: 1024px) {
// }
</style>

View File

@@ -0,0 +1,169 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import {getCalculateReport} from '@/api/workshop'
const props = defineProps({
isSummaryReport:{
type:Boolean,
default:false,
},
calculateReport:{
type:Object,
default:()=>{},
},
})
const emit = defineEmits([
'update:isSummaryReport'
])
let data = reactive({
})
const goBack = ()=>{
emit('update:isSummaryReport',false)
}
onMounted(()=>{
})
onUnmounted(()=>{
})
const {} = toRefs(data);
</script>
<template>
<div class="summaryReport">
<div class="header">
Swipe Style
<img @click="goBack" src="@/assets/images/workshop/back.svg" alt="">
</div>
<div class="body">
<div class="text">
<div class="title">Summary Report!</div>
<div class="progress">Current progress: {{ calculateReport?.count }}/{{ calculateReport?.count + calculateReport?.answeringCount }} people</div>
<!-- <div class="info">Based on the choice of the majority, system<br /> detected the most popular trends for you: <span>XXX</span>.</div> -->
</div>
<div class="popularChoices swiper">
<div class="title">Popular Choices</div>
<div class="imgBox">
<div class="imgList">
<div class="item" v-for="item in calculateReport?.mostLikeOutfits">
<img :src="item?.outfitImagePath" alt="">
</div>
</div>
</div>
</div>
<!-- <div class="text middle">
<div class="info">System detected the most unpopular trends for you: <br /> <span>XXX/XXX/XXX</span>.</div>
</div> -->
<div class="unpopularChoices swiper">
<div class="title">Unpopular Choices</div>
<div class="imgBox">
<div class="imgList">
<div class="item" v-for="item in calculateReport?.leastLikeOutfits">
<img :src="item?.outfitImagePath" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.summaryReport{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
> .header{
padding: .9rem 0;
position: relative;
font-weight: 600;
font-size: 2.2rem;
text-align: center;
font-family: poppinsBold;
> img{
position: absolute;
left: 3rem;
width: 1.7rem;
height: 1.7rem;
top: 50%;
transform: translateY(-50%);
}
}
> .body{
flex: 1;
overflow-y: auto;
> .text{
margin-top: 2rem;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
> .progress{
font-size: 1rem;
color: #cdcdcd;
font-weight: 500;
}
> .title{
font-weight: 500;
font-size: 2rem;
color: #4D52C3;
}
> .info{
font-weight: 400;
font-size: 1.2rem;
color: #000;
margin-top: 1.2rem;
> span{
font-weight: 600;
color: #4d52c3;
}
}
}
> .swiper{
width: calc(100% - 3.2rem * 2);
margin: 0 auto;
margin-top: 2.4rem;
> .title{
font-weight: 500;
font-size: 1.6rem;
// margin-bottom: 1.2rem;
}
> .imgBox{
margin-top: 1.6rem;
> .imgList{
width: auto;
overflow-x: auto;
display: flex;
> .item{
shrink: 0;
padding-bottom: 1.2rem;
padding-right: 1.2rem;
> img{
// margin-right: 1.2rem;
width: 12.8rem;
height: 19.2rem;
border-radius: 1rem;
box-shadow: .7rem .4rem 1rem 0rem rgba(0,0,0,.2);
object-fit: contain;
&:last-child{
}
}
}
}
}
}
> .popularChoices{
> .imgBox{
> .imgList{
padding-bottom: calc(5.2rem - 2.4rem);
}
> .imgList::-webkit-scrollbar {
display: none;
}
}
}
> .unpopularChoices{
}
}
}
</style>

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import {getQueryPage} from '@/api/workshop'
import { useWorkshopStore } from '@/stores/modules/workshop'
import { useRouter,useRoute, onBeforeRouteLeave } from 'vue-router'
const route = useRoute()
const router = useRouter()
const store = useWorkshopStore()
let dataDom = reactive({
loadingDom:null as any,
})
let data = reactive({
})
const handleBeforeUnload = (e:any) => {
if (window.location.pathname === '/activities/workshop/success') {
localStorage.setItem('shouldRedirectAfterRefresh', 'true');
// e.preventDefault();
// e.returnValue = '刷新后将跳转到登录页';
}
};
onBeforeRouteLeave((to, from, next) => {
if (confirm('自定义提示:确定离开当前页面吗?')) {
next();
} else {
next(false);
}
});
onMounted(()=>{
getQueryPage({page:1,size:200}).then((rv:any)=>{
store.setQueryList(rv.content)
})
setTimeout(()=>{
dataDom.loadingDom.style.opacity = '0';
setTimeout(()=>{
dataDom.loadingDom.style.display = 'none';
},1000)
},3000)
if (localStorage.getItem('shouldRedirectAfterRefresh') === 'true') {
localStorage.removeItem('shouldRedirectAfterRefresh'); // 清除标记
router.push('/activities/workshop/login'); // 跳转
}
window.addEventListener('pagehide', handleBeforeUnload)
})
onUnmounted(()=>{
window.removeEventListener('pagehide', handleBeforeUnload)
})
const { loadingDom } = toRefs(dataDom);
</script>
<template>
<div class="workshop">
<div class="loading" ref="loadingDom">
<img src="@/assets/images/workshop/loadingPage.png" alt="">
</div>
<RouterView />
</div>
</template>
<style lang="less" scoped>
.workshop {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
> .loading{
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
transition: all 1s;
background: #fff;
img{
width: 25rem;
}
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,332 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed, watch } from "vue";
import { getMousePosition } from '@/utils/tools'
import { useWorkshopStore } from '@/stores/modules/workshop'
import { gsap, TweenMax } from "gsap";
import {userSave} from '@/api/workshop'
import { useRouter,useRoute } from 'vue-router'
const store = useWorkshopStore()
const router = useRouter()
let data = reactive({
imgEvent:{} as any,
tlRight:gsap.timeline({ paused: true }),
tlLeft:gsap.timeline({ paused: true }),
editList:[
{
id:1,
},
{
id:2,
},
],
selectIndex:0 as any,
maskShow:false,
dressStyle:{},
})
let queryList = computed(()=>store.queryList) as any
let imgDom = ref()
let editDom = ref()
let noLikeDom = ref()
let likeDom = ref()
let maskDom = ref()
const next = (moveSize:any)=>{
editDom.value.style.transition = 'all .3s'
editDom.value.style.opacity = '0'
if(moveSize > 0){
queryList.value[data.selectIndex].count = false
}else{
queryList.value[data.selectIndex].count = true
}
setTimeout(()=>{
data.selectIndex++
editDom.value.style.transition = ''
editDom.value.style.opacity = '1'
data.tlLeft.progress(0)
data.tlRight.progress(0)
},300)
}
const setLikeOrOnLike = (event:any)=>{
imgDom.value.style.transition = ''
data.imgEvent = event
// document.addEventListener('mousemove', sizeMouseMove);
document.addEventListener('touchmove', sizeTouchmove);
// document.addEventListener('mouseup', sizeMouseup);
document.addEventListener('touchend', sizeMouseup);
}
const sizeTouchmove = (event:any)=>{
let e:any = getMousePosition(event,true)
let moveSize = data.imgEvent.screenX - e?.screenX
let domWidth = e?.target.parentNode.parentNode.offsetWidth
if(moveSize > 0){
data.tlLeft.progress(Math.abs(moveSize) / domWidth)
}else{
data.tlRight.progress(Math.abs(moveSize) / domWidth)
}
}
const sizeMouseup = (event:any)=>{
let e:any = getMousePosition(event,true)
let moveSize = data.imgEvent.screenX - e?.screenX
let domWidth = e?.target.parentNode.parentNode.offsetWidth
let moveScale = Math.abs(moveSize) / domWidth
if(moveScale > .4){
next(moveSize)
// emit('setLikeOrOnLike',moveSize > 0?'onLike':'like')
}else{
imgDom.value.style.transition = 'all .3s'
data.tlLeft.progress(0)
data.tlRight.progress(0)
}
// document.removeEventListener('mousemove',this.sizeMouseMove)
document.removeEventListener('touchmove',sizeTouchmove)
// document.removeEventListener('mouseup',this.sizeMouseup)
document.removeEventListener('touchend',sizeMouseup)
}
const openMask = ()=>{
let imgDomStyle = imgDom.value.getBoundingClientRect()
data.dressStyle = {
top:imgDomStyle.top + 'px',
left:imgDomStyle.left + 'px',
width:imgDomStyle.width + 'px',
height:imgDomStyle.height + 'px',
}
maskDom.value.style.opacity = '1'
maskDom.value.style.display = 'block'
document.addEventListener('touchend', touchendUp);
}
const touchendUp = ()=>{
maskDom.value.style.opacity = '0'
setTimeout(()=>{
maskDom.value.style.display = 'none'
}, 300)
document.removeEventListener('touchend',touchendUp)
}
watch(()=>data.selectIndex,(newVal,oldVal)=>{
console.log(newVal)
let value = {
userId:store.userData.id,
questionNum:queryList.value[oldVal].id,
isLike:queryList.value[oldVal].count,
}
userSave(value)
if(newVal == 30){
router.push({
path:'/activities/workshop/success',
})
}
})
const cliSetNext = (num:number)=>{
imgDom.value.style.transition = 'all .3s'
if(num > 0){
data.tlLeft.progress(1)
}else{
data.tlRight.progress(1)
}
setTimeout(()=>{
imgDom.value.style.transition = 'all .0s'
next(num)
},300)
}
onMounted(()=>{
data.tlRight.to(imgDom.value, { rotation: 20, duration: 1 }, 0) // 第 0 秒开始
.to(likeDom.value, { scale:1.3, duration: 1 }, '-=1')
data.tlLeft.to(imgDom.value, { rotation: -20, duration: 1 }, 0) // 第 0 秒开始
.to(noLikeDom.value, { scale:1.3, duration: 1 }, '-=1')
})
onUnmounted(()=>{
})
const { selectIndex, maskShow, dressStyle } = toRefs(data)
</script>
<template>
<div class="workshopEdit">
<div class="header">
Swipe Style
</div>
<div class="user">
<div class="header">
<div class="profile">
<img src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="text">
<div class="info">{{ store.userData.title }}</div>
<div class="info">{{ store.userData.name }}</div>
</div>
</div>
<div class="progress">
<div class="icon">
<img src="@/assets/images/workshop/editHanger.png" alt="">
</div>
<div class="text">{{ selectIndex + 1 }}/{{queryList.length}}</div>
</div>
</div>
<div class="edit" ref="editDom">
<img
ref="imgDom"
@mousedown.stop="setLikeOrOnLike(getMousePosition($event,false))"
@touchstart.passive="setLikeOrOnLike(getMousePosition($event,true))"
:src="queryList?.[selectIndex]?.itemImagePath" alt="">
<div class="btnBox">
<div class="likeOrNoLike">
<img ref="noLikeDom" @click="cliSetNext(1)" src="@/assets/images/workshop/editNoLike.png" alt="">
<img ref="likeDom" @click="cliSetNext(-1)" class="likeDom" src="@/assets/images/workshop/editLike.png" alt="">
</div>
<div class="tryOn" @touchstart.passive="openMask"></div>
</div>
</div>
<div class="mask" ref="maskDom">
<img :style="dressStyle" :src="queryList?.[selectIndex]?.outfitImagePath" alt="">
</div>
</div>
</template>
<style lang="less" scoped>
.workshopEdit {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
touch-action: none; /* 完全禁用浏览器默认触摸行为 */
> .header{
margin-top: .9rem;
position: relative;
font-weight: 600;
font-size: 2.2rem;
text-align: center;
font-family: poppinsSemiBold;
}
> .user{
margin-top: 1.3rem;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 3rem;
> .header{
display: flex;
align-items: center;
> .profile{
display: flex;
align-items: center;
margin-right: .8rem;
> img{
width: 3rem;
height: 3rem;
object-fit: cover;
border-radius: 50%;
}
}
> .text{
> .info{
font-weight: 500;
font-size: 1.2rem;
}
}
}
> .progress{
border: 1px solid #cbcdf1;
width: 7.2rem;
height: 2.4rem;
background: #f6f7ff;
border-radius: 1rem;
color: #4d52c3;
display: flex;
align-items: center;
justify-content: center;
> .icon{
margin-right: .1rem;
> img{
transform: translateY(.2rem);
width: 1.5rem;
height: 1.5rem;
}
}
> .text{
font-weight: 700;
font-size: 1.3rem;
}
}
}
> .edit{
padding: 0 3rem;
margin-top: 1.7rem;
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-bottom: 5rem;
> img{
width: 100%;
height: 50%;
flex: 1;
object-fit: contain;
// height: 50rem;
border-radius: 1.2rem;
box-shadow: .7rem .4rem 1rem -0rem rgba(0,0,0,.2);
transform-origin: center bottom;
margin-bottom: 2rem;
}
> .btnBox{
margin-top: 4.7rem;
padding: 0 1.1rem;
width: 100%;
> .likeOrNoLike{
width: 100%;
display: flex;
justify-content: space-between;
> img{
width: 6.6rem;
height: 6.6rem
}
}
> .tryOn{
background:
url('@/assets/images/workshop/editOpenGroup.png')
center/contain /* 位置/尺寸 */
no-repeat /* 不重复 */
fixed; /* 背景色 */
width: 7.4rem;
height: 7.4rem;
position: absolute;
left: 50%;
transform: translate(-50%,-50%);
top: 0;
}
}
}
> .mask{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: rgba(255,255,255,.5);
transition: all .3s;
opacity: 0;
display: none;
img{
position: fixed;
border-radius: 1.2rem;
box-shadow: .7rem .4rem 1rem -0rem rgba(0,0,0,.2);
object-fit: contain;
background-color: #fff;
}
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,178 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { useRouter,useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useWorkshopStore } from '@/stores/modules/workshop'
import {userCreate} from '@/api/workshop'
const route = useRoute()
const router = useRouter()
const store = useWorkshopStore()
const props = defineProps({
})
const emit = defineEmits([
]);
let data = reactive({
describe:'',
title:'',
name:'',
})
const goEditWorkshop = ()=>{
if(!data.title){
return ElMessage({
message: 'Please enter the title.',
type: 'primary' as any,
})
}
if(!data.name){
return ElMessage({
message: 'Please enter your name.',
type: 'primary' as any,
})
}
let value = {
title:data.title,
name:data.name,
description:data.describe,
}
userCreate(value).then((rv:any)=>{
store.setLogin({title:data.title,name:data.name,describe:data.describe,id:rv.id})
router.push('/activities/workshop/edit')
})
}
const textareaChange = (e:any)=>{
if(e.target.value.length <= 1000){
data.describe = e.target.value
}else{
e.target.value = data.describe
}
}
onMounted(()=>{
console.log(useWorkshopStore())
})
onUnmounted(()=>{
})
const { describe, title, name } = toRefs(data);
</script>
<template>
<div class="workshopLogin">
<div class="title">
<p>Swipe Style</p>
<img src="@/assets/images/workshop/loginTitleImg.png" alt="">
</div>
<div class="userInput">
<label>
<span>Job Title</span>
<input type="text" v-model="title" placeholder="Your job title">
</label>
<label>
<span>Name</span>
<input type="text" v-model="name" placeholder="Your name">
</label>
<label>
<span>Describe your favourite style in one sentence...</span>
<div class="textarea">
<textarea placeholder="Describe..." @input="textareaChange"></textarea>
</div>
<div class="textareaNum">{{ describe.length }}/300</div>
</label>
</div>
<div class="enter" @click="goEditWorkshop">Enter</div>
</div>
</template>
<style lang="less" scoped>
.workshopLogin {
.title{
padding-top: 3.3rem;
margin: 0 auto;
margin-top: 4rem;
color: #4D52C3;
position: relative;
margin-bottom: 2rem;
width: 31.3rem;
> p{
text-align: center;
font-weight: 700;
font-size: 3rem;
font-family: poppinsBold;
}
> img{
width: 3.5rem;
height: 3.5rem;
position: absolute;
right: 0;
top: 0;
margin-left: 11rem;
}
}
> .userInput{
width: 31.3rem;
margin: 0 auto;
> label{
display: flex;
flex-direction: column;
margin-bottom: .8rem;
> span{
font-size: 1.2rem;
color: #626262;
margin-bottom: .8rem;
font-weight: 500;
}
input, textarea{
font-size: 1.2rem;
&:focus{
outline: none; /* 清除默认焦点样式 */
}
}
> input{
border-radius: .87rem;
padding: 1.7rem;
background: #f1f4ff;
border: 1.7px #4d52c3 solid;
}
> .textarea{
background: #f1f4ff;
border: none;
height: 13rem;
border-radius: .87rem;
padding: 1.7rem;
> textarea{
border: none;
width: 100%;
height: 100%;
background: transparent;
}
}
> .textareaNum{
font-size: 1.2rem;
color: #626262;
margin-left: auto;
margin-top: 1rem;
}
}
}
> .enter{
width: 31.3rem;
margin: 0 auto;
font-size: 1.74rem;
color: #fff;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
background: #4d52c3;
border-radius: 1.3rem;
height: 5.2rem;
line-height: 5.2rem;
box-shadow: 0px .8rem 1.7rem 0px #cbd6ff;
cursor: pointer;
font-family: poppinsSemiBold;
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,186 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
import { useRouter,useRoute } from 'vue-router'
import SummaryReport from '../components/summaryReport/index.vue'
import { useWorkshopStore } from '@/stores/modules/workshop'
import {getCalculateReport} from '@/api/workshop'
const router = useRouter()
const props = defineProps({
})
const emit = defineEmits({
})
const store = useWorkshopStore()
let data = reactive({
loadingSuccess:false,
isSummaryReport:false,
calculateReport:null as any,
count:0,
})
let queryList = computed(()=>store.queryList) as any
const goSummaryReport = ()=>{
// data.loadingSuccess = true
if(!data.loadingSuccess)return
data.isSummaryReport = true
}
let time = null as any;
const getCalculate = ()=>{
getCalculateReport().then((rv:any)=>{
data.calculateReport = rv
if(rv.count < 3){
data.loadingSuccess = false
}else{
data.loadingSuccess = true
}
if(rv.answeringCount > 0){
time = setTimeout(()=>{
getCalculate()
},1000)
}
})
}
onMounted(()=>{
getCalculate()
})
onUnmounted(()=>{
clearTimeout(time)
})
const { loadingSuccess, isSummaryReport, calculateReport } = toRefs(data);
</script>
<template>
<div class="workshopSuccess">
<div class="loadingSuccess" v-show="!isSummaryReport">
<div class="congratulations">
<div class="icon">🎉</div>
<div class="text">
<div class="title">Congratulations!</div>
<div class="info">You have completed all your selections!</div>
<div class="describe">Your Result: Like: <span>{{ queryList.filter((item:any)=>item?.count).length }}</span> sets, Dislike: <span>{{ queryList.filter((item:any)=>!item.count).length }}</span> sets.</div>
</div>
</div>
<div class="botderBottom"></div>
<div class="statusBox">
<div class="title" @click="goSummaryReport">Check Report</div>
<div class="icon">
<img src="@/assets/images/workshop/successGoTo.png" alt="">
</div>
<div class="status" @click="goSummaryReport">
<img v-show="!loadingSuccess" src="@/assets/images/workshop/loading.png" alt="">
<img v-show="loadingSuccess" src="@/assets/images/workshop/success.png" alt="">
</div>
<div class="info" v-show="!loadingSuccess">Current progress: {{calculateReport?.count}}/{{ calculateReport?.count + calculateReport?.answeringCount }} people</div>
<div class="info" v-show="loadingSuccess">Done!</div>
<!-- <div class="goSummaryReport" @click="goSummaryReport" v-show="loadingSuccess">
Load More >
</div> -->
</div>
</div>
<div class="summaryReport" v-show="isSummaryReport">
<SummaryReport v-model:isSummaryReport="isSummaryReport" :calculateReport="calculateReport" ref="summaryReport"></SummaryReport>
</div>
</div>
</template>
<style lang="less" scoped>
.workshopSuccess{
width: 100%;
height: 100%;
position: relative;
> .loadingSuccess{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
> .congratulations{
margin: 0 auto;
// margin-top: 11.7rem;
width: 27rem;
margin-bottom: 4rem;
> img{
margin: 0 auto;
width: 15.4rem;
height: 15.4rem;
}
> div{
text-align: center;
}
> .icon{
font-size: 12.8rem;
display: flex;
justify-content: center;
}
> .text{
> .title{
font-size: 3rem;
font-weight: 700;
color: #4d52c3;
font-family: poppinsBold;
}
> .info{
margin: 1.2rem 1.8rem;
font-weight: 500;
font-size: 1.6rem;
}
> .describe{
font-weight: 500;
font-size: 1.2rem;
> span{
color: #4D52C3;
}
}
}
}
> .botderBottom{
width: 31rem;
border-bottom: 1px solid #d9d9d9;
margin: 0 auto;
}
> .statusBox{
margin-top: 1rem;
display: flex;
flex-direction: column;
align-items: center;
> .title{
font-size: 1.6rem;
font-weight: 600;
color: #4d52c3;
font-family: poppinsBold;
}
> .status{
margin-top: 1rem;
> img{
width: 9rem;
height: 9rem
}
}
> .info{
color: #cdcdcd;
font-size: 1.2rem;
font-weight: 500;
}
> .goSummaryReport{
margin-top: 2rem;
cursor: pointer;
font-size: 1.4rem;
font-weight: 600;
color: #4d52c3;
border-radius: 3.4rem;
line-height: 3rem;
width: 11rem;
text-align: center;
border: .6px solid #e3e3e3;
box-shadow: 0 .4rem 1.5rem -.5rem rgba(0,0,0,.2);
}
}
}
> .summaryReport{
position: absolute;
background: #fff;
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,111 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { getMousePosition } from '@/utils/tools'
import { gsap, TweenMax } from "gsap";
const props = defineProps({
item: {
type: Object,
default: () => ({})
},
})
const emit = defineEmits([
'result',
])
let data = reactive({
tlRight:gsap.timeline({ paused: true }),
tlLeft:gsap.timeline({ paused: true }),
imgEvent:{} as any,
})
let imgDom = ref()
const submit = (index:number)=>{
emit('result',index)
}
onMounted(()=>{
})
onUnmounted(()=>{
})
const {} = toRefs(data);
defineExpose({
})
</script>
<template>
<div class="judge">
<div class="edit">
<div class="title">Whose style do you think this is?</div>
<img
ref="imgDom"
:src="item.itemImagePath" alt="">
<div class="btnBox">
<div class="select">
<div class="item" @click="submit(1)">123</div>
<div class="item" @click="submit(2)">123</div>
<div class="item" @click="submit(3)">123</div>
<div class="item" @click="submit(4)">123</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.judge{
width: 100%;
height: 100%;
position: relative;
display: flex;
> .edit{
padding: 0 3rem;
padding-bottom: 1.6rem;
margin-top: 1.7rem;
flex: 1;
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
flex-direction: column;
padding-bottom: 7rem;
> .title{
font-weight: 500;
font-size: 2rem;
text-align: center;
margin-bottom: .8rem;
}
> img{
flex: 1;
overflow: hidden;
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 1.2rem;
box-shadow: .7rem .4rem 1rem -0rem rgba(0,0,0,.2);
transform-origin: center bottom;
}
> .btnBox{
// margin-top: 1.6rem;
margin-top: 1.6rem;
width: 100%;
> .select{
width: 100%;
display: flex;
gap: 1.2rem;
> .item{
text-align: center;
flex: 1;
padding: 0 1.8rem;
line-height: 2.4rem;
color: #4D52C3;
font-weight: 500;
font-size: 1.6rem;
background-color: #fff;
border: 2px solid #dadada;
border-radius: 1rem;
box-shadow: 0px .4rem .4rem 0px rgba(0,0,0,0.25);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,160 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { getMousePosition } from '@/utils/tools'
import { gsap, TweenMax } from "gsap";
const props = defineProps({
item: {
type: Object,
default: () => ({})
},
})
const emit = defineEmits([
'result',
])
let data = reactive({
tlRight:gsap.timeline({ paused: true }),
tlLeft:gsap.timeline({ paused: true }),
imgEvent:{} as any,
})
let imgDom = ref()
let noLikeDom = ref()
let likeDom = ref()
const setLikeOrOnLike = (event:any)=>{
imgDom.value.style.transition = ''
data.imgEvent = event
// document.addEventListener('mousemove', sizeMouseMove);
document.addEventListener('touchmove', sizeTouchmove);
// document.addEventListener('mouseup', sizeMouseup);
document.addEventListener('touchend', sizeMouseup);
}
const sizeTouchmove = (event:any)=>{
let e:any = getMousePosition(event,true)
let moveSize = data.imgEvent.screenX - e?.screenX
let domWidth = e?.target.parentNode.parentNode.offsetWidth
if(moveSize > 0){
data.tlLeft.progress(Math.abs(moveSize) / domWidth)
}else{
data.tlRight.progress(Math.abs(moveSize) / domWidth)
}
}
const sizeMouseup = (event:any)=>{
let e:any = getMousePosition(event,true)
let moveSize = data.imgEvent.screenX - e?.screenX
let domWidth = e?.target.parentNode.parentNode.offsetWidth
let moveScale = Math.abs(moveSize) / domWidth
if(moveScale > .4){
emit('result',moveSize)
}else{
imgDom.value.style.transition = 'all .3s'
data.tlLeft.progress(0)
data.tlRight.progress(0)
}
// document.removeEventListener('mousemove',this.sizeMouseMove)
document.removeEventListener('touchmove',sizeTouchmove)
// document.removeEventListener('mouseup',this.sizeMouseup)
document.removeEventListener('touchend',sizeMouseup)
}
const reset = ()=>{
data.tlLeft.progress(0)
data.tlRight.progress(0)
}
const cliSetNext = (num:number)=>{
imgDom.value.style.transition = 'all .3s'
if(num > 0){
data.tlLeft.progress(1)
}else{
data.tlRight.progress(1)
}
setTimeout(()=>{
imgDom.value.style.transition = 'all .0s'
emit('result',num)
},300)
}
onMounted(()=>{
data.tlRight.to(imgDom.value, { rotation: 20, duration: 1 }, 0) // 第 0 秒开始
.to(likeDom.value, { scale:1.3, duration: 1 }, '-=1')
data.tlLeft.to(imgDom.value, { rotation: -20, duration: 1 }, 0) // 第 0 秒开始
.to(noLikeDom.value, { scale:1.3, duration: 1 }, '-=1')
})
onUnmounted(()=>{
})
const {} = toRefs(data);
defineExpose({
reset,
})
</script>
<template>
<div class="judge">
<div class="edit">
<div class="title">123123123</div>
<img
ref="imgDom"
@mousedown.stop="setLikeOrOnLike(getMousePosition($event,false))"
@touchstart.passive="setLikeOrOnLike(getMousePosition($event,true))"
:src="item.itemImagePath" alt="">
<div class="btnBox">
<div class="likeOrNoLike">
<img ref="noLikeDom" @click="cliSetNext(1)" src="@/assets/images/workshop/workshop2No.png" alt="">
<img ref="likeDom" @click="cliSetNext(-1)" class="likeDom" src="@/assets/images/workshop/workshop2Yes.png" alt="">
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.judge{
width: 100%;
height: 100%;
position: relative;
display: flex;
> .edit{
padding: 0 3rem 3rem 3rem;
padding-bottom: 1.6rem;
margin-top: 1.7rem;
flex: 1;
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
flex-direction: column;
padding-bottom: 3.5rem;
> .title{
font-weight: 500;
font-size: 2rem;
text-align: center;
margin-bottom: .8rem;
}
> img{
flex: 1;
overflow: hidden;
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 1.2rem;
box-shadow: .7rem .4rem 1rem -0rem rgba(0,0,0,.2);
transform-origin: center bottom;
}
> .btnBox{
// margin-top: 1.6rem;
padding: 0 1.8rem;
margin-top: 1.6rem;
width: 100%;
> .likeOrNoLike{
width: 100%;
display: flex;
justify-content: space-between;
> img{
width: 6.6rem;
height: 6.6rem
}
}
}
}
}
</style>

View File

@@ -0,0 +1,113 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
// const props = defineProps({
// })
const emit = defineEmits([
'next'
])
let data = reactive({
})
const next = ()=>{
emit('next')
}
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="result">
<div class="icon">😄</div>
<div class="title">Correct! You got it!</div>
<div class="keyPoint">
<div class="aiBot">
<img src="@/assets/images/workshop/workshop2Bot.png" alt="">
<div>AI Bot:</div>
</div>
<div class="text">1312312312312312323123123123</div>
</div>
<div class="next" @click="next">
<div>Next</div>
<img src="@/assets/images/workshop/workshopRight.png" alt="">
</div>
</div>
</template>
<style lang="less" scoped>
.result{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
overflow-y: auto;
> div{
flex-shrink: 0;
}
> .icon{
font-size: 14.3rem;
font-weight: 400;
text-align: center;
margin-bottom: .6rem;
}
> .title{
font-size: 2.8rem;
font-family: poppinsBold;
font-weight: 700;
text-align: center;
margin-bottom: 2rem;
color: #4D52C3;
}
> .keyPoint{
font-size: 1.6rem;
font-weight: 500;
text-align: center;
margin-bottom: 2rem;
width: calc(100% - 30px * 2);
margin: 0 auto;
height: 21.5rem;
padding: 1.8rem 1.6rem 0;
border: 2px solid #b7b9e9;
border-radius: 2rem;
box-shadow: 0px .7rem .4rem 0px rgba(0, 0, 0, 0.15);
margin-bottom: 10rem;
display: flex;
flex-direction: column;
text-align: left;
> .aiBot{
margin-bottom: .6rem;
font-size: 1.6rem;
font-weight: 500;
display: flex;
align-items: center;
> img{
margin-right: .4rem;
}
}
> .text{
font-size: 1.2rem;
font-weight: 400;
font-family: poppinsRegular;
}
}
> .next{
margin: 0 3rem 0 auto;
border: 1px solid #4d52c3;
height: 3.6rem;
display: flex;
font-size: 1.6rem;
border-radius: 1rem;
padding: 0 1.3rem 0 1.6rem;
color: #4D52C3;
align-items: center;
margin-bottom: 2rem;
> img{
margin-left: .6rem;
width: .5rem;
height: 1rem;
}
}
}
</style>

View File

@@ -0,0 +1,274 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import {getCalculateReport} from '@/api/workshop'
const props = defineProps({
isSummaryReport:{
type:Boolean,
default:false,
},
calculateReport:{
type:Object,
default:()=>{},
},
})
const emit = defineEmits([
'update:isSummaryReport'
])
let data = reactive({
})
const goBack = ()=>{
emit('update:isSummaryReport',false)
}
onMounted(()=>{
})
onUnmounted(()=>{
})
const {} = toRefs(data);
</script>
<template>
<div class="summaryReport">
<img class="bg" src="@/assets/images/workshop/workshop2SuccessBg.png" alt="">
<div class="header">
Swipe Style
<img @click="goBack" src="@/assets/images/workshop/workshop2Back.png" alt="">
</div>
<div class="body">
<div class="topThree">
<div class="item item1">
<div class="head">
<img class="one" src="@/assets/images/workshop/workshop2TopicOne.png" alt="">
<img class="profile" src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="name">Name</div>
<div class="points">
99
points
</div>
</div>
<div class="item item2">
<div class="head">
<img class="profile" src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="name">Name</div>
<div class="points">
99
points
</div>
</div>
<div class="item item3">
<div class="head">
<img class="profile" src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="name">Name</div>
<div class="points">
99
points
</div>
</div>
<div class="topThreeBg">
<img src="@/assets/images/workshop/workshopTopThree.png" alt="">
</div>
</div>
</div>
<div class="tabulate">
<div class="bgImg">
<img src="@/assets/images/workshop/workshop2TopicList.png" alt="">
<div class="placeholder"></div>
</div>
<div class="content">
<div class="roll">
<div class="item">
<div class="serialNum">1</div>
<div class="profile">
<img src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="user">
<div class="name">Name</div>
<div class="points">90 points</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.summaryReport{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
> .bg{
position: absolute;
width: 100%;
height: auto;
object-fit: contain;
top: 0;
right: 0;
background-color: #4d52c3;
}
> .header{
padding: .9rem 0;
position: relative;
font-weight: 600;
font-size: 2.4rem;
text-align: center;
color: #fff;
font-family: poppinsBold;
margin-bottom: 1.2rem;
> img{
position: absolute;
left: 3rem;
width: 1.7rem;
height: 1.7rem;
top: 50%;
transform: translateY(-50%);
}
}
> .body{
flex: 1;
> .topThree{
position: relative;
width: 31.8rem;
margin: 0 auto;
> .topThreeBg{
width: 31.8rem;
padding-top: 17rem;
height: auto;
> img{
width: 100%;
height: auto;
}
}
> .item{
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
> .head{
position: relative;
> .one{
width: 4rem;
height: 4rem;
position: absolute;
z-index: 2;
left: 50%;
transform: translateX(-50%);
top: 0;
}
> .profile{
margin-top: 3rem;
width: 5.6rem;
height: 5.6rem;
}
}
> .name{
margin-bottom: .4rem;
color: #fff;
font-size: 1.6rem;
font-weight: 500;
}
> .points{
border-radius: 1.2rem;
font-size: 1.2rem;
font-weight: 500;
padding: 0 1.2rem;
line-height: 3.4rem;
background-color: #cbcdf1;
color: #4D52C3;
}
}
> .item1{
left: 50%;
transform: translateX(-50%);
top: 0;
}
> .item2{
left: 1.9rem;
top: calc(6.2rem - 3rem);
}
> .item3{
top: calc(9.4rem - 3rem);
left: auto;
right: 1.9rem;
}
}
}
> .tabulate{
margin: 0 .8rem;
margin-top: -12rem;
flex: 1;
overflow: hidden;
> .bgImg{
width: 100%;
position: absolute;
margin-top: auto;
display: flex;
flex-direction: column;
> img{
width: 100%;
height: auto;
}
> .placeholder{
margin-top: -1rem;
width: 100%;
height: 13rem;
background-color: #efeefc;
}
}
> .content{
padding: 3.2rem 1.6rem 0 1.6rem;
height: 100%;
// height: calc(100% + 12rem);
> .roll{
overflow-y: auto;
height: 100%;
> .item{
margin-bottom: 1.6rem;
background-color: #ffffff;
border-radius: 2rem;
display: flex;
align-items: center;
height: 9.2rem;
> .serialNum{
font-size: 1.6rem;
font-weight: 350;
color: #858494;
margin-left: 2.3rem;
}
> .profile{
margin-left: 2.3rem;
width: 5.6rem;
height: 5.6rem;
> img{
width: 100%;
height: 100%;
}
}
> .user{
margin-left: 1.6rem;
> .name{
font-size: 1.6rem;
font-weight: 500;
color: #0c092a;
margin-bottom: .4rem;
}
> .points{
font-size: 1.4rem;
font-weight: 400;
color: #858494;
}
}
&:last-child{
margin-bottom: 0;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import {getQueryPage} from '@/api/workshop'
import { useWorkshopStore } from '@/stores/modules/workshop'
const store = useWorkshopStore()
let dataDom = reactive({
loadingDom:null as any,
})
let data = reactive({
})
onMounted(()=>{
// getQueryPage({page:1,size:200}).then((rv:any)=>{
// store.setQueryList(rv.content)
// })
getQueryPage({page:1,size:200}).then((rv:any)=>{
store.setQueryList(rv.content)
})
setTimeout(()=>{
dataDom.loadingDom.style.opacity = '0';
setTimeout(()=>{
dataDom.loadingDom.style.display = 'none';
},1000)
},3000)
})
onUnmounted(()=>{
})
const { loadingDom } = toRefs(dataDom);
</script>
<template>
<div class="workshop">
<div class="loading" ref="loadingDom">
<img src="@/assets/images/workshop/loadingPage.png" alt="">
</div>
<RouterView />
</div>
</template>
<style lang="less" scoped>
.workshop {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
> .loading{
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
transition: all 1s;
background: #fff;
img{
width: 25rem;
}
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,223 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed, watch } from "vue";
import { useWorkshopStore } from '@/stores/modules/workshop'
import {userSave} from '@/api/workshop'
import { useRouter,useRoute } from 'vue-router'
import editJudge from '../components/edit/editJudge.vue'
import editChoice from '../components/edit/editChoice.vue'
import result from '../components/edit/result.vue'
const store = useWorkshopStore()
const router = useRouter()
let data = reactive({
selectIndex:0,
isResult:false,
})
let queryList = ref([
{
id:1,
count:null as any,
type:'judge',
},
{
id:2,
count:null as any,
type:'choice',
},
{
id:3,
count:null as any,
type:'choice',
},
{
id:4,
count:null as any,
type:'judge',
},
] as any)
// let queryList = computed(()=>store.queryList) as any
let editDom = ref(null) as any
let editJudgeDom = ref(null) as any
let editChoiceDom = ref(null) as any
const judgeResult = (moveSize:any)=>{
editDom.value.style.transition = 'all .3s'
editDom.value.style.opacity = '0'
if(moveSize > 0){
queryList.value[data.selectIndex].count = false
}else{
queryList.value[data.selectIndex].count = true
}
setTimeout(()=>{
data.isResult = true
},300)
}
const choiceResult = (number:number)=>{
editDom.value.style.transition = 'all .3s'
editDom.value.style.opacity = '0'
queryList.value[data.selectIndex].count = number
setTimeout(()=>{
data.isResult = true
},300)
}
const next = ()=>{
data.selectIndex++
editDom.value.style.transition = ''
editDom.value.style.opacity = '1'
data.isResult = false
editJudgeDom.value.reset()
}
watch(()=>data.selectIndex,(newVal,oldVal)=>{
let value = {
userId:store.userData.id,
questionNum:queryList.value[oldVal].id,
isLike:queryList.value[oldVal].count,
}
// userSave(value)
if(newVal == queryList.value.length){
router.push({
path:'/activities/workshop2/success',
})
}
})
onMounted(()=>{
})
onUnmounted(()=>{
})
const { selectIndex, isResult } = toRefs(data)
</script>
<template>
<div class="workshopEdit">
<div class="header">
Swipe Style
</div>
<div class="user">
<div class="header">
<div class="profile">
<img src="@/assets/images/workshop/editProfile.png" alt="">
</div>
<div class="text">
<div class="info">{{ store.userData.title }}</div>
<div class="info">{{ store.userData.name }}</div>
</div>
</div>
<div class="progress">
<div class="label">
<div class="icon">
<img src="@/assets/images/workshop/workshop2Topic.png" alt="">
</div>
<div class="text">{{ selectIndex + 1 }}/{{queryList.length}}</div>
</div>
<div class="label">
<div class="icon">
<img src="@/assets/images/workshop/workshop2Ranking.png" alt="">
</div>
<div class="text">{{ selectIndex + 1 }}/{{queryList.length}}</div>
</div>
</div>
</div>
<div class="edit" ref="editDom" v-show="!isResult" v-if="queryList?.length > selectIndex">
<editJudge v-show="queryList?.[selectIndex].type == 'judge'" ref="editJudgeDom" @result="judgeResult" :item="queryList?.[selectIndex]"></editJudge>
<editChoice v-show="queryList?.[selectIndex].type == 'choice'" ref="editChoiceDom" @result="choiceResult" :item="queryList?.[selectIndex]"></editChoice>
</div>
<div class="edit" v-show="isResult">
<result @next="next"></result>
</div>
</div>
</template>
<style lang="less" scoped>
.workshopEdit {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
touch-action: none; /* 完全禁用浏览器默认触摸行为 */
> .header{
margin-top: .9rem;
position: relative;
font-weight: 600;
font-size: 2.2rem;
text-align: center;
font-family: poppinsSemiBold;
}
> .user{
margin-top: 1.3rem;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 3rem;
> .header{
display: flex;
align-items: center;
> .profile{
display: flex;
align-items: center;
margin-right: .8rem;
> img{
width: 3rem;
height: 3rem;
object-fit: cover;
border-radius: 50%;
}
}
> .text{
> .info{
font-weight: 500;
font-size: 1.2rem;
}
}
}
> .progress{
// display: flex;
> .label{
border: 1px solid #cbcdf1;
width: 7.2rem;
height: 2.4rem;
background: #f6f7ff;
border-radius: 1rem;
color: #4d52c3;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: .4rem;
&:last-child{
margin-bottom: 0rem;
}
> .icon{
margin-right: .35rem;
display: flex;
align-items: center;
justify-content: center;
> img{
width: 1.5rem;
height: 1.5rem;
}
}
> .text{
font-weight: 700;
font-size: 1.3rem;
}
}
}
}
> .edit{
flex: 1;
overflow: hidden;
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,176 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { useRouter,useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useWorkshopStore } from '@/stores/modules/workshop'
import {userCreate} from '@/api/workshop'
const route = useRoute()
const router = useRouter()
const store = useWorkshopStore()
const props = defineProps({
})
const emit = defineEmits([
]);
let data = reactive({
describe:'',
title:'',
name:'',
})
const goEditWorkshop = ()=>{
if(!data.title){
return ElMessage({
message: 'Please enter the title.',
type: 'primary' as any,
})
}
if(!data.name){
return ElMessage({
message: 'Please enter your name.',
type: 'primary' as any,
})
}
let value = {
title:data.title,
name:data.name,
}
userCreate(value).then((rv:any)=>{
// store.setLogin({title:data.title,name:data.name,id:rv.id})
router.push('/activities/workshop/edit')
})
}
onMounted(()=>{
console.log(useWorkshopStore())
})
onUnmounted(()=>{
})
const { title, name } = toRefs(data);
</script>
<template>
<div class="workshopLogin">
<div class="title">
<p>Swipe Style</p>
<div class="info">Welcome back!</div>
<img src="@/assets/images/workshop/loginTitleImg.png" alt="">
</div>
<div class="userInput">
<label>
<span>Job Title</span>
<input type="text" v-model="title" placeholder="Your job title">
</label>
<label>
<span>Name</span>
<input type="text" v-model="name" placeholder="Your name">
</label>
</div>
<div class="enter" @click="goEditWorkshop">Enter</div>
</div>
</template>
<style lang="less" scoped>
.workshopLogin {
.title{
padding-top: 3.3rem;
margin: 0 auto;
color: #4D52C3;
position: relative;
width: 31.3rem;
margin-top: 3.5rem;
margin-bottom: 3.5rem;
> p{
text-align: center;
font-weight: 700;
font-size: 3rem;
font-family: poppinsBold;
margin-top: 3.5rem;
}
> .info{
font-size: 2rem;
margin-top: 1.2rem;
font-weight: 500;
color: #000;
text-align: center;
}
> img{
width: 3.5rem;
height: 3.5rem;
position: absolute;
right: 0;
top: 0;
margin-left: 11rem;
}
}
> .userInput{
width: 31.3rem;
margin: 0 auto;
margin-bottom: 6.5rem;
> label{
display: flex;
flex-direction: column;
margin-bottom: .8rem;
&:last-child{
margin-top: 0;
}
> span{
font-size: 1.2rem;
color: #626262;
margin-bottom: .8rem;
font-weight: 500;
}
input, textarea{
font-size: 1.2rem;
&:focus{
outline: none; /* 清除默认焦点样式 */
}
}
> input{
border-radius: .87rem;
padding: 1.7rem;
background: #f1f4ff;
border: 1.7px #4d52c3 solid;
}
> .textarea{
background: #f1f4ff;
border: none;
height: 13rem;
border-radius: .87rem;
padding: 1.7rem;
> textarea{
border: none;
width: 100%;
height: 100%;
background: transparent;
}
}
> .textareaNum{
font-size: 1.2rem;
color: #626262;
margin-left: auto;
margin-top: 1rem;
}
}
}
> .enter{
width: 31.3rem;
margin: 0 auto;
font-size: 1.74rem;
color: #fff;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
background: #4d52c3;
border-radius: 1.3rem;
height: 5.2rem;
line-height: 5.2rem;
box-shadow: 0px .8rem 1.7rem 0px #cbd6ff;
cursor: pointer;
font-family: poppinsSemiBold;
}
}
@media (min-width: 1024px) {
}
</style>

View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
import { useRouter,useRoute } from 'vue-router'
import SummaryReport from '../components/summaryReport/index.vue'
import { useWorkshopStore } from '@/stores/modules/workshop'
import {getCalculateReport} from '@/api/workshop'
const router = useRouter()
const props = defineProps({
})
const emit = defineEmits({
})
const store = useWorkshopStore()
let data = reactive({
isSummaryReport:false,
calculateReport:null as any,
count:0,
})
let queryList = computed(()=>store.queryList) as any
const goSummaryReport = ()=>{
// data.loadingSuccess = true
data.isSummaryReport = true
}
let time = null as any;
const getCalculate = ()=>{
getCalculateReport().then((rv:any)=>{
data.calculateReport = rv
if(rv.count < 3){
}else{
}
if(rv.answeringCount > 0){
time = setTimeout(()=>{
getCalculate()
},1000)
}
})
}
const setResiart = ()=>{
router.push({path:'/activities/workshop2/edit'})
}
onMounted(()=>{
getCalculate()
})
onUnmounted(()=>{
clearTimeout(time)
})
const { isSummaryReport, calculateReport } = toRefs(data);
</script>
<template>
<div class="workshopSuccess">
<div class="loadingSuccess" v-show="!isSummaryReport">
<div class="congratulations">
<div class="icon">🎉</div>
<div class="text">
<div class="title">Congratulations!</div>
<div class="info">You have completed all your selections!</div>
<div class="describe">Your AI's compatibility with you is: <span>{{ queryList.filter((item:any)=>item?.count).length }}</span></div>
</div>
</div>
<div class="statusBox">
<div class="goSummaryReport" @click="setResiart">
<img class="workshopRestart" src="@/assets/images/workshop/workshopRestart.png" alt="">
<div>Resiart</div>
</div>
<div class="goSummaryReport" @click="goSummaryReport">
<div>Load More</div>
<img class="workshopRight" src="@/assets/images/workshop/workshopRight.png" alt="">
</div>
</div>
</div>
<div class="summaryReport" v-show="isSummaryReport">
<SummaryReport v-model:isSummaryReport="isSummaryReport" :calculateReport="calculateReport" ref="summaryReport"></SummaryReport>
</div>
</div>
</template>
<style lang="less" scoped>
.workshopSuccess{
width: 100%;
height: 100%;
position: relative;
> .loadingSuccess{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
> .congratulations{
margin: 0 auto;
// margin-top: 11.7rem;
width: 27rem;
margin-bottom: 2.7rem;
> img{
margin: 0 auto;
width: 15.4rem;
height: 15.4rem;
}
> div{
text-align: center;
}
> .icon{
font-size: 12.8rem;
display: flex;
justify-content: center;
margin-bottom: 1.2rem;
}
> .text{
> .title{
font-size: 2.8rem;
font-weight: 700;
color: #4d52c3;
font-family: poppinsBold;
}
> .info{
font-weight: 500;
font-size: 1.6rem;
margin-bottom: 2rem;
}
> .describe{
font-weight: 500;
font-size: 1.2rem;
> span{
color: #4D52C3;
}
}
}
}
> .statusBox{
display: flex;
justify-content: center;
> .goSummaryReport{
margin-right: 3.2rem;
font-family: poppinsBold;
margin-top: 2rem;
cursor: pointer;
font-size: 1.4rem;
font-weight: 600;
color: #4d52c3;
border-radius: 3.4rem;
height: 3rem;
text-align: center;
padding: 0 1.1rem;
border: .6px solid #e3e3e3;
box-shadow: 0 .4rem 1.5rem -.5rem rgba(0,0,0,.2);
display: flex;
justify-content: center;
align-items: center;
&:last-child{
margin-right: 0;
}
> .workshopRestart{
width: 1rem;
height: 1rem;
margin-right: .4rem;
}
> .workshopRight{
width: .6rem;
height: .8rem;
margin-left: .4rem;
}
}
}
}
> .summaryReport{
position: absolute;
background: #fff;
width: 100%;
height: 100%;
}
}
</style>

15
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import TheWelcome from '_c/TheWelcome.vue'
import { useUserInfoStore } from '@/stores/modules/userInfo'
const store = useUserInfoStore()
store.changeNum()
console.log(store.name)
console.log(store.num)
console.log(import.meta.env.VITE_APP_URL)
</script>
<template>
<main>
<TheWelcome />
</main>
</template>