添加 老项目的代码 参考

This commit is contained in:
xsl
2026-08-19 21:48:12 +08:00
parent 312ef2b716
commit f5b167e4b6
89 changed files with 6496 additions and 0 deletions
+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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
Vue’s
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" 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>
@@ -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>
@@ -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

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