Merge branch 'dev_vite' of http://18.167.251.121:10003/aidlab/aida_front into dev_vite

This commit is contained in:
李志鹏
2026-04-17 10:17:05 +08:00
18 changed files with 856 additions and 226 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View File

@@ -9,6 +9,11 @@
"id": 2,
"title": "AiDA X SFT AI Fashion Award 2024",
"imgUrl": "/image/events/Fashion-Award-2024.png"
},
{
"id": 3,
"title": "AiDA Global Design Awards 2026",
"imgUrl": "/image/events/award-poster.gif"
}
],
"eventsItem": [
@@ -20,7 +25,7 @@
{
"paragraph": [
{
"text": "Click the “View Details” button for more information and to join the competition! The AiDA Global Design Award 2026 is an international design competition hosted by CodeCreate, a globally leading AI fashion solutions provider, celebrating the future of creativity powered by artificial intelligence. Open to designers from Hong Kong, China, Singapore, South Korea, and beyond, the competition brings together global talent, empowering AI as a creative partner—pushing fashion beyond traditional boundaries and unlocking new possibilities where technology amplifies human imagination."
"text": "Click the “View Details” button for more information and to join the competition! The AiDA Global Design Award 2026 is an international design competition hosted by CodeCreate, a globally leading AI fashion solutions provider, celebrating the future of creativity powered by artificial intelligence. Open to designers worldwide the competition brings together global talent, empowering AI as a creative partner—pushing fashion beyond traditional boundaries and unlocking new possibilities where technology amplifies human imagination."
}
]
},

View File

@@ -9,6 +9,11 @@
"id": 2,
"title": "AiDA X SFT AI时尚设计比赛2024",
"imgUrl": "/image/events/Fashion-Award-2024.png"
},
{
"id": 3,
"title": "AiDA全球设计奖 2026",
"imgUrl": "/image/events/award-poster-zh.gif"
}
],
"eventsItem": [
@@ -20,7 +25,7 @@
{
"paragraph": [
{
"text": "秉承推动 AI 赋能创意设计的初衷CodeCreate 举办了「AiDA 全球设计大奖 2026」面向来自香港、中国、新加坡、韩国及全球的设计师,鼓励大家探索 AI 与时尚设计的无限可能,突破传统界限,释放科技与想象力的创新潜能。点击“查看详情”按钮获取更多比赛信息,抓住成为 AI 时尚先锋的机会吧!"
"text": "秉承推动 AI 赋能创意设计的初衷CodeCreate 举办了「AiDA 全球设计大奖 2026」面向来全球的设计师鼓励大家探索 AI 与时尚设计的无限可能,突破传统界限,释放科技与想象力的创新潜能。点击“查看详情”按钮获取更多比赛信息,抓住成为 AI 时尚先锋的机会吧!"
}
]
},

View File

@@ -1251,8 +1251,8 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
border-color: #000 !important;
}
.ant-spin .ant-spin-dot {
width: 1.5em;
height: 1.5em;
width: 4.5rem;
height: 4.5rem;
}
.ant-spin-dot-item {
background-color: #000000 !important;
@@ -2470,6 +2470,22 @@ textarea:focus {
opacity: 0.8;
border-radius: 0.7rem;
}
.mini-scrollbar::-webkit-scrollbar {
width: 0.4rem;
}
.mini-scrollbar::-webkit-scrollbar-thumb {
border-radius: 0.4rem;
background: rgba(0, 0, 0, 0.2);
}
.mosaic-bg {
--mosaic-bg-size: 1rem;
--mosaic-bg-color1: #efefef;
--mosaic-bg-color2: #fff;
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
background-repeat: repeat;
background-position: 50% 50%;
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
}
.mark_loading {
position: fixed;
width: 100%;
@@ -2507,6 +2523,6 @@ textarea:focus {
.justify-center {
justify-content: center;
}
.flex-1{
flex: 1;
}
.flex-1 {
flex: 1;
}

View File

@@ -1379,8 +1379,8 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
}
//loding样式
.ant-spin .ant-spin-dot{
width: 1.5em;
height: 1.5em;
width: 4.5rem;
height: 4.5rem;
}
.ant-spin-dot-item{
background-color: #000000 !important;

View File

@@ -1,22 +1,37 @@
<template>
<div class="account_systemMessage">
<div class="account_systemMessage">
<div class="account_generalMessage_title modal_title_text">
<!-- <span>系统消息</span> -->
<div class="account_generalMessage_title_setting" @click="allRead">{{$t('account.AllRead')}}</div>
<div class="account_generalMessage_title_setting" @click="allRead">
{{ $t("account.AllRead") }}
</div>
</div>
<div class="account_generalMessage_item modal_title_text" v-for="item in dataList" :key="item.id" @click="setRead(item)">
<div
class="account_generalMessage_item modal_title_text"
v-for="item in dataList"
:key="item.id"
@click="setRead(item)"
>
<a-badge :dot="item.isRead == 0"></a-badge>
<div class="account_generalMessage_item_title">
<div class="account_generalMessage_item_title_text" :title="item.content">{{ item.content.title }}</div>
<div class="account_generalMessage_item_title_text" :title="item.content">
{{ item.content.title }}
</div>
<div class="modal_title_text_intro">{{ item.createTime }}</div>
</div>
<div class="modal_title_text_intro">
{{ item.content.content }}
<span v-if="item.content.link" class="account_generalMessage_item_link">{{ item.content.link }}</span>
<span v-if="item.content.link" class="account_generalMessage_item_link">{{
item.content.link
}}</span>
</div>
</div>
<div class="account_generalMessage_item modal_title_text" style="display:flex;justify-content: center;" v-if="dataList.length == 0 && isNoData">
{{$t('account.dataNull')}}
<div
class="account_generalMessage_item modal_title_text"
style="display: flex; justify-content: center"
v-if="dataList.length == 0 && isNoData"
>
{{ $t("account.dataNull") }}
</div>
<div class="page_loading_box" v-show="!isNoData">
<span class="page_loading" ref="loadingDom" v-show="!isShowMark"></span>
@@ -24,120 +39,138 @@
<a-spin size="large" />
</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent,computed,ref,reactive,nextTick,toRefs,createVNode, onMounted} from 'vue'
import { Https } from "@/tool/https";
import { useRouter,useRoute } from 'vue-router'
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
import {
defineComponent,
computed,
ref,
reactive,
nextTick,
toRefs,
createVNode,
onMounted
} from "vue"
import { Https } from "@/tool/https"
import { useRouter, useRoute } from "vue-router"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
import { isValidUrl } from "@/tool/util"
export default defineComponent({
components:{
},
components: {},
// emits:['putListData'],
props:['setReadStatus','setAllmessage','getHistory'],
setup(prop,{emit}) {
props: ["setReadStatus", "setAllmessage", "getHistory"],
setup(prop, { emit }) {
const router = useRouter()
const store = useStore();
const store = useStore()
let accountMessage = reactive({
dataList: [],
page:1,
size:10,
page: 1,
size: 10,
isNoData: false,
isShowMark: false,
isShowMark: false
})
let loadingDom:any = ref(null)
let setmessageList = ()=>{
let loadingDom: any = ref(null)
let setmessageList = () => {
accountMessage.isShowMark = true
let data = {
page: accountMessage.page,
size: accountMessage.size,
size: accountMessage.size
}
prop.getHistory(data).then((rv:any)=>{
accountMessage.isShowMark = false
if(rv.content.length == 0) {
prop.getHistory(data)
.then((rv: any) => {
accountMessage.isShowMark = false
if (rv.content.length == 0) {
accountMessage.isNoData = true
} else {
rv.content.forEach((item: any) => {
item.content = JSON.parse(item.content)
})
accountMessage.dataList.push(...rv.content)
}
})
.catch(() => {
accountMessage.isShowMark = false
accountMessage.isNoData = true
}else{
rv.content.forEach((item:any) => {
item.content = JSON.parse(item.content)
});
accountMessage.dataList.push(...rv.content)
})
}
let setRead = (item: any) => {
let content = item.content.content
if (isValidUrl(content)) {
if (import.meta.env.VITE_APP_BASE_URL === "https://develop.api.aida.com.hk") {
content += "&env=dev"
}
}).catch(() => {
accountMessage.isShowMark = false
accountMessage.isNoData = true
})
}
let setRead = (item:any)=>{
prop.setReadStatus(item).then((rv:any)=>{
item.isRead = 1
}).catch((err:any)=>{
})
}
let allRead = ()=>{
// emit('setAllmessage')
prop.setAllmessage().then(()=>{
accountMessage.dataList.forEach((item:any)=>{
window.open(content, "_blank")
}
prop.setReadStatus(item)
.then((rv: any) => {
item.isRead = 1
})
}).catch((err:any)=>{
})
.catch((err: any) => {})
}
let allRead = () => {
// emit('setAllmessage')
prop.setAllmessage()
.then(() => {
accountMessage.dataList.forEach((item: any) => {
item.isRead = 1
})
})
.catch((err: any) => {})
}
// provide('exhibitionList',exhibitionList)
onMounted (()=>{
onMounted(() => {
accountMessage.isNoData = false
accountMessage.page = 0
let imgParent:any = document.querySelector('.account_systemMessage .page_loading')
let imgParent: any = document.querySelector(".account_systemMessage .page_loading")
new IntersectionObserver(
(entries, observer) => {
// 如果不是相交,则直接返回
// console.log(entries[0]);
if (!entries[0].intersectionRatio) return;
accountMessage.page+=1
if (!entries[0].intersectionRatio) return
accountMessage.page += 1
setmessageList()
},
}
// { root:worksPage }
).observe(loadingDom.value);
).observe(loadingDom.value)
})
return{
...toRefs(accountMessage),
setmessageList,
setRead,
allRead,
loadingDom,
}
},
data(){
return{
}
},
return {
...toRefs(accountMessage),
setmessageList,
setRead,
allRead,
loadingDom
}
},
data() {
return {}
}
})
</script>
<style lang="less" scoped>
.account_systemMessage{
.account_systemMessage {
width: 100%;
.account_generalMessage_item{
.account_generalMessage_item {
font-size: var(--aida-fsize1-6);
.account_generalMessage_item_title{
.account_generalMessage_item_title {
display: flex;
align-items: center;
margin-bottom: 2rem;
.account_generalMessage_item_title_text{
.account_generalMessage_item_title_text {
max-width: 80%;
white-space: nowrap;
overflow: hidden;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.modal_title_text_intro{
.modal_title_text_intro {
margin-left: 4rem;
}
}
}
.modal_title_text_intro{
.modal_title_text_intro {
word-break: break-word;
white-space: pre-wrap;
font-family: Arial, sans-serif;

View File

@@ -233,10 +233,10 @@ export default defineComponent({
let sketchH = editPrintElementData.sketchWH.height * editPrintElementData.sketchWH.scale[1]
let x = sketchW / 2 - (sketchW * (width / editPrintElementData.sketchWH.width)/2)
let y = sketchH / 2 -(sketchH * height/2)
if(editPrintElementData.stateOverallSingle !== 'single'){
x = sketchW / 2
y = sketchH / 2
}
// if(editPrintElementData.stateOverallSingle !== 'single'){
// x = sketchW / 2
// y = sketchH / 2
// }
let location = [x,y]
resolve({scale,location})
}

View File

@@ -21,7 +21,11 @@
{{ $t("event.detail") }}
</div>
</div>
<div class="modal_title_text content" v-for="item in eventsDetail.textList">
<div
class="modal_title_text content"
v-for="item in eventsDetail.textList"
:class="{ award: eventsDetail.id === 3 }"
>
<div class="eventsDetail_content_right_btn_box">
<div
class="eventsDetail_content_right_btn"
@@ -122,9 +126,14 @@ export default defineComponent({
const openDetail = () => {
let language = locale.value === "ENGLISH" ? "en" : "zh"
let url = `https://aida-global-design-awards.com.hk/${language}`
// 如果是dev环境把域名换成http://192.168.31.198
if (import.meta.env.VITE_APP_BASE_URL === "https://develop.api.aida.com.hk") {
url += "?env=dev"
}
window.open(url, "_blank")
// router.push("/award/index")
// router.push("/award/index")
}
onMounted(() => {
const currentLocale = locale.value
@@ -265,18 +274,21 @@ export default defineComponent({
.eventsDetail_content_right_btn_box {
display: flex;
justify-content: space-evenly;
.eventsDetail_content_right_btn {
}
// .eventsDetail_content_right_btn {
// }
}
}
.modal_title_text:last-child {
}
// .modal_title_text:last-child {
// }
.modal_title_text:last-child::after {
content: "";
display: block;
border-top: 3px solid;
height: 6rem;
}
.modal_title_text.award:last-child:after {
display: none;
}
}
}
}

View File

@@ -1748,5 +1748,17 @@ export default {
productImageSubTitle:' (来自设计集)',
apparelSketchTitle:'服装线稿图 ',
apparelSketchSubTitle:' (来自设计集)',
productName:'商品名称',
price:'价格',
productDescription:'商品描述',
designFor:'目标人群',
productCategory:'商品类别',
categoryTips:'请选择所有适用选项',
policy:'默认情况下,所有销售均遵循平台的许可政策——买家在下载后将获得使用许可',
learnMore:'了解更多',
draftSaved: '草稿已保存',
draftDesc: '您的商品已保存为草稿。\n您可以继续编辑或稍后在“我的商品”中发布。',
listingLive:'商品已上架',
publishDesc:'您的商品现已上架。\n买家可以浏览并购买您的设计。'
}
}

View File

@@ -1799,5 +1799,17 @@ export default {
productImageSubTitle:'(from design collection)',
apparelSketchTitle:'Apparel Sketch ',
apparelSketchSubTitle:'(from design collection)',
productName:'Product Name',
price:'Price',
productDescription:'Product Description',
designFor:'Designed For',
productCategory:'Product Category',
categoryTips:'select all that apply',
policy:'By default, all sales follow the platform\'s licensing policy — buyers will receive a usage license upon download.',
learnMore:'Learn more',
draftSaved: 'Draft Saved',
draftDesc: 'Your listing has been saved as a draft. \nYou can continue editing or publish it later from My Listings.',
listingLive:'Listing Live',
publishDesc:'Your listing is now live on the marketplace.\nBuyers can discover and purchase your design.'
}
}

View File

@@ -220,6 +220,13 @@ const routes: Array<RouteRecordRaw> = [
meta: { enter: "all" },
component: () =>
import("@/views/SellerDashboard/MyListings/EditDetail/index.vue")
},
{
path:'edit/status/:status',
name:'Status',
meta:{enter:'all'},
component: () =>
import("@/views/SellerDashboard/MyListings/EditDetail/Status.vue")
}
]
},
@@ -483,7 +490,6 @@ const routes: Array<RouteRecordRaw> = [
},
component: () => import("@/views/userManual.vue")
},
{
path: "/:catchAll(.*)",
redirect: "/404"

View File

@@ -672,6 +672,17 @@ function sketchToMask(sketchImage) {
img.src = sketchImage;
});
}
function isValidUrl(string) {
try {
const url = new URL(string)
// 通常我们只需要 http 或 https 协议
return url.protocol === "http:" || url.protocol === "https:"
} catch (err) {
return false
}
}
export {
isEmail,
getUploadUrl,
@@ -695,5 +706,6 @@ export {
calculateGradientCoordinate,
segmentImage,
UrlToFile,
sketchToMask
sketchToMask,
isValidUrl
}

View File

@@ -0,0 +1,55 @@
<template>
<div class="edit-detail-wrapper">
<div class="edit-detail-header flex align-center space-between">
<div class="bread-crumb">导航</div>
<div class="operate-menu flex">
<div class="menu-btn flex align-center save">
<span>Save Draft</span>
<SvgIcon name="CSave" color="#000000" size="16" />
</div>
<div class="menu-btn flex align-center publish">
<span>Publish</span>
<SvgIcon name="CPublish" color="#ffffff" size="16" />
</div>
</div>
</div>
<div class="edit-detail-content"></div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.c-svg {
width: initial;
}
.edit-detail-wrapper {
.menu-btn {
height: 6rem;
border: 0.15rem solid #000000;
border-radius: 4rem;
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-weight: 400;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
}
.edit-detail-header {
width: 100%;
height: 6rem;
margin-bottom: 2rem;
.operate-menu {
column-gap: 2rem;
.publish {
background-color: #000000;
color: #ffffff;
}
}
}
.edit-detail-content{
padding-right: 6.4rem;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div class="status-wrapper flex flex-col flex-1">
<seller-header
class="edit-detail-header"
title="Edit Listing Details"
:breadcrumbs="[
{ title: 'My Listings', name: 'myListingsIndex' },
{ title: 'Select Collection', name: 'myListingsSelect' },
{ title: 'Select Sketch', name: 'myListingsSelectItem' },
{ title: 'Edit Listing Details', name: 'EditDetail' },
{ title: $t(title), name: 'Status' }
]"
/>
<div class="status-container flex flex-col flex-1 flex-center">
<img src="@/assets/images/seller/success-0.png" class="icon" alt="" />
<div class="title">{{ $t(title) }}</div>
<div class="desc">
{{ $t(desc) }}
</div>
<div class="btn">Back to My Listings</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
import { useRoute } from "vue-router"
import SellerHeader from "../../seller-header.vue"
const ROUTE = useRoute()
const title = computed(() => {
if (ROUTE.params.status === "publish") return "SellerListEdit.listingLive"
if (ROUTE.params.status === "draft") return "SellerListEdit.draftSaved"
})
const desc = computed(() => {
if (ROUTE.params.status === "publish") return "SellerListEdit.publishDesc"
if (ROUTE.params.status === "draft") return "SellerListEdit.draftDesc"
})
</script>
<style lang="less" scoped>
.status-wrapper {
.status-container {
row-gap: 2.4rem;
font-weight: 400;
.title {
font-size: 2.2rem;
}
.icon {
width: 8.33rem;
height: 8.33rem;
}
.desc {
width: 58.2rem;
text-align: center;
white-space: pre-line;
color: #585858;
font-size: 1.8rem;
}
.btn {
margin-top: 3.6rem;
height: 6rem;
width: 30rem;
border-radius: 4rem;
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
background-color: #000;
color: #fff;
}
}
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div class="radio-button-group">
<button
v-for="item in options"
:key="item.key"
type="button"
:class="[
'radio-button',
{
'is-active': multiple
? selectedValues.includes(item.key)
: modelValue === item.key
}
]"
@click="selectOption(item.key)"
>
{{ item.name }}
</button>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue"
interface Option {
name: string | number
value: string | number | boolean
key: string
optype: boolean
}
const props = defineProps<{
modelValue: string | number | boolean | Array<string | number | boolean> | null
options: Option[] // 按钮选项数组
multiple?: boolean // 是否支持多选,默认为 false
}>()
const emit = defineEmits<{
(e: "update:modelValue", value: any): void
}>()
const multiple = props.multiple === true
const selectedValues = computed(() => {
if (!multiple) {
return typeof props.modelValue === 'undefined' || props.modelValue === null
? []
: [props.modelValue]
}
return Array.isArray(props.modelValue) ? props.modelValue : []
})
const selectOption = (value: any) => {
if (multiple) {
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
const index = current.indexOf(value)
if (index >= 0) {
current.splice(index, 1)
} else {
current.push(value)
}
emit("update:modelValue", current)
return
}
if (props.modelValue !== value) {
emit("update:modelValue", value)
}
}
</script>
<style lang="less" scoped>
.radio-button-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.radio-button {
border: 1px solid #d9d9d9;
height: 3.2rem;
min-width: 8rem;
padding: 0 1.7rem;
color: #000;
cursor: pointer;
border-radius: 2rem;
outline: none;
background-color: #fff;
font-size: 1.2rem;
transition: all 0.2s ease;
}
.radio-button:hover {
border-color: #000;
}
.radio-button.is-active {
color: #fff;
background-color: #000;
border-color: #000;
font-family: pingfang_medium;
}
</style>

View File

@@ -12,18 +12,21 @@
>
<template #right>
<div class="operate-menu flex">
<div class="menu-btn flex align-center save">
<div class="menu-btn flex align-center save" @click="handleClickMenu('draft')">
<span>{{ $t("SellerListEdit.saveDraft") }}</span>
<SvgIcon name="CSave" color="#000000" size="16" />
<SvgIcon name="CSave" size="16" />
</div>
<div class="menu-btn flex align-center publish">
<div
class="menu-btn flex align-center publish"
@click="handleClickMenu('publish')"
>
<span>{{ $t("SellerListEdit.publish") }}</span>
<SvgIcon name="CPublish" color="#ffffff" size="16" />
<SvgIcon name="CPublish" size="16" />
</div>
</div>
</template>
</seller-header>
<div class="edit-detail-content flex space-between">
<div class="edit-detail-content flex">
<div class="left">
<div class="main-image-container flex">
<div
@@ -32,25 +35,20 @@
:class="`main-image-item flex flex-col align-center ${type}`"
>
<div class="title" :class="{ required: type !== 'mainProductImage' }">
{{ $t(`SellerListEdit.${type}`) }}
{{ topImageTitleMap[type] }}
</div>
<div class="sketch-item flex flex-center" :class="type">
<div
v-show="selectList[currentIndex][type]"
class="crop-tool flex flex-center"
>
<div v-if="previewImageMap[type]" class="crop-tool flex flex-center">
<SvgIcon name="CCrop" color="#fff" size="12" />
</div>
<img
v-show="selectList[currentIndex][type]"
:src="selectList[currentIndex][type]"
v-if="previewImageMap[type]"
:src="previewImageMap[type]"
class="sketch-img"
:class="{ cover: type === 'cover' }"
alt=""
/>
<div
class="trigger flex flex-col align-center"
v-show="!selectList[currentIndex][type]"
>
<div v-else class="trigger flex flex-col align-center">
<template v-if="type === 'cover'">
<SvgIcon
class="trigger-icon"
@@ -63,10 +61,7 @@
</div>
</template>
<template v-else>
<img
src="@/assets/images/seller/image-placeholder.png"
class="trigger-img"
/>
<div class="trigger-img placeholder"></div>
<div class="trigger-tips">
{{ $t("SellerListEdit.productImageDesc") }}
</div>
@@ -92,17 +87,18 @@
<div class="product-image-list flex">
<div
v-for="(item, index) in prodImgList"
:key="index"
class="product-image-item flex flex-center"
:class="{ selected: item.selected }"
:key="index"
@click="handleSelectProdImg(index)"
>
<img
v-show="item.selected"
v-if="item.selected"
src="@/assets/images/seller/checked.png"
class="checked"
alt=""
/>
<img class="img-src" :src="item.url" />
<img class="img-src" :src="item.url" alt="" />
<div
v-if="item.selected && index === firstSelectedIndex"
class="main-pic"
@@ -127,7 +123,7 @@
:key="index"
class="sketch-element flex flex-center"
>
<img class="img-src" :src="item.url" />
<img class="img-src" :src="item.url" alt="" />
<div class="crop-tool flex flex-center">
<SvgIcon name="CCrop" color="#fff" size="12" />
</div>
@@ -138,12 +134,13 @@
<div class="right">
<div class="form-container flex flex-col">
<div class="form-item">
<div class="form-item-label required">Product Name</div>
<div class="form-item-label required">
{{ $t("SellerListEdit.productName") }}
</div>
<div class="form-item-value product-name">
<a-input
v-model:value="selectList[currentIndex].productName"
v-model:value="currentListing.productName"
show-count
:rows="2"
placeholder="Enter product name"
:bordered="false"
:maxlength="60"
@@ -151,16 +148,66 @@
</div>
</div>
<div class="form-item">
<div class="form-item-label required">Price</div>
<div class="form-item-label required">{{ $t("SellerListEdit.price") }}</div>
<div class="form-item-value price flex align-center">
<span>HK$</span>
<a-input
v-model:value="selectList[currentIndex].desc"
v-model:value="currentListing.price"
placeholder="0.00"
:bordered="false"
/>
</div>
</div>
<div class="form-item">
<div class="form-item-label required">
{{ $t("SellerListEdit.productDescription") }}
</div>
<div class="form-item-value desc">
<a-textarea
v-model:value="currentListing.desc"
show-count
:rows="4"
placeholder="Enter product description"
:bordered="false"
:maxlength="500"
/>
</div>
</div>
<div class="form-item">
<div class="form-item-label required">
{{ $t("SellerListEdit.designFor") }}
</div>
<div class="form-item-value no-border">
<Radio :options="genderOptions" v-model="currentListing.gender" />
</div>
</div>
<div class="form-item">
<div class="form-item-label with-tip">
<span class="required">{{ $t("SellerListEdit.productCategory") }}</span>
<span class="help-text">{{ $t("SellerListEdit.categoryTips") }}</span>
</div>
<div class="form-item-value no-border">
<Radio :options="categoryOptions" v-model="currentListing.category" />
</div>
</div>
<div class="license-note flex align-center">
<img src="@/assets/images/seller/tips.png" class="info-icon" />
<div class="note-copy">
{{ $t("SellerListEdit.policy") }}
<a href="javascript:void(0)">{{ $t("SellerListEdit.learnMore") }}</a>
</div>
</div>
</div>
<div class="page-control flex align-center">
<a-pagination
v-model:current="currentPage"
:total="selectList.length"
:page-size="1"
showQuickJumper
showLessItems
responsive
:showSizeChanger="false"
/>
</div>
</div>
</div>
@@ -168,14 +215,62 @@
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
import { computed, ref, watch, defineOptions } from "vue"
import { useRouter } from "vue-router"
import SellerHeader from "../../seller-header.vue"
import testImg from "@/assets/images/test.png"
import { ElInput } from "element-plus"
import Radio from "./components/Radio.vue"
import { Https } from "@/tool/https"
import { useStore } from "vuex"
const topImageList = ["sketch", "mainProductImage", "cover"]
const currentIndex = ref(0)
const selectList = ref([
const ROUTER = useRouter()
defineOptions({
name: "EditDetail"
})
const STORE = useStore()
type CategoryOption = {
label: string
value: string
}
type ListingItem = {
sketch: string
mainProductImage: string
cover: string
productImage: string[]
apparelSketch: string[]
productName: string
price: string
desc: string
gender: string
category: string
prodImageList: Array<{
url: string
selected: boolean
}>
}
const topImageList = ["sketch", "mainProductImage", "cover"] as const
const topImageTitleMap: Record<(typeof topImageList)[number], string> = {
sketch: "Sketch",
mainProductImage: "Main Product Image",
cover: "Cover"
}
const genderOptions = STORE.state.UserHabit?.sex.value || []
const fallbackCategoryOptions: Record<string, CategoryOption[]> = {
MALE: STORE.state.UserHabit?.MalePosition || [],
FEMALE: STORE.state.UserHabit?.FemalePosition || []
}
const currentPage = ref(1)
const currentIndex = computed(() => currentPage.value - 1)
const selectList = ref<ListingItem[]>([
{
sketch: testImg,
mainProductImage: "",
@@ -183,69 +278,117 @@ const selectList = ref([
productImage: [],
apparelSketch: [],
productName: "",
price: "",
price: "12",
desc: "",
gender: "FEMALE",
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
},
{
sketch: testImg,
mainProductImage: "",
cover: "",
productImage: [],
apparelSketch: [],
productName: "",
price: "12",
desc: "",
gender: "",
category: ""
}
])
const prodImgList = ref([
{
url: testImg,
selected: false
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
sketch: testImg,
mainProductImage: "",
cover: "",
productImage: [],
apparelSketch: [],
productName: "",
price: "12",
desc: "",
gender: "",
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
}
])
const prodImgList = computed(() => currentListing.value.prodImageList || [])
const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
const categoryOptions = computed(() => {
const gender = selectList.value[currentIndex.value].gender
return fallbackCategoryOptions[gender] || []
})
const currentListing = computed(() => selectList.value[currentIndex.value])
const previewImageMap = computed(() => ({
sketch: currentListing.value.sketch,
mainProductImage: currentListing.value.mainProductImage,
cover:
currentListing.value.cover ||
currentListing.value.mainProductImage ||
currentListing.value.sketch
}))
const firstSelectedIndex = ref(null) //显示main标签的图片索引
const selectedProdImgs = computed(() => {
return prodImgList.value.filter((item) => item.selected).length
})
const firstSelectedIndex = ref(-1)
const handleSelectProdImg = (index: number) => {
const item = prodImgList.value[index]
item.selected = !item.selected
const target = prodImgList.value[index]
if (item.selected) {
if (firstSelectedIndex.value === -1) {
firstSelectedIndex.value = index
selectList.value[currentIndex.value].mainProductImage = item.url
}
} else if (firstSelectedIndex.value === index) {
selectList.value[currentIndex.value].mainProductImage = null
firstSelectedIndex.value = -1
const willSelect = !target.selected
target.selected = willSelect
if (willSelect && !currentListing.value.mainProductImage) {
currentListing.value.mainProductImage = target.url
firstSelectedIndex.value = index
}
if (!willSelect && currentListing.value.mainProductImage === target.url) {
firstSelectedIndex.value = null
currentListing.value.mainProductImage = ""
}
}
const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
const handleClickMenu = (status: "draft" | "publish") => {
if (status === "draft") {
// save draft logic
console.log("Saving draft...", currentListing.value)
ROUTER.push({ name: "Status", params: { status: "draft" } })
} else if (status === "publish") {
// publish logic
console.log("Publishing...", currentListing.value)
ROUTER.push({ name: "Status", params: { status: "publish" } })
}
}
</script>
<style lang="less" scoped>
@@ -255,8 +398,13 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
}
.edit-detail-wrapper {
overflow-y: auto;
// set the scollbar hidden
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
margin-top: -1rem;
overflow: hidden;
&::-webkit-scrollbar {
display: none;
}
@@ -268,26 +416,47 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-weight: 400;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
transition: all 0.25s ease;
&:hover {
background: #000;
color: #fff;
}
// &.publish:hover {
// background: #fff;
// color: #000;
// }
}
.edit-detail-header {
margin-bottom: 2rem;
}
.operate-menu {
column-gap: 2rem;
.publish {
background-color: #000000;
color: #ffffff;
}
// .publish {
// background-color: #000000;
// color: #ffffff;
// }
}
}
.edit-detail-content {
padding-right: 6.4rem;
flex: 1;
min-height: 0;
overflow-y: auto;
justify-content: space-between;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
.required {
&::after {
content: "*";
@@ -295,17 +464,23 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
margin-left: 0.4rem;
}
}
.left {
// flex: 1;
// min-width: 0;
.main-image-container {
column-gap: 3.6rem;
// max-width: 80.2rem;
.main-image-item {
flex-shrink: 0;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 0.8rem;
text-align: center;
}
.sketch-item {
width: 11.6rem;
height: 20.4rem;
@@ -313,62 +488,77 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
border-radius: 1rem;
position: relative;
background-color: #f6f6f6;
overflow: hidden;
&.cover {
width: 16.17rem;
// border-style: dashed;
width: 16.2rem;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='11' ry='11' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='8%2c 5' stroke-linecap='square'/%3e%3c/svg%3e");
border: none;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='13%2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
}
.crop-tool {
position: absolute;
top: 0.4rem;
right: 0.4rem;
top: 0.8rem;
right: 0.8rem;
width: 2rem;
height: 2rem;
border-radius: 50%;
cursor: pointer;
background-color: #000000;
z-index: 1;
cursor: pointer;
}
.sketch-img {
// width: 100%;
height: 100%;
}
.trigger {
row-gap: 1.2rem;
cursor: pointer;
height: 100%;
padding-top: 6.8rem;
.trigger-img {
padding: 6rem 2rem 0;
.placeholder {
width: 2.4rem;
height: 2.4rem;
border-radius: 0.6rem;
background: linear-gradient(135deg, #efefef 0%, #cdcdcd 100%);
}
.trigger-tips {
font-size: 1.2rem;
width: 9rem;
text-align: center;
color: #585858;
line-height: 1.3;
}
}
}
}
}
.product-image-list-container {
margin-top: 3rem;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 1.2rem;
.main-title {
font-weight: 400;
font-style: bold;
}
.sub-title {
font-size: 1.2rem;
color: #999;
}
.title-right {
color: #585858;
font-size: 1.4rem;
font-weight: 400;
}
}
.product-image-list {
overflow-x: auto;
overflow-y: hidden;
@@ -376,19 +566,18 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
max-width: 80.2rem;
padding-bottom: 1.2rem;
//滚动条高度0.8rem,背景色#D9D9D9,滚动条圆角0.4rem,小方块为黑色
&::-webkit-scrollbar {
height: 0.8rem;
}
&::-webkit-scrollbar-track {
background: #d9d9d9;
border-radius: 0.8rem;
height: 0.8rem;
}
&::-webkit-scrollbar-thumb {
background: #000000;
border-radius: 0.8rem;
height: 0.8rem;
}
.product-image-item {
@@ -398,6 +587,9 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
border: 0.15rem solid #c7c7c7;
position: relative;
cursor: pointer;
overflow: hidden;
flex-shrink: 0;
&::after {
position: absolute;
top: 0;
@@ -409,22 +601,28 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
opacity: 0.7;
border-radius: 1rem;
}
&.selected {
// border-color: #000000;
border-color: #000;
&::after {
display: none;
}
}
.checked {
width: 2rem;
height: 2rem;
position: absolute;
top: 0.8rem;
right: 0.8rem;
z-index: 1;
}
.img-src {
height: 100%;
}
.main-pic {
position: absolute;
height: 2.4rem;
@@ -433,8 +631,7 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
right: 0.8rem;
bottom: 0.8rem;
z-index: 1;
background: #000000cc;
font-weight: 400;
background: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: 1.4rem;
border-radius: 1.2rem;
@@ -443,34 +640,46 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
}
}
}
.apparel-container {
margin-top: 3rem;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 0.8rem;
.main-title {
font-weight: 400;
font-style: bold;
&::after {
content: "*";
color: #df2b2c;
}
}
.sub-title {
font-size: 1.2rem;
color: #999;
}
}
.sketch-list-container {
column-gap: 1rem;
flex-wrap: wrap;
.sketch-element {
width: 10rem;
height: 14.6rem;
border: 0.15rem solid #c7c7c7;
border-radius: 1.2rem;
position: relative;
overflow: hidden;
.img-src {
width: 100%;
height: 100%;
}
.crop-tool {
position: absolute;
top: 0.4rem;
@@ -478,24 +687,40 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
width: 2rem;
height: 2rem;
border-radius: 50%;
cursor: pointer;
background-color: #000000;
}
}
}
}
}
.right {
width: 55.2rem;
flex-shrink: 0;
.form-container {
row-gap: 3rem;
.form-item {
.form-item-label {
font-size: 1.4rem;
font-weight: 400;
font-style: bold;
margin-bottom: 0.6rem;
line-height: 1.5;
&.with-tip {
display: flex;
align-items: center;
column-gap: 0.8rem;
}
.help-text {
font-size: 1rem;
color: #999;
}
}
.form-item-value {
border: 0.16rem solid #d1d1d1;
border-radius: 1.2rem;
@@ -503,32 +728,87 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
padding: 1.6rem;
box-sizing: border-box;
font-size: 1.2rem;
font-weight: 400;
color: #000;
&.no-border {
border: none;
padding: 0;
}
&.price {
column-gap: 0.6rem;
}
:deep(.ant-input) {
:deep(.ant-input),
:deep(.ant-input-affix-wrapper),
:deep(.ant-input-textarea textarea) {
border: none;
padding: 0;
font-size: 1.2rem;
font-weight: 400;
color: #000;
box-shadow: none;
}
:deep(.ant-input-affix-wrapper) {
padding: 0;
font-weight: 400;
input {
font-size: 1.2rem;
}
:deep(.ant-input) {
line-height: 1.5;
}
:deep(.ant-input-show-count-suffix) {
color: #df2c2c;
font-size: 1rem;
}
:deep(textarea.ant-input) {
resize: none;
min-height: 5.4rem;
padding-bottom: 1.8rem;
line-height: 1.5;
}
:deep(.ant-input-textarea-show-count) {
position: relative;
&::after {
float: none;
position: absolute;
color: #df2c2c;
bottom: 0;
right: 1.6rem;
}
}
}
}
}
.license-note {
padding: 1.6rem;
column-gap: 1.6rem;
background: #f7f7f7;
border-radius: 0.8rem;
.info-icon {
width: 2.4rem;
height: 2.4rem;
flex-shrink: 0;
}
.note-copy {
font-size: 1.2rem;
line-height: 1.5;
color: #000;
font-weight: 400;
font-style: medium;
a {
color: #0080ed;
text-decoration: underline;
margin-left: 0.4rem;
}
}
}
.page-control {
justify-content: flex-end;
margin-top: 4rem;
}
}
}
</style>

View File

@@ -245,7 +245,7 @@ const { showDrafts } = toRefs(data);
v-bind="config"
:group="{
name: 'sortable',
pull: true,
pull: false,
put: true
}"
>

View File

@@ -20,8 +20,8 @@ const fun = ref(null)
let deleteDraftsRef = ref(null)
const open = (fun)=>{
fun.value = fun
const open = (deleteFun)=>{
fun.value = deleteFun
emit('update:visible', true)
}