Merge branch 'dev_vite' of http://18.167.251.121:10003/aidlab/aida_front into dev_vite
This commit is contained in:
BIN
src/assets/images/seller/selectCollectionNullStatus.png
Normal file
BIN
src/assets/images/seller/selectCollectionNullStatus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -8,16 +8,11 @@
|
|||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
class="range_picker"
|
class="range_picker"
|
||||||
v-model:value="rangePickerValue"
|
v-model:value="rangePickerValue"
|
||||||
:placeholder="[
|
:placeholder="[$t('HistoryPage.StartDate'), $t('HistoryPage.EndDate')]"
|
||||||
$t('HistoryPage.StartDate'),
|
|
||||||
$t('HistoryPage.EndDate'),
|
|
||||||
]"
|
|
||||||
valueFormat="YYYY-MM-DD"
|
valueFormat="YYYY-MM-DD"
|
||||||
>
|
>
|
||||||
<template #suffixIcon>
|
<template #suffixIcon>
|
||||||
<span
|
<span class="icon iconfont range_picker_icon icon-rili"></span>
|
||||||
class="icon iconfont range_picker_icon icon-rili"
|
|
||||||
></span>
|
|
||||||
</template>
|
</template>
|
||||||
</a-range-picker>
|
</a-range-picker>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,38 +58,50 @@
|
|||||||
show-search
|
show-search
|
||||||
></a-select>
|
></a-select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="admin_state_item">
|
||||||
|
<span>Orgnization:</span>
|
||||||
|
<a-select
|
||||||
|
v-model:value="organizationId"
|
||||||
|
placeholder="Select the organization"
|
||||||
|
allow-clear
|
||||||
|
style="width: 250px"
|
||||||
|
@popupScroll="handleOrganizationScroll"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in organizationOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_search">
|
<div class="admin_search">
|
||||||
<div class="admin_search_item" @click="searchHistoryList" :style="{height:isAwayOrUnfold?'4rem':''}">
|
<div
|
||||||
|
class="admin_search_item"
|
||||||
|
@click="searchHistoryList"
|
||||||
|
:style="{ height: isAwayOrUnfold ? '4rem' : '' }"
|
||||||
|
>
|
||||||
Search
|
Search
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_search_item" @click="addhHistoryList">
|
<div class="admin_search_item" @click="addhHistoryList">Add</div>
|
||||||
Add
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_state_list">
|
<div class="admin_state_list">
|
||||||
<div
|
<div class="admin_state_list_item" @click="lastGeTrialList('year')">
|
||||||
class="admin_state_list_item"
|
|
||||||
@click="lastGeTrialList('year')"
|
|
||||||
>
|
|
||||||
Nearly a year
|
Nearly a year
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="admin_state_list_item" @click="lastGeTrialList('month')">
|
||||||
class="admin_state_list_item"
|
|
||||||
@click="lastGeTrialList('month')"
|
|
||||||
>
|
|
||||||
Last month
|
Last month
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="admin_state_list_item" @click="lastGeTrialList('week')">Last week</div>
|
||||||
class="admin_state_list_item"
|
|
||||||
@click="lastGeTrialList('week')"
|
|
||||||
>
|
|
||||||
Last week
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="awayOrUnfold" :class="{ active: isAwayOrUnfold }">
|
<div class="awayOrUnfold" :class="{ active: isAwayOrUnfold }">
|
||||||
<span class="icon iconfont menu_icon icon-xiala" @click="()=>isAwayOrUnfold = !isAwayOrUnfold"></span>
|
<span
|
||||||
|
class="icon iconfont menu_icon icon-xiala"
|
||||||
|
@click="() => (isAwayOrUnfold = !isAwayOrUnfold)"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_table_content" ref="historyTable">
|
<div class="admin_table_content" ref="historyTable">
|
||||||
<a-table
|
<a-table
|
||||||
@@ -104,24 +111,19 @@
|
|||||||
:data-source="dataList"
|
:data-source="dataList"
|
||||||
:scroll="{ y: historyTableHeight }"
|
:scroll="{ y: historyTableHeight }"
|
||||||
@change="changePage"
|
@change="changePage"
|
||||||
:showSorterTooltip='false'
|
:showSorterTooltip="false"
|
||||||
:pagination="{
|
:pagination="{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
total: total,
|
total: total,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
bordered: false,
|
bordered: false
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, text, record, index }">
|
<template #bodyCell="{ column, text, record, index }">
|
||||||
<div class="operate_list" v-if="column?.Operations">
|
<div class="operate_list" v-if="column?.Operations">
|
||||||
<div
|
<div class="operate_item" @click="setAagree(record)">Edit</div>
|
||||||
class="operate_item"
|
|
||||||
@click="setAagree(record)"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</div>
|
|
||||||
<!-- <div
|
<!-- <div
|
||||||
class="operate_item"
|
class="operate_item"
|
||||||
@click="deleteGroup(record, index)"
|
@click="deleteGroup(record, index)"
|
||||||
@@ -132,24 +134,19 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</div>
|
</div>
|
||||||
<allUserPoerationsVue ref="allUserPoerationsVue" @searchHistoryList="searchHistoryList"></allUserPoerationsVue>
|
<allUserPoerationsVue
|
||||||
|
ref="allUserPoerationsVue"
|
||||||
|
@searchHistoryList="searchHistoryList"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { defineComponent, ref, createVNode, computed, reactive, toRefs, onMounted } from "vue"
|
||||||
defineComponent,
|
import { formatTime } from "@/tool/util"
|
||||||
ref,
|
import { useStore } from "vuex"
|
||||||
createVNode,
|
import { Https } from "@/tool/https"
|
||||||
computed,
|
import allUserPoerationsVue from "./allUserPoerations.vue"
|
||||||
reactive,
|
import SelectUser from "@/component/common/SelectUser.vue"
|
||||||
toRefs,
|
|
||||||
onMounted,
|
|
||||||
} from "vue";
|
|
||||||
import { formatTime } from "@/tool/util";
|
|
||||||
import { useStore } from "vuex";
|
|
||||||
import { Https } from "@/tool/https";
|
|
||||||
import allUserPoerationsVue from "./allUserPoerations.vue";
|
|
||||||
import SelectUser from '@/component/common/SelectUser.vue'
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { allUserPoerationsVue, SelectUser },
|
components: { allUserPoerationsVue, SelectUser },
|
||||||
setup() {
|
setup() {
|
||||||
@@ -159,7 +156,7 @@ export default defineComponent({
|
|||||||
tableLoading: false,
|
tableLoading: false,
|
||||||
allCountry: [],
|
allCountry: [],
|
||||||
isAwayOrUnfold: false
|
isAwayOrUnfold: false
|
||||||
});
|
})
|
||||||
let filterData: any = reactive({
|
let filterData: any = reactive({
|
||||||
rangePickerValue: [],
|
rangePickerValue: [],
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
@@ -172,40 +169,41 @@ export default defineComponent({
|
|||||||
occupation: "",
|
occupation: "",
|
||||||
systemUser: "",
|
systemUser: "",
|
||||||
order: "", //'Ascending 升序 Descending 降序'
|
order: "", //'Ascending 升序 Descending 降序'
|
||||||
orderBy:'',
|
orderBy: "",
|
||||||
userName: "",
|
userName: "",
|
||||||
});
|
organizationId: null
|
||||||
|
})
|
||||||
let state: any = ref([
|
let state: any = ref([
|
||||||
{
|
{
|
||||||
label: "all",
|
label: "all",
|
||||||
value: "",
|
value: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'visitor',
|
label: "visitor",
|
||||||
value:'0',
|
value: "0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'yearly',
|
label: "yearly",
|
||||||
value:'1',
|
value: "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'monthly',
|
label: "monthly",
|
||||||
value:'2',
|
value: "2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'trial',
|
label: "trial",
|
||||||
value:'3',
|
value: "3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "userInEvent",
|
label: "userInEvent",
|
||||||
value: "4",
|
value: "4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edu Admin",
|
label: "Edu Admin",
|
||||||
value: "7",
|
value: "7"
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
let renameData: any = ref({}); //修改名字选中的数据
|
let renameData: any = ref({}) //修改名字选中的数据
|
||||||
const columns: any = computed(() => {
|
const columns: any = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -215,7 +213,7 @@ export default defineComponent({
|
|||||||
key: "id",
|
key: "id",
|
||||||
width: 100,
|
width: 100,
|
||||||
fixed: "left",
|
fixed: "left",
|
||||||
sorter: true,
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Email",
|
title: "Email",
|
||||||
@@ -246,7 +244,7 @@ export default defineComponent({
|
|||||||
dataIndex: "language",
|
dataIndex: "language",
|
||||||
key: "language",
|
key: "language",
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis:true,
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Valid Start Time",
|
title: "Valid Start Time",
|
||||||
@@ -256,12 +254,12 @@ export default defineComponent({
|
|||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
customRender: (record: any) => {
|
customRender: (record: any) => {
|
||||||
let time = ''
|
let time = ""
|
||||||
if (record.text) {
|
if (record.text) {
|
||||||
time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
time = formatTime(record.text / 1000, "YYYY-MM-DD hh:mm:ss")
|
||||||
}
|
}
|
||||||
return time
|
return time
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Valid End Time",
|
title: "Valid End Time",
|
||||||
@@ -271,19 +269,19 @@ export default defineComponent({
|
|||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
customRender: (record: any) => {
|
customRender: (record: any) => {
|
||||||
let time = ''
|
let time = ""
|
||||||
if (record.text) {
|
if (record.text) {
|
||||||
time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
time = formatTime(record.text / 1000, "YYYY-MM-DD hh:mm:ss")
|
||||||
}
|
}
|
||||||
return time
|
return time
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Country or Region",
|
title: "Country or Region",
|
||||||
align: "center",
|
align: "center",
|
||||||
dataIndex: "country",
|
dataIndex: "country",
|
||||||
key: "country",
|
key: "country",
|
||||||
width:200,
|
width: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Create Date",
|
title: "Create Date",
|
||||||
@@ -291,7 +289,7 @@ export default defineComponent({
|
|||||||
dataIndex: "createDate",
|
dataIndex: "createDate",
|
||||||
key: "createDate",
|
key: "createDate",
|
||||||
width: 200,
|
width: 200,
|
||||||
sorter: true,
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Is Beginner",
|
title: "Is Beginner",
|
||||||
@@ -301,21 +299,21 @@ export default defineComponent({
|
|||||||
width: 80,
|
width: 80,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
customRender: (record: any) => {
|
customRender: (record: any) => {
|
||||||
let str;
|
let str
|
||||||
if (record.value == 1) {
|
if (record.value == 1) {
|
||||||
str = "Yes";
|
str = "Yes"
|
||||||
} else {
|
} else {
|
||||||
str = "No";
|
str = "No"
|
||||||
|
}
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
return str;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Machine Room Ip',
|
title: "Machine Room Ip",
|
||||||
align: "center",
|
align: "center",
|
||||||
dataIndex: "browserIdentifiers",
|
dataIndex: "browserIdentifiers",
|
||||||
key: "browserIdentifiers",
|
key: "browserIdentifiers",
|
||||||
width:200,
|
width: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Credits",
|
title: "Credits",
|
||||||
@@ -327,10 +325,10 @@ export default defineComponent({
|
|||||||
dataIndex: "credits",
|
dataIndex: "credits",
|
||||||
key: "credits",
|
key: "credits",
|
||||||
width: 100,
|
width: 100,
|
||||||
sorter: true,
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'User Type',
|
title: "User Type",
|
||||||
align: "center",
|
align: "center",
|
||||||
// width: 150,
|
// width: 150,
|
||||||
// minWidth: 100,
|
// minWidth: 100,
|
||||||
@@ -340,22 +338,22 @@ export default defineComponent({
|
|||||||
key: "systemUser",
|
key: "systemUser",
|
||||||
width: 100,
|
width: 100,
|
||||||
customRender: (record: any) => {
|
customRender: (record: any) => {
|
||||||
let str;
|
let str
|
||||||
if (record.value == 0) {
|
if (record.value == 0) {
|
||||||
str = "visitor";
|
str = "visitor"
|
||||||
} else if (record.value == 1) {
|
} else if (record.value == 1) {
|
||||||
str = "yearly";
|
str = "yearly"
|
||||||
} else if (record.value == 2) {
|
} else if (record.value == 2) {
|
||||||
str = "monthly";
|
str = "monthly"
|
||||||
} else if (record.value == 3) {
|
} else if (record.value == 3) {
|
||||||
str = "trial";
|
str = "trial"
|
||||||
} else if (record.value == 4) {
|
} else if (record.value == 4) {
|
||||||
str = "userInEvent";
|
str = "userInEvent"
|
||||||
} else if (record.value == 7) {
|
} else if (record.value == 7) {
|
||||||
str = "Edu Admin";
|
str = "Edu Admin"
|
||||||
|
}
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
return str;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Operations",
|
title: "Operations",
|
||||||
@@ -364,58 +362,58 @@ export default defineComponent({
|
|||||||
align: "center",
|
align: "center",
|
||||||
fixed: "right",
|
fixed: "right",
|
||||||
// slots:{customRender:'action'}
|
// slots:{customRender:'action'}
|
||||||
Operations: true,
|
Operations: true
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
});
|
})
|
||||||
//改变页码
|
//改变页码
|
||||||
let changePage = (e: any, filters: any, sorter: any) => {
|
let changePage = (e: any, filters: any, sorter: any) => {
|
||||||
filterData.currentPage = e.current;
|
filterData.currentPage = e.current
|
||||||
filterData.pageSize = e.pageSize;
|
filterData.pageSize = e.pageSize
|
||||||
if (sorter.order) {
|
if (sorter.order) {
|
||||||
if(sorter.columnKey == 'id'){
|
if (sorter.columnKey == "id") {
|
||||||
filterData.orderBy = 'id'
|
filterData.orderBy = "id"
|
||||||
} else if (sorter.columnKey == "createDate") {
|
} else if (sorter.columnKey == "createDate") {
|
||||||
filterData.orderBy = 'time'
|
filterData.orderBy = "time"
|
||||||
} else if (sorter.columnKey == "credits") {
|
} else if (sorter.columnKey == "credits") {
|
||||||
filterData.orderBy = 'credits'
|
filterData.orderBy = "credits"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sorter.order) {
|
if (sorter.order) {
|
||||||
filterData.order = sorter.order == "descend" ? "Descending" : "Ascending";
|
filterData.order = sorter.order == "descend" ? "Descending" : "Ascending"
|
||||||
} else {
|
} else {
|
||||||
filterData.order = ''
|
filterData.order = ""
|
||||||
|
}
|
||||||
|
gettrialList()
|
||||||
}
|
}
|
||||||
gettrialList();
|
|
||||||
};
|
|
||||||
|
|
||||||
//查询列表
|
//查询列表
|
||||||
let searchHistoryList = () => {
|
let searchHistoryList = () => {
|
||||||
filterData.currentPage = 1;
|
filterData.currentPage = 1
|
||||||
gettrialList();
|
gettrialList()
|
||||||
};
|
}
|
||||||
let clearHistoryList = () => {
|
let clearHistoryList = () => {
|
||||||
filterData.rangePickerValue = [],
|
;((filterData.rangePickerValue = []),
|
||||||
filterData.currentPage = 1,
|
(filterData.currentPage = 1),
|
||||||
filterData.pageSize = 10,
|
(filterData.pageSize = 10),
|
||||||
filterData.total = 0,
|
(filterData.total = 0),
|
||||||
filterData.country = "",
|
(filterData.country = ""),
|
||||||
filterData.email = "",
|
(filterData.email = ""),
|
||||||
filterData.userType = "",
|
(filterData.userType = ""),
|
||||||
filterData.ids = [],
|
(filterData.ids = []),
|
||||||
filterData.occupation = "",
|
(filterData.occupation = ""),
|
||||||
filterData.order = "", //'Ascending 升序 Descending 降序'
|
(filterData.order = ""), //'Ascending 升序 Descending 降序'
|
||||||
filterData.orderBy = "", //'Ascending 升序 Descending 降序'
|
(filterData.orderBy = ""), //'Ascending 升序 Descending 降序'
|
||||||
filterData.systemUser = "",
|
(filterData.systemUser = ""),
|
||||||
filterData.userName = "";
|
(filterData.userName = ""))
|
||||||
};
|
}
|
||||||
let setHistoryListData = () => {
|
let setHistoryListData = () => {
|
||||||
let startDate: any = filterData.rangePickerValue?.[0]
|
let startDate: any = filterData.rangePickerValue?.[0]
|
||||||
? filterData.rangePickerValue[0] + " " + "00:00:00"
|
? filterData.rangePickerValue[0] + " " + "00:00:00"
|
||||||
: "";
|
: ""
|
||||||
let endDate: any = filterData.rangePickerValue?.[1]
|
let endDate: any = filterData.rangePickerValue?.[1]
|
||||||
? filterData.rangePickerValue[1] + " " + "23:59:59"
|
? filterData.rangePickerValue[1] + " " + "23:59:59"
|
||||||
: "";
|
: ""
|
||||||
let data = {
|
let data = {
|
||||||
endTime: endDate,
|
endTime: endDate,
|
||||||
startTime: startDate,
|
startTime: startDate,
|
||||||
@@ -430,63 +428,118 @@ export default defineComponent({
|
|||||||
order: filterData.order,
|
order: filterData.order,
|
||||||
orderBy: filterData.orderBy,
|
orderBy: filterData.orderBy,
|
||||||
userName: filterData.userName,
|
userName: filterData.userName,
|
||||||
};
|
organizationId: filterData.organizationId
|
||||||
return data;
|
}
|
||||||
};
|
return data
|
||||||
|
}
|
||||||
//获取列表
|
//获取列表
|
||||||
let gettrialList = () => {
|
let gettrialList = () => {
|
||||||
filter.tableLoading = true;
|
filter.tableLoading = true
|
||||||
let data = setHistoryListData();
|
let data = setHistoryListData()
|
||||||
Https.axiosPost(Https.httpUrls.getUserInfo, data).then(
|
Https.axiosPost(Https.httpUrls.getUserInfo, data).then((rv: any) => {
|
||||||
(rv: any) => {
|
|
||||||
if (rv) {
|
if (rv) {
|
||||||
// this.dataList = rv
|
// this.dataList = rv
|
||||||
filter.dataList = rv.records;
|
filter.dataList = rv.records
|
||||||
filterData.total = rv.total;
|
filterData.total = rv.total
|
||||||
filter.tableLoading = false;
|
filter.tableLoading = false
|
||||||
|
|
||||||
// this.workspaceItem.position = this.singleTypeList[0].label
|
// this.workspaceItem.position = this.singleTypeList[0].label
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
let lastGeTrialList = (str: string) => {
|
let lastGeTrialList = (str: string) => {
|
||||||
clearHistoryList();
|
clearHistoryList()
|
||||||
let currentDate = new Date();
|
let currentDate = new Date()
|
||||||
let currentTimestamp = Math.floor(currentDate.getTime() / 1000);
|
let currentTimestamp = Math.floor(currentDate.getTime() / 1000)
|
||||||
// 计算30天前的时间戳
|
// 计算30天前的时间戳
|
||||||
let thirtyDaysAgoTimestamp;
|
let thirtyDaysAgoTimestamp
|
||||||
if (str == "year") {
|
if (str == "year") {
|
||||||
thirtyDaysAgoTimestamp = currentTimestamp - 360 * 24 * 60 * 60;
|
thirtyDaysAgoTimestamp = currentTimestamp - 360 * 24 * 60 * 60
|
||||||
} else if (str == "month") {
|
} else if (str == "month") {
|
||||||
thirtyDaysAgoTimestamp = currentTimestamp - 30 * 24 * 60 * 60;
|
thirtyDaysAgoTimestamp = currentTimestamp - 30 * 24 * 60 * 60
|
||||||
} else if (str == "week") {
|
} else if (str == "week") {
|
||||||
thirtyDaysAgoTimestamp = currentTimestamp - 7 * 24 * 60 * 60;
|
thirtyDaysAgoTimestamp = currentTimestamp - 7 * 24 * 60 * 60
|
||||||
|
}
|
||||||
|
filterData.rangePickerValue[0] = formatTime(thirtyDaysAgoTimestamp, "YYYY-MM-DD")
|
||||||
|
gettrialList()
|
||||||
}
|
}
|
||||||
filterData.rangePickerValue[0] = formatTime(
|
|
||||||
thirtyDaysAgoTimestamp,
|
|
||||||
"YYYY-MM-DD"
|
|
||||||
);
|
|
||||||
gettrialList();
|
|
||||||
};
|
|
||||||
let filterOption = (input: any, option: any) => {
|
let filterOption = (input: any, option: any) => {
|
||||||
// 使用 option.label 进行搜索
|
// 使用 option.label 进行搜索
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
};
|
}
|
||||||
let addhHistoryList = () => {
|
let addhHistoryList = () => {
|
||||||
allUserPoerationsVue.value.init('Add','')
|
allUserPoerationsVue.value.init("Add", "")
|
||||||
};
|
}
|
||||||
let allUserPoerationsVue = ref()
|
let allUserPoerationsVue = ref()
|
||||||
let setAagree = (data: any) => {
|
let setAagree = (data: any) => {
|
||||||
allUserPoerationsVue.value.init('Edit',data)
|
allUserPoerationsVue.value.init("Edit", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const organizationOptions = ref([])
|
||||||
|
const organizationParams = reactive({
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
const organizationLoading = ref(false)
|
||||||
|
const getOrganizationList = async (isLoadMore = false) => {
|
||||||
|
console.log("111111")
|
||||||
|
if (organizationLoading.value) return
|
||||||
|
if (isLoadMore) {
|
||||||
|
const loaded = organizationParams.page * organizationParams.size
|
||||||
|
if (organizationParams.total && loaded >= organizationParams.total) return
|
||||||
|
organizationParams.page += 1
|
||||||
|
} else {
|
||||||
|
organizationParams.page = 1
|
||||||
|
organizationOptions.value = []
|
||||||
|
}
|
||||||
|
organizationLoading.value = true
|
||||||
|
try {
|
||||||
|
const { total, ...requestParams } = organizationParams
|
||||||
|
const rv: any = await Https.axiosPost(
|
||||||
|
Https.httpUrls.queryOrganization,
|
||||||
|
requestParams
|
||||||
|
)
|
||||||
|
if (rv) {
|
||||||
|
const newRecords = rv.records || []
|
||||||
|
// 遍历新数据,如果已存在则覆盖,不存在则追加
|
||||||
|
newRecords.forEach((newOrg: any) => {
|
||||||
|
const newOrgId = String(newOrg.id)
|
||||||
|
const existingIndex = organizationOptions.value.findIndex(
|
||||||
|
(org: any) => String(org.id) === newOrgId
|
||||||
|
)
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
// 如果已存在,用新数据覆盖旧项
|
||||||
|
organizationOptions.value[existingIndex] = newOrg
|
||||||
|
} else {
|
||||||
|
// 如果不存在,追加到末尾
|
||||||
|
organizationOptions.value.push(newOrg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
organizationParams.total = rv.total || 0
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
organizationLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOrganizationScroll = (e: any) => {
|
||||||
|
const target = e?.target
|
||||||
|
if (!target) return
|
||||||
|
const nearBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 20
|
||||||
|
if (nearBottom) {
|
||||||
|
getOrganizationList(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let allCountry: any = sessionStorage.getItem("allCountry");
|
let allCountry: any = sessionStorage.getItem("allCountry")
|
||||||
if (allCountry) {
|
if (allCountry) {
|
||||||
filter.allCountry = JSON.parse(allCountry);
|
filter.allCountry = JSON.parse(allCountry)
|
||||||
}
|
}
|
||||||
gettrialList();
|
gettrialList()
|
||||||
});
|
getOrganizationList()
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
...toRefs(filter),
|
...toRefs(filter),
|
||||||
...toRefs(filterData),
|
...toRefs(filterData),
|
||||||
@@ -501,22 +554,26 @@ export default defineComponent({
|
|||||||
filterOption,
|
filterOption,
|
||||||
allUserPoerationsVue,
|
allUserPoerationsVue,
|
||||||
setAagree,
|
setAagree,
|
||||||
};
|
handleOrganizationScroll,
|
||||||
|
getOrganizationList,
|
||||||
|
organizationOptions,
|
||||||
|
organizationParams
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyTableHeight: 0,
|
historyTableHeight: 0,
|
||||||
handleResizeColumn: (w: any, col: any) => {
|
handleResizeColumn: (w: any, col: any) => {
|
||||||
col.width = w;
|
col.width = w
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let historyTable: any = this.$refs.historyTable;
|
let historyTable: any = this.$refs.historyTable
|
||||||
this.historyTableHeight = historyTable.clientHeight - 200;
|
this.historyTableHeight = historyTable.clientHeight - 200
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.admin_page .admin_table_search .admin_state {
|
.admin_page .admin_table_search .admin_state {
|
||||||
@@ -524,6 +581,5 @@ export default defineComponent({
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.admin_page {
|
.admin_page {
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
class="search-form"
|
class="search-form"
|
||||||
layout="inline"
|
layout="inline"
|
||||||
:model="searchForm"
|
:model="searchForm"
|
||||||
:label-col="{ style: { width: '12rem' } }"
|
:label-col="{ style: { width: '14rem' } }"
|
||||||
:wrapper-col="{ style: { width: '22rem' } }"
|
:wrapper-col="{ style: { width: '20rem' } }"
|
||||||
>
|
>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
<a-input v-model:value="searchForm.id" allow-clear placeholder="Input the id" />
|
<a-input v-model:value="searchForm.id" allow-clear placeholder="Input the id" />
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
:options="countryList"
|
:options="countryList"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item class="search-form__actions">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="handleSearch">Search</a-button>
|
<a-button type="primary" @click="handleSearch">Search</a-button>
|
||||||
<a-button @click="handleReset">Reset</a-button>
|
<a-button @click="handleReset">Reset</a-button>
|
||||||
@@ -92,6 +92,7 @@
|
|||||||
:loading="tableLoading"
|
:loading="tableLoading"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
|
:customRow="customPlanRow"
|
||||||
@change="changePage"
|
@change="changePage"
|
||||||
@resizeColumn="handleResizeColumn"
|
@resizeColumn="handleResizeColumn"
|
||||||
:scroll="{ y: historyTableHeight }"
|
:scroll="{ y: historyTableHeight }"
|
||||||
@@ -107,10 +108,11 @@
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
column.key === 'currentPeriodStart' || column.key === 'currentPeriodEnd'
|
column.key === 'currentPeriodStart' ||
|
||||||
|
column.key === 'currentPeriodEnd'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ formatTime(record[column.key], 'YYYY-MM-DD hh:mm:ss') }}
|
{{ formatTime(record[column.key], "YYYY-MM-DD hh:mm:ss") }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="column.key === 'status'">
|
<template v-if="column.key === 'status'">
|
||||||
@@ -120,15 +122,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'actions'">
|
<template v-else-if="column.key === 'actions'">
|
||||||
<a-space>
|
<a-space class="plan-row-actions" @click.stop>
|
||||||
<a @click="openEdit(record)">Edit</a>
|
<a @click.stop="openEdit(record)">Edit</a>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
title="Confirm to delete this subscription plan?"
|
title="Confirm to delete this subscription plan?"
|
||||||
ok-text="Confirm"
|
ok-text="Confirm"
|
||||||
cancel-text="Cancel"
|
cancel-text="Cancel"
|
||||||
@confirm="removePlan(record.id)"
|
@confirm="removePlan(record.id)"
|
||||||
>
|
>
|
||||||
<a class="danger-text">Delete</a>
|
<a class="danger-text" @click.stop>Delete</a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
@@ -137,6 +139,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="userInfoModalVisible"
|
||||||
|
title="User Info"
|
||||||
|
width="80%"
|
||||||
|
:footer="null"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="user-info-modal"
|
||||||
|
>
|
||||||
|
<a-table
|
||||||
|
:columns="userInfoColumns"
|
||||||
|
:data-source="userInfoList"
|
||||||
|
:loading="userInfoLoading"
|
||||||
|
:pagination="{
|
||||||
|
showSizeChanger: true,
|
||||||
|
current: userInfoPage.current,
|
||||||
|
pageSize: userInfoPage.size,
|
||||||
|
total: userInfoPage.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
bordered: false
|
||||||
|
}"
|
||||||
|
row-key="id"
|
||||||
|
:scroll="{ x: 1280, y: 420 }"
|
||||||
|
@change="changeUserInfoPage"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
column.key === 'validStartTime' ||
|
||||||
|
column.key === 'validEndTime' ||
|
||||||
|
column.key === 'createDate'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ formatUserTime(record[column.key]) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'systemUser'">
|
||||||
|
{{ getSystemUserLabel(record.systemUser) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'isBeginner'">
|
||||||
|
{{ record.isBeginner == 1 ? "Yes" : "No" }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
<div class="subscriptionPlanModal" ref="subscriptionPlanModal"></div>
|
<div class="subscriptionPlanModal" ref="subscriptionPlanModal"></div>
|
||||||
<a-modal
|
<a-modal
|
||||||
class="subscriptionPlan_modal generalModel"
|
class="subscriptionPlan_modal generalModel"
|
||||||
@@ -213,7 +259,10 @@
|
|||||||
@select="handleOrganizationSelect"
|
@select="handleOrganizationSelect"
|
||||||
@change="handleOrganizationChange"
|
@change="handleOrganizationChange"
|
||||||
>
|
>
|
||||||
<a-select-option value="ADD_ORGANIZATION" class="add-organization-option">
|
<a-select-option
|
||||||
|
value="ADD_ORGANIZATION"
|
||||||
|
class="add-organization-option"
|
||||||
|
>
|
||||||
+ Create Organization
|
+ Create Organization
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
@@ -393,27 +442,30 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
onMounted,
|
onMounted,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
|
onActivated,
|
||||||
computed,
|
computed,
|
||||||
nextTick,
|
nextTick,
|
||||||
useTemplateRef
|
useTemplateRef
|
||||||
} from 'vue'
|
} from "vue"
|
||||||
import SelectUser from '@/component/common/SelectUser.vue'
|
import SelectUser from "@/component/common/SelectUser.vue"
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from "ant-design-vue"
|
||||||
import { Https } from '@/tool/https'
|
import { Https } from "@/tool/https"
|
||||||
import { formatTime } from '@/tool/util'
|
import { formatTime } from "@/tool/util"
|
||||||
import store from '@/store'
|
import store from "@/store"
|
||||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form'
|
import type { FormInstance, Rule } from "ant-design-vue/es/form"
|
||||||
import { debounce } from 'lodash-es'
|
import { debounce } from "lodash-es"
|
||||||
import dayjs, { Dayjs } from 'dayjs'
|
import dayjs, { Dayjs } from "dayjs"
|
||||||
|
|
||||||
type PlanStatus = 'PENDING' | 'ACTIVE' | 'EXPIRED'
|
type PlanStatus = "PENDING" | "ACTIVE" | "EXPIRED"
|
||||||
interface SubscriptionPlan {
|
interface SubscriptionPlan {
|
||||||
id: number
|
id: number
|
||||||
|
userId?: string | number
|
||||||
name: string
|
name: string
|
||||||
currentPeriodStart: string
|
currentPeriodStart: string
|
||||||
currentPeriodEnd: string
|
currentPeriodEnd: string
|
||||||
organizationId: string
|
organizationId: string
|
||||||
adminAccId: string
|
adminAccId: string
|
||||||
|
adminAccEmail?: string
|
||||||
status: PlanStatus
|
status: PlanStatus
|
||||||
creditLimit: number
|
creditLimit: number
|
||||||
accountNum?: number
|
accountNum?: number
|
||||||
@@ -421,17 +473,32 @@ interface SubscriptionPlan {
|
|||||||
endStamp: number
|
endStamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserInfoRecord {
|
||||||
|
id: number
|
||||||
|
userEmail?: string
|
||||||
|
userName?: string
|
||||||
|
language?: string
|
||||||
|
validStartTime?: number | string
|
||||||
|
validEndTime?: number | string
|
||||||
|
country?: string
|
||||||
|
createDate?: number | string
|
||||||
|
isBeginner?: number
|
||||||
|
browserIdentifiers?: string
|
||||||
|
credits?: number
|
||||||
|
systemUser?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
const countryList = ref([])
|
const countryList = ref([])
|
||||||
const userRef = ref(null)
|
const userRef = ref(null)
|
||||||
|
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
name: '',
|
name: "",
|
||||||
startTime: '',
|
startTime: "",
|
||||||
endTime: '',
|
endTime: "",
|
||||||
organizationId: undefined as string | undefined,
|
organizationId: undefined as string | undefined,
|
||||||
adminAccId: undefined as string | undefined,
|
adminAccId: undefined as string | undefined,
|
||||||
status: [] as PlanStatus[] | [],
|
status: [] as PlanStatus[] | [],
|
||||||
id: '',
|
id: "",
|
||||||
countryOrRegion: null,
|
countryOrRegion: null,
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
@@ -442,15 +509,24 @@ const toSeconds = (dateStr: string) => Math.floor(new Date(dateStr).getTime() /
|
|||||||
|
|
||||||
const tableData = ref<SubscriptionPlan[]>([])
|
const tableData = ref<SubscriptionPlan[]>([])
|
||||||
const tableLoading = ref(false)
|
const tableLoading = ref(false)
|
||||||
|
const userInfoModalVisible = ref(false)
|
||||||
|
const userInfoLoading = ref(false)
|
||||||
|
const userInfoList = ref<UserInfoRecord[]>([])
|
||||||
|
const currentUserInfoPlanId = ref<string | number | null>(null)
|
||||||
|
const userInfoPage = reactive({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
const confirmLoading = ref(false)
|
const confirmLoading = ref(false)
|
||||||
const modalTitle = ref('New Subscription Plan')
|
const modalTitle = ref("New Subscription Plan")
|
||||||
const isEditMode = ref(false)
|
const isEditMode = ref(false)
|
||||||
const formState = reactive({
|
const formState = reactive({
|
||||||
name: '',
|
name: "",
|
||||||
currentPeriodStart: '',
|
currentPeriodStart: "",
|
||||||
currentPeriodEnd: '',
|
currentPeriodEnd: "",
|
||||||
organizationId: undefined as string | undefined,
|
organizationId: undefined as string | undefined,
|
||||||
adminAccId: undefined as string | undefined,
|
adminAccId: undefined as string | undefined,
|
||||||
creditLimit: null as number | null,
|
creditLimit: null as number | null,
|
||||||
@@ -461,44 +537,44 @@ const formState = reactive({
|
|||||||
|
|
||||||
const organizationModalVisible = ref(false)
|
const organizationModalVisible = ref(false)
|
||||||
const organizationForm = reactive({
|
const organizationForm = reactive({
|
||||||
name: '',
|
name: "",
|
||||||
type: undefined as string | undefined
|
type: undefined as string | undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusLabelMap: Record<PlanStatus, string> = {
|
const statusLabelMap: Record<PlanStatus, string> = {
|
||||||
PENDING: 'Pending',
|
PENDING: "Pending",
|
||||||
ACTIVE: 'Active',
|
ACTIVE: "Active",
|
||||||
EXPIRED: 'Expired'
|
EXPIRED: "Expired"
|
||||||
}
|
}
|
||||||
const statusColorMap: Record<PlanStatus, string> = {
|
const statusColorMap: Record<PlanStatus, string> = {
|
||||||
PENDING: 'blue',
|
PENDING: "blue",
|
||||||
ACTIVE: 'green',
|
ACTIVE: "green",
|
||||||
EXPIRED: 'red'
|
EXPIRED: "red"
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusOption = ref([
|
const statusOption = ref([
|
||||||
{
|
{
|
||||||
label: 'Pending',
|
label: "Pending",
|
||||||
value: 'PENDING'
|
value: "PENDING"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Active',
|
label: "Active",
|
||||||
value: 'ACTIVE'
|
value: "ACTIVE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Expired',
|
label: "Expired",
|
||||||
value: 'EXPIRED'
|
value: "EXPIRED"
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const disabledDate = (current: Dayjs) => {
|
const disabledDate = (current: Dayjs) => {
|
||||||
return current && current < dayjs().subtract(1, 'days').endOf('day')
|
return current && current < dayjs().subtract(1, "days").endOf("day")
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableEndDate = (current: Dayjs) => {
|
const disableEndDate = (current: Dayjs) => {
|
||||||
if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
const specificTime = dayjs(formState.currentPeriodEnd)
|
const specificTime = dayjs(formState.currentPeriodEnd)
|
||||||
return current && current < dayjs(formState.currentPeriodEnd * 1000).startOf('day')
|
return current && current < dayjs(formState.currentPeriodEnd * 1000).startOf("day")
|
||||||
}
|
}
|
||||||
return disabledDate(current)
|
return disabledDate(current)
|
||||||
}
|
}
|
||||||
@@ -509,7 +585,7 @@ const range = (start: number, end: number) => {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
const disableEndTime = date => {
|
const disableEndTime = (date) => {
|
||||||
if (!formState.currentPeriodEnd || !isEditMode.value)
|
if (!formState.currentPeriodEnd || !isEditMode.value)
|
||||||
return {
|
return {
|
||||||
disabledHours: () => [],
|
disabledHours: () => [],
|
||||||
@@ -519,7 +595,7 @@ const disableEndTime = date => {
|
|||||||
|
|
||||||
const specificTime = dayjs.unix(formState.currentPeriodEnd)
|
const specificTime = dayjs.unix(formState.currentPeriodEnd)
|
||||||
|
|
||||||
if (date && date.isSame(specificTime, 'day')) {
|
if (date && date.isSame(specificTime, "day")) {
|
||||||
// 如果是指定日期当天,禁用时间戳之前的时间
|
// 如果是指定日期当天,禁用时间戳之前的时间
|
||||||
const hour = specificTime.hour()
|
const hour = specificTime.hour()
|
||||||
const minute = specificTime.minute()
|
const minute = specificTime.minute()
|
||||||
@@ -527,7 +603,7 @@ const disableEndTime = date => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
disabledHours: () => Array.from({ length: hour }, (_, i) => i), // 禁用小时之前
|
disabledHours: () => Array.from({ length: hour }, (_, i) => i), // 禁用小时之前
|
||||||
disabledMinutes: selectedHour => {
|
disabledMinutes: (selectedHour) => {
|
||||||
if (selectedHour === hour) {
|
if (selectedHour === hour) {
|
||||||
return Array.from({ length: minute }, (_, i) => i) // 同小时,禁用分钟之前
|
return Array.from({ length: minute }, (_, i) => i) // 同小时,禁用分钟之前
|
||||||
}
|
}
|
||||||
@@ -555,114 +631,328 @@ const normalizeStatus = (status?: string): PlanStatus | undefined => {
|
|||||||
return upper
|
return upper
|
||||||
}
|
}
|
||||||
const getStatusColor = (status?: string) =>
|
const getStatusColor = (status?: string) =>
|
||||||
statusColorMap[normalizeStatus(status) as PlanStatus] || 'default'
|
statusColorMap[normalizeStatus(status) as PlanStatus] || "default"
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: 'Name', dataIndex: 'name', key: 'name', align: 'center', width: 180 },
|
{ title: "Name", dataIndex: "name", key: "name", align: "center", width: 180 },
|
||||||
{ title: 'ID', dataIndex: 'id', key: 'id', align: 'center', width: 80 },
|
{ title: "ID", dataIndex: "id", key: "id", align: "center", width: 80 },
|
||||||
{
|
{
|
||||||
title: 'Organization',
|
title: "Organization",
|
||||||
dataIndex: 'organizationName',
|
dataIndex: "organizationName",
|
||||||
key: 'organizationName',
|
key: "organizationName",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 180
|
width: 180
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Admin Account',
|
title: "Admin Account",
|
||||||
dataIndex: 'adminAccEmail',
|
dataIndex: "adminAccEmail",
|
||||||
key: 'adminAccEmail',
|
key: "adminAccEmail",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 180,
|
width: 180,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Sub-Account Num',
|
title: "Sub-Account Num",
|
||||||
dataIndex: 'accountNum',
|
dataIndex: "accountNum",
|
||||||
key: 'accountNum',
|
key: "accountNum",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 120,
|
width: 120,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Country or Region',
|
title: "Country or Region",
|
||||||
dataIndex: 'countryOrRegion',
|
dataIndex: "countryOrRegion",
|
||||||
key: 'countryOrRegion',
|
key: "countryOrRegion",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 120,
|
width: 120,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Start Time',
|
title: "Start Time",
|
||||||
dataIndex: 'currentPeriodStart',
|
dataIndex: "currentPeriodStart",
|
||||||
key: 'currentPeriodStart',
|
key: "currentPeriodStart",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 200
|
width: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'End Time',
|
title: "End Time",
|
||||||
dataIndex: 'currentPeriodEnd',
|
dataIndex: "currentPeriodEnd",
|
||||||
key: 'currentPeriodEnd',
|
key: "currentPeriodEnd",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 200
|
width: 200
|
||||||
},
|
},
|
||||||
{ title: 'Status', dataIndex: 'status', key: 'status', align: 'center', width: 100 },
|
{ title: "Status", dataIndex: "status", key: "status", align: "center", width: 100 },
|
||||||
{
|
{
|
||||||
title: 'Credit Limit',
|
title: "Credit Limit",
|
||||||
dataIndex: 'creditLimit',
|
dataIndex: "creditLimit",
|
||||||
key: 'creditLimit',
|
key: "creditLimit",
|
||||||
align: 'center',
|
align: "center",
|
||||||
width: 120
|
width: 120
|
||||||
},
|
},
|
||||||
{ title: 'Operations', key: 'actions', width: 160, align: 'center', fixed: 'right' }
|
{ title: "Operations", key: "actions", width: 160, align: "center", fixed: "right" }
|
||||||
]
|
]
|
||||||
|
|
||||||
const historyTable = ref()
|
const userInfoColumns = [
|
||||||
|
{ title: "User Id", dataIndex: "id", key: "id", align: "center", width: 100 },
|
||||||
|
{
|
||||||
|
title: "Email",
|
||||||
|
dataIndex: "userEmail",
|
||||||
|
key: "userEmail",
|
||||||
|
align: "center",
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User Name",
|
||||||
|
dataIndex: "userName",
|
||||||
|
key: "userName",
|
||||||
|
align: "center",
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Valid Start Time",
|
||||||
|
dataIndex: "validStartTime",
|
||||||
|
key: "validStartTime",
|
||||||
|
align: "center",
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Valid End Time",
|
||||||
|
dataIndex: "validEndTime",
|
||||||
|
key: "validEndTime",
|
||||||
|
align: "center",
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Country or Region",
|
||||||
|
dataIndex: "country",
|
||||||
|
key: "country",
|
||||||
|
align: "center",
|
||||||
|
width: 180,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Credits",
|
||||||
|
dataIndex: "credits",
|
||||||
|
key: "credits",
|
||||||
|
align: "center",
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User Type",
|
||||||
|
dataIndex: "systemUser",
|
||||||
|
key: "systemUser",
|
||||||
|
align: "center",
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const systemUserLabelMap: Record<string, string> = {
|
||||||
|
"0": "visitor",
|
||||||
|
"1": "yearly",
|
||||||
|
"2": "monthly",
|
||||||
|
"3": "trial",
|
||||||
|
"4": "userInEvent",
|
||||||
|
"7": "Edu Admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSystemUserLabel = (systemUser?: number | string) => {
|
||||||
|
if (systemUser === undefined || systemUser === null) return ""
|
||||||
|
return systemUserLabelMap[String(systemUser)] || String(systemUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatUserTime = (value?: number | string) => {
|
||||||
|
if (!value) return ""
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return formatTime(value / 1000, "YYYY-MM-DD hh:mm:ss")
|
||||||
|
}
|
||||||
|
if (/^\d+$/.test(value)) {
|
||||||
|
return formatTime(Number(value) / 1000, "YYYY-MM-DD hh:mm:ss")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeUserInfoList = (res: any): UserInfoRecord[] => {
|
||||||
|
if (Array.isArray(res)) return res
|
||||||
|
if (Array.isArray(res?.records)) return res.records
|
||||||
|
if (res) return [res]
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildUserInfoParams = (id: string | number) => ({
|
||||||
|
endTime: "",
|
||||||
|
startTime: "",
|
||||||
|
size: userInfoPage.size,
|
||||||
|
page: userInfoPage.current,
|
||||||
|
systemUser: "",
|
||||||
|
country: "",
|
||||||
|
email: "",
|
||||||
|
userType: "",
|
||||||
|
ids: [],
|
||||||
|
occupation: "",
|
||||||
|
order: "",
|
||||||
|
orderBy: "",
|
||||||
|
userName: "",
|
||||||
|
subscriptionPlanId: id
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchUserInfo = async () => {
|
||||||
|
if (currentUserInfoPlanId.value === null) return
|
||||||
|
userInfoLoading.value = true
|
||||||
|
userInfoList.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await Https.axiosPost(
|
||||||
|
Https.httpUrls.getUserInfo,
|
||||||
|
buildUserInfoParams(currentUserInfoPlanId.value)
|
||||||
|
)
|
||||||
|
const records = normalizeUserInfoList(res)
|
||||||
|
userInfoList.value = records
|
||||||
|
userInfoPage.total = Number(res?.total ?? records.length)
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || "Failed to load user info")
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
userInfoLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUserInfo = async (record: SubscriptionPlan) => {
|
||||||
|
console.log(record)
|
||||||
|
// debugger
|
||||||
|
|
||||||
|
currentUserInfoPlanId.value = record.id
|
||||||
|
userInfoPage.current = 1
|
||||||
|
userInfoModalVisible.value = true
|
||||||
|
await fetchUserInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeUserInfoPage = (pagination: any) => {
|
||||||
|
userInfoPage.current = pagination.current
|
||||||
|
userInfoPage.size = pagination.pageSize
|
||||||
|
fetchUserInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const customPlanRow = (record: SubscriptionPlan) => {
|
||||||
|
return {
|
||||||
|
onClick: (event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement | null
|
||||||
|
if (target?.closest(".plan-row-actions")) return
|
||||||
|
openUserInfo(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyTable = ref<HTMLElement | null>(null)
|
||||||
const historyTableHeight = ref(0)
|
const historyTableHeight = ref(0)
|
||||||
|
const minTableBodyHeight = 120
|
||||||
|
let tableResizeObserver: ResizeObserver | null = null
|
||||||
|
let tableResizeTimer: ReturnType<typeof window.setTimeout> | null = null
|
||||||
|
|
||||||
const handleResizeColumn = (w: any, col: any) => {
|
const handleResizeColumn = (w: any, col: any) => {
|
||||||
col.width = w
|
col.width = w
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateTableHeight = () => {
|
const getElementOuterHeight = (element: Element | null) => {
|
||||||
nextTick(() => {
|
if (!element) return 0
|
||||||
if (historyTable.value) {
|
const htmlElement = element as HTMLElement
|
||||||
historyTableHeight.value = historyTable.value.clientHeight - 200
|
const style = window.getComputedStyle(htmlElement)
|
||||||
|
return (
|
||||||
|
htmlElement.offsetHeight +
|
||||||
|
Number.parseFloat(style.marginTop || "0") +
|
||||||
|
Number.parseFloat(style.marginBottom || "0")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calculateTableHeight = () => {
|
||||||
|
if (tableResizeTimer) {
|
||||||
|
window.clearTimeout(tableResizeTimer)
|
||||||
|
}
|
||||||
|
tableResizeTimer = window.setTimeout(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
const tableWrapper = historyTable.value
|
||||||
|
if (!tableWrapper) {
|
||||||
|
tableResizeTimer = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableHead = tableWrapper.querySelector(".ant-table-thead") ?? null
|
||||||
|
const pagination = tableWrapper.querySelector(".ant-pagination") ?? null
|
||||||
|
const tableHeadHeight = getElementOuterHeight(tableHead) || 55
|
||||||
|
const paginationHeight = getElementOuterHeight(pagination) || 48
|
||||||
|
const reservedHeight = tableHeadHeight + paginationHeight + 8
|
||||||
|
|
||||||
|
historyTableHeight.value = Math.max(
|
||||||
|
minTableBodyHeight,
|
||||||
|
tableWrapper.clientHeight - reservedHeight
|
||||||
|
)
|
||||||
|
tableResizeTimer = null
|
||||||
})
|
})
|
||||||
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
calculateTableHeight()
|
calculateTableHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupTableResizeObserver = () => {
|
||||||
|
if (!historyTable.value || typeof ResizeObserver === "undefined") return
|
||||||
|
tableResizeObserver?.disconnect()
|
||||||
|
tableResizeObserver = new ResizeObserver(() => {
|
||||||
|
calculateTableHeight()
|
||||||
|
})
|
||||||
|
tableResizeObserver.observe(historyTable.value)
|
||||||
|
|
||||||
|
const searchCard = historyTable.value
|
||||||
|
.closest(".subscription-plan")
|
||||||
|
?.querySelector(".search-card")
|
||||||
|
if (searchCard) {
|
||||||
|
tableResizeObserver.observe(searchCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getOrganizationList()
|
await getOrganizationList()
|
||||||
await handleSearch()
|
await handleSearch()
|
||||||
calculateTableHeight()
|
calculateTableHeight()
|
||||||
window.addEventListener('resize', handleResize)
|
setupTableResizeObserver()
|
||||||
const list = sessionStorage.getItem('allCountry')
|
window.addEventListener("resize", handleResize)
|
||||||
|
const list = sessionStorage.getItem("allCountry")
|
||||||
countryList.value = list ? JSON.parse(list) : []
|
countryList.value = list ? JSON.parse(list) : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
calculateTableHeight()
|
||||||
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener("resize", handleResize)
|
||||||
|
tableResizeObserver?.disconnect()
|
||||||
|
if (tableResizeTimer) {
|
||||||
|
window.clearTimeout(tableResizeTimer)
|
||||||
|
tableResizeTimer = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleFetchTableData = async () => {
|
const handleFetchTableData = async () => {
|
||||||
tableLoading.value = true
|
tableLoading.value = true
|
||||||
return Https.axiosPost(Https.httpUrls.searchAllSubscribePlan, searchForm)
|
return Https.axiosPost(Https.httpUrls.searchAllSubscribePlan, searchForm)
|
||||||
.then(res => {
|
.then((res) => {
|
||||||
tableData.value = res.records
|
tableData.value = res.records
|
||||||
searchForm.total = res.total
|
searchForm.total = res.total
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
tableLoading.value = false
|
tableLoading.value = false
|
||||||
|
calculateTableHeight()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetFormState = () => {
|
const resetFormState = () => {
|
||||||
formState.name = ''
|
formState.name = ""
|
||||||
formState.currentPeriodStart = ''
|
formState.currentPeriodStart = ""
|
||||||
formState.currentPeriodEnd = ''
|
formState.currentPeriodEnd = ""
|
||||||
formState.organizationId = undefined
|
formState.organizationId = undefined
|
||||||
formState.adminAccId = undefined
|
formState.adminAccId = undefined
|
||||||
formState.creditLimit = null
|
formState.creditLimit = null
|
||||||
@@ -683,26 +973,26 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
searchForm.name = ''
|
searchForm.name = ""
|
||||||
searchForm.startTime = ''
|
searchForm.startTime = ""
|
||||||
searchForm.endTime = ''
|
searchForm.endTime = ""
|
||||||
searchForm.organizationId = undefined
|
searchForm.organizationId = undefined
|
||||||
searchForm.adminAccId = undefined
|
searchForm.adminAccId = undefined
|
||||||
searchForm.status = []
|
searchForm.status = []
|
||||||
searchForm.id = ''
|
searchForm.id = ""
|
||||||
searchForm.countryOrRegion = null
|
searchForm.countryOrRegion = null
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
modalTitle.value = 'New Subscription Plan'
|
modalTitle.value = "New Subscription Plan"
|
||||||
isEditMode.value = false
|
isEditMode.value = false
|
||||||
resetFormState()
|
resetFormState()
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const openEdit = (record: SubscriptionPlan) => {
|
const openEdit = (record: SubscriptionPlan) => {
|
||||||
modalTitle.value = 'Edit Subscription Plan'
|
modalTitle.value = "Edit Subscription Plan"
|
||||||
isEditMode.value = true
|
isEditMode.value = true
|
||||||
formState.name = record.name
|
formState.name = record.name
|
||||||
formState.currentPeriodStart = String(record.currentPeriodStart)
|
formState.currentPeriodStart = String(record.currentPeriodStart)
|
||||||
@@ -755,25 +1045,25 @@ const validateForm = (): boolean => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requiredFields: FieldRule[] = [
|
const requiredFields: FieldRule[] = [
|
||||||
{ value: formState.currentPeriodStart, message: 'Please select the start time' },
|
{ value: formState.currentPeriodStart, message: "Please select the start time" },
|
||||||
{ value: formState.currentPeriodEnd, message: 'Please select the end time' },
|
{ value: formState.currentPeriodEnd, message: "Please select the end time" },
|
||||||
{ value: formState.adminAccId, message: 'Please select the admin account' },
|
{ value: formState.adminAccId, message: "Please select the admin account" },
|
||||||
{
|
{
|
||||||
value: formState.creditLimit,
|
value: formState.creditLimit,
|
||||||
message: 'Please input credit limit',
|
message: "Please input credit limit",
|
||||||
checkNull: true
|
checkNull: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: formState.accountNum,
|
value: formState.accountNum,
|
||||||
message: 'Please input account number',
|
message: "Please input account number",
|
||||||
checkNull: true
|
checkNull: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!isEditMode.value) {
|
if (!isEditMode.value) {
|
||||||
requiredFields.push(
|
requiredFields.push(
|
||||||
{ value: formState.name, message: 'Please input the name' },
|
{ value: formState.name, message: "Please input the name" },
|
||||||
{ value: formState.organizationId, message: 'Please select organization' }
|
{ value: formState.organizationId, message: "Please select organization" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,7 +1096,7 @@ const handleSubmit = async () => {
|
|||||||
res = await Https.axiosPost(Https.httpUrls.createSubscribePlan, params)
|
res = await Https.axiosPost(Https.httpUrls.createSubscribePlan, params)
|
||||||
}
|
}
|
||||||
message.success(
|
message.success(
|
||||||
`${isEditMode.value ? 'Subscription plan updated' : 'Subscription plan created'}`
|
`${isEditMode.value ? "Subscription plan updated" : "Subscription plan created"}`
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error.message)
|
message.error(error.message)
|
||||||
@@ -835,8 +1125,8 @@ const cancelModal = () => {
|
|||||||
const removePlan = (id: number) => {
|
const removePlan = (id: number) => {
|
||||||
tableLoading.value = true
|
tableLoading.value = true
|
||||||
Https.axiosGet(Https.httpUrls.deleteSubscribePlan, { params: { id } })
|
Https.axiosGet(Https.httpUrls.deleteSubscribePlan, { params: { id } })
|
||||||
.then(res => {
|
.then((res) => {
|
||||||
message.success('Subscription plan deleted')
|
message.success("Subscription plan deleted")
|
||||||
handleReset()
|
handleReset()
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -901,15 +1191,15 @@ const handleOrganizationScroll = (e: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOrganizationSelect = (value: string) => {
|
const handleOrganizationSelect = (value: string) => {
|
||||||
if (value === 'ADD_ORGANIZATION') {
|
if (value === "ADD_ORGANIZATION") {
|
||||||
// 打开添加组织弹窗
|
// 打开添加组织弹窗
|
||||||
organizationModalVisible.value = true
|
organizationModalVisible.value = true
|
||||||
// 使用nextTick确保值被重置,使其不被选中
|
// 使用nextTick确保值被重置,使其不被选中
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (searchForm.organizationId === 'ADD_ORGANIZATION') {
|
if (searchForm.organizationId === "ADD_ORGANIZATION") {
|
||||||
searchForm.organizationId = undefined
|
searchForm.organizationId = undefined
|
||||||
}
|
}
|
||||||
if (formState.organizationId === 'ADD_ORGANIZATION') {
|
if (formState.organizationId === "ADD_ORGANIZATION") {
|
||||||
formState.organizationId = undefined
|
formState.organizationId = undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -918,12 +1208,12 @@ const handleOrganizationSelect = (value: string) => {
|
|||||||
|
|
||||||
const handleOrganizationChange = (value: string) => {
|
const handleOrganizationChange = (value: string) => {
|
||||||
// 如果change事件触发时值是"添加组织",立即重置
|
// 如果change事件触发时值是"添加组织",立即重置
|
||||||
if (value === 'ADD_ORGANIZATION') {
|
if (value === "ADD_ORGANIZATION") {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (searchForm.organizationId === 'ADD_ORGANIZATION') {
|
if (searchForm.organizationId === "ADD_ORGANIZATION") {
|
||||||
searchForm.organizationId = undefined
|
searchForm.organizationId = undefined
|
||||||
}
|
}
|
||||||
if (formState.organizationId === 'ADD_ORGANIZATION') {
|
if (formState.organizationId === "ADD_ORGANIZATION") {
|
||||||
formState.organizationId = undefined
|
formState.organizationId = undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -932,13 +1222,13 @@ const handleOrganizationChange = (value: string) => {
|
|||||||
|
|
||||||
const cancelOrganizationModal = () => {
|
const cancelOrganizationModal = () => {
|
||||||
organizationModalVisible.value = false
|
organizationModalVisible.value = false
|
||||||
organizationForm.name = ''
|
organizationForm.name = ""
|
||||||
organizationForm.type = undefined
|
organizationForm.type = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateOrganization = async () => {
|
const handleCreateOrganization = async () => {
|
||||||
if (!organizationForm.name || !organizationForm.type) {
|
if (!organizationForm.name || !organizationForm.type) {
|
||||||
message.warning('Please fill in name and type')
|
message.warning("Please fill in name and type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -948,7 +1238,7 @@ const handleCreateOrganization = async () => {
|
|||||||
type: organizationForm.type
|
type: organizationForm.type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
message.success('Organization created successfully')
|
message.success("Organization created successfully")
|
||||||
cancelOrganizationModal()
|
cancelOrganizationModal()
|
||||||
// 刷新组织列表
|
// 刷新组织列表
|
||||||
await getOrganizationList()
|
await getOrganizationList()
|
||||||
@@ -960,26 +1250,28 @@ const handleCreateOrganization = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error.message || 'Failed to create organization')
|
message.error(error.message || "Failed to create organization")
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterOption = (input: string, option: any) => {
|
const filterOption = (input: string, option: any) => {
|
||||||
const label = option?.label ?? option?.children ?? option?.key?.label ?? ''
|
const label = option?.label ?? option?.children ?? option?.key?.label ?? ""
|
||||||
return String(label).toLowerCase().includes(input.toLowerCase())
|
return String(label).toLowerCase().includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.subscription-plan {
|
.subscription-plan {
|
||||||
padding: 2rem 2.4rem 3.2rem 0;
|
padding: 2rem 2.4rem 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.search-card {
|
.search-card {
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-card {
|
.table-card {
|
||||||
@@ -987,13 +1279,15 @@ const filterOption = (input: string, option: any) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
:deep(.ant-card-body) {
|
:deep(.ant-card-body) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 2.4rem;
|
padding: 2.4rem 2.4rem 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-card__header {
|
.table-card__header {
|
||||||
@@ -1007,6 +1301,42 @@ const filterOption = (input: string, option: any) => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
||||||
|
:deep(.ant-table-wrapper),
|
||||||
|
:deep(.ant-spin-nested-loading),
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table) {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-container) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-content) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-body) {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-pagination) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-text {
|
.danger-text {
|
||||||
@@ -1026,8 +1356,23 @@ const filterOption = (input: string, option: any) => {
|
|||||||
:deep(.ant-table-tbody > tr:hover > td) {
|
:deep(.ant-table-tbody > tr:hover > td) {
|
||||||
background: rgb(202, 202, 202);
|
background: rgb(202, 202, 202);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-tbody > tr) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.plan-row-actions) {
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-modal {
|
||||||
|
:deep(.ant-modal-body) {
|
||||||
|
padding: 2.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.subscriptionPlan_modal) {
|
:deep(.subscriptionPlan_modal) {
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
// height: calc(65rem * 1.2);
|
// height: calc(65rem * 1.2);
|
||||||
@@ -1079,11 +1424,69 @@ const filterOption = (input: string, option: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.search-form) {
|
:deep(.search-form) {
|
||||||
column-gap: 2rem;
|
--search-label-width: 14rem;
|
||||||
row-gap: 2rem;
|
--search-control-width: 20rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
column-gap: 1.8rem;
|
||||||
|
row-gap: 1.8rem;
|
||||||
|
|
||||||
|
.ant-form-item {
|
||||||
|
min-width: calc(var(--search-label-width) + var(--search-control-width));
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-label {
|
||||||
|
flex: 0 0 var(--search-label-width);
|
||||||
|
max-width: var(--search-label-width);
|
||||||
|
overflow: visible;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-control {
|
||||||
|
flex: 0 0 var(--search-control-width);
|
||||||
|
max-width: var(--search-control-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input,
|
||||||
|
.ant-input-affix-wrapper,
|
||||||
|
.ant-picker,
|
||||||
.ant-select {
|
.ant-select {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-form__actions {
|
||||||
|
min-width: auto;
|
||||||
|
|
||||||
|
.ant-form-item-control {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1600px) {
|
||||||
|
:deep(.search-form) {
|
||||||
|
--search-control-width: 22rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
:deep(.search-form) {
|
||||||
|
.ant-form-item {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-control {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: calc(100% - var(--search-label-width));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-select-dropdown) {
|
:deep(.ant-select-dropdown) {
|
||||||
|
|||||||
@@ -1737,7 +1737,7 @@ export default {
|
|||||||
GetStarted: '开始体验',
|
GetStarted: '开始体验',
|
||||||
},
|
},
|
||||||
SellerListEdit:{
|
SellerListEdit:{
|
||||||
saveDraft: "保存草稿",
|
saveDraft: "全部保存",
|
||||||
publish: "发布",
|
publish: "发布",
|
||||||
sketch: "线稿图",
|
sketch: "线稿图",
|
||||||
mainProductImage: "产品主图",
|
mainProductImage: "产品主图",
|
||||||
|
|||||||
@@ -1788,7 +1788,7 @@ export default {
|
|||||||
GetStarted: 'Get Started',
|
GetStarted: 'Get Started',
|
||||||
},
|
},
|
||||||
SellerListEdit:{
|
SellerListEdit:{
|
||||||
saveDraft:'Save Draft',
|
saveDraft:'Save All New',
|
||||||
publish:'Publish',
|
publish:'Publish',
|
||||||
sketch:'Sketch',
|
sketch:'Sketch',
|
||||||
mainProductImage:'Main Product Image',
|
mainProductImage:'Main Product Image',
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ const open = (url, callback, options, origin) => {
|
|||||||
coverOrigin.value = origin
|
coverOrigin.value = origin
|
||||||
data.url = origin[0].url
|
data.url = origin[0].url
|
||||||
}
|
}
|
||||||
console.log("-------", origin)
|
|
||||||
show.value = true
|
show.value = true
|
||||||
}
|
}
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
|
|||||||
@@ -204,23 +204,23 @@
|
|||||||
.cropper-box-canvas {
|
.cropper-box-canvas {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|
||||||
img {
|
// img {
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.is-cover {
|
&.is-cover {
|
||||||
:deep(.vue-cropper) {
|
:deep(.vue-cropper) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
:deep(.cropper-box-canvas) {
|
// :deep(.cropper-box-canvas) {
|
||||||
width: 31.1rem !important;
|
// width: 31.1rem !important;
|
||||||
left: 50% !important;
|
// left: 50% !important;
|
||||||
transform: translateX(-50%) !important;
|
// transform: translateX(-50%) !important;
|
||||||
img {
|
// img {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { ListingItem } from "../types"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
sketchList: ListingItem["sketchList"]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "crop", data: string | null, type: "apparel", index?: number): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="apparel-container">
|
||||||
|
<div class="title">
|
||||||
|
<span class="main-title">{{ $t("SellerListEdit.apparelSketchTitle") }}</span>
|
||||||
|
<span class="sub-title">{{ $t("SellerListEdit.apparelSketchSubTitle") }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sketch-list-container flex">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in sketchList"
|
||||||
|
:key="index"
|
||||||
|
class="sketch-element flex flex-center"
|
||||||
|
>
|
||||||
|
<img class="img-src" :src="item.url || ''" alt="" />
|
||||||
|
<div class="crop-tool flex flex-center" @click="emit('crop', item.url, 'apparel', index)">
|
||||||
|
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.apparel-container {
|
||||||
|
margin-top: 3rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-tool {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.4rem;
|
||||||
|
right: 0.4rem;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Radio from "./Radio.vue"
|
||||||
|
import type { RadioOption } from "../types"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
productName: string
|
||||||
|
price: string
|
||||||
|
desc: string
|
||||||
|
gender: string
|
||||||
|
category: string[] | null
|
||||||
|
genderOptions: RadioOption[]
|
||||||
|
categoryOptions: RadioOption[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:productName", value: string): void
|
||||||
|
(e: "update:price", value: string): void
|
||||||
|
(e: "update:desc", value: string): void
|
||||||
|
(e: "update:gender", value: string): void
|
||||||
|
(e: "update:category", value: string[] | null): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="form-container flex flex-col">
|
||||||
|
<div class="form-item">
|
||||||
|
<div class="form-item-label required">
|
||||||
|
{{ $t("SellerListEdit.productName") }}
|
||||||
|
</div>
|
||||||
|
<div class="form-item-value product-name">
|
||||||
|
<a-input
|
||||||
|
:value="productName"
|
||||||
|
show-count
|
||||||
|
placeholder="Enter product name"
|
||||||
|
:bordered="false"
|
||||||
|
:maxlength="60"
|
||||||
|
@update:value="emit('update:productName', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<div class="form-item-label required">{{ $t("SellerListEdit.price") }}</div>
|
||||||
|
<div class="form-item-value price flex align-center">
|
||||||
|
<span>HK$</span>
|
||||||
|
<a-input
|
||||||
|
:value="price"
|
||||||
|
placeholder="0.00"
|
||||||
|
:bordered="false"
|
||||||
|
@update:value="emit('update:price', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<div class="form-item-label required">
|
||||||
|
{{ $t("SellerListEdit.productDescription") }}
|
||||||
|
</div>
|
||||||
|
<div class="form-item-value desc">
|
||||||
|
<a-textarea
|
||||||
|
:value="desc"
|
||||||
|
show-count
|
||||||
|
:rows="4"
|
||||||
|
placeholder="Enter product description"
|
||||||
|
:bordered="false"
|
||||||
|
:maxlength="500"
|
||||||
|
@update:value="emit('update:desc', $event)"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
:model-value="gender"
|
||||||
|
@update:model-value="emit('update:gender', $event)"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
multiple
|
||||||
|
:options="categoryOptions"
|
||||||
|
:model-value="category"
|
||||||
|
@update:model-value="emit('update:category', $event)"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.required {
|
||||||
|
&::after {
|
||||||
|
content: "*";
|
||||||
|
color: #df2b2c;
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
position: relative;
|
||||||
|
padding: 1.6rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
&.no-border {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.price {
|
||||||
|
column-gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-input),
|
||||||
|
:deep(.ant-input-affix-wrapper),
|
||||||
|
:deep(.ant-input-textarea textarea) {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #000;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
import type { ListingItem } from "../types"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
imageList: ListingItem["prodImageList"]
|
||||||
|
firstSelectedIndex: number | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "select", index: number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selectedCount = computed(() => props.imageList.filter((item) => item.selected).length)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="product-image-list-container">
|
||||||
|
<div class="title flex align-center space-between">
|
||||||
|
<div class="title-left">
|
||||||
|
<span class="main-title">{{ $t("SellerListEdit.productImageMainTitle") }}</span>
|
||||||
|
<span class="sub-title">{{ $t("SellerListEdit.productImageSubTitle") }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="title-right">{{ selectedCount }}/{{ imageList.length }} selected</div>
|
||||||
|
</div>
|
||||||
|
<div class="product-image-list flex">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in imageList"
|
||||||
|
:key="index"
|
||||||
|
class="product-image-item flex flex-center"
|
||||||
|
:class="{ selected: item.selected }"
|
||||||
|
@click="emit('select', index)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="item.selected"
|
||||||
|
src="@/assets/images/seller/checked.png"
|
||||||
|
class="checked"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<img class="img-src" :src="item.url" alt="" />
|
||||||
|
<div v-if="item.selected && index === firstSelectedIndex" class="main-pic">
|
||||||
|
main
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.product-image-list-container {
|
||||||
|
margin-top: 3rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-list {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
column-gap: 0.8rem;
|
||||||
|
max-width: 80.2rem;
|
||||||
|
padding-bottom: 1.2rem;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #d9d9d9;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #000000;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-item {
|
||||||
|
width: 11.6rem;
|
||||||
|
height: 20.6rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 0.15rem solid #c7c7c7;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
content: "";
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
opacity: 0.7;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
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;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
left: 0.8rem;
|
||||||
|
right: 0.8rem;
|
||||||
|
bottom: 0.8rem;
|
||||||
|
z-index: 1;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-radius: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
'radio-button',
|
'radio-button',
|
||||||
{
|
{
|
||||||
'is-active': multiple
|
'is-active': multiple
|
||||||
? selectedValues.includes(item.key)
|
? selectedValues.includes(normalizeValue(item.key))
|
||||||
: modelValue === item.key
|
: modelValue === item.key
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
@@ -30,7 +30,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string | number | boolean | Array<string | number | boolean> | null
|
modelValue:
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| Array<string | number | boolean | null | undefined>
|
||||||
|
| null
|
||||||
options: Option[] // 按钮选项数组
|
options: Option[] // 按钮选项数组
|
||||||
multiple?: boolean // 是否支持多选,默认为 false
|
multiple?: boolean // 是否支持多选,默认为 false
|
||||||
}>()
|
}>()
|
||||||
@@ -41,6 +46,9 @@
|
|||||||
|
|
||||||
const multiple = props.multiple === true
|
const multiple = props.multiple === true
|
||||||
|
|
||||||
|
const normalizeValue = (value: any) =>
|
||||||
|
typeof value === "string" ? value.toLocaleLowerCase() : value
|
||||||
|
|
||||||
const selectedValues = computed(() => {
|
const selectedValues = computed(() => {
|
||||||
if (!multiple) {
|
if (!multiple) {
|
||||||
return typeof props.modelValue === "undefined" || props.modelValue === null
|
return typeof props.modelValue === "undefined" || props.modelValue === null
|
||||||
@@ -48,19 +56,30 @@
|
|||||||
: [props.modelValue]
|
: [props.modelValue]
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.isArray(props.modelValue) ? props.modelValue : []
|
return Array.isArray(props.modelValue)
|
||||||
|
? props.modelValue
|
||||||
|
.filter((value) => value !== null && typeof value !== "undefined")
|
||||||
|
.map(normalizeValue)
|
||||||
|
: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectOption = (value: any) => {
|
const selectOption = (value: any) => {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
const selectedValue = normalizeValue(value)
|
||||||
const index = current.indexOf(value)
|
const current = Array.isArray(props.modelValue)
|
||||||
|
? props.modelValue
|
||||||
|
.filter((item) => item !== null && typeof item !== "undefined")
|
||||||
|
.map(normalizeValue)
|
||||||
|
: []
|
||||||
|
const index = current.indexOf(selectedValue)
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
current.splice(index, 1)
|
current.splice(index, 1)
|
||||||
} else {
|
} else {
|
||||||
current.push(value.toLocaleLowerCase)
|
current.push(selectedValue)
|
||||||
}
|
}
|
||||||
emit("update:modelValue", current)
|
|
||||||
|
emit("update:modelValue", current.length ? current : null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TopImageType } from "../types"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
images: Record<TopImageType, string | null>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "crop", data: string | null, type: TopImageType): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const topImageList: TopImageType[] = ["sketch", "mainProductImage", "cover"]
|
||||||
|
const topImageTitleMap: Record<TopImageType, string> = {
|
||||||
|
sketch: "SellerListEdit.sketch",
|
||||||
|
mainProductImage: "SellerListEdit.mainProductImage",
|
||||||
|
cover: "SellerListEdit.cover"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main-image-container flex">
|
||||||
|
<div
|
||||||
|
v-for="type in topImageList"
|
||||||
|
:key="type"
|
||||||
|
:class="`main-image-item flex flex-col align-center ${type}`"
|
||||||
|
>
|
||||||
|
<div class="title" :class="{ required: type !== 'mainProductImage' }">
|
||||||
|
{{ $t(topImageTitleMap[type]) }}
|
||||||
|
</div>
|
||||||
|
<div class="sketch-item flex flex-center" :class="type">
|
||||||
|
<div
|
||||||
|
v-if="images[type]"
|
||||||
|
class="crop-tool flex flex-center"
|
||||||
|
@click="emit('crop', images[type], type)"
|
||||||
|
>
|
||||||
|
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
v-if="images[type]"
|
||||||
|
:src="images[type] || ''"
|
||||||
|
class="sketch-img"
|
||||||
|
:class="type"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<div v-else class="trigger flex flex-col align-center">
|
||||||
|
<div
|
||||||
|
v-if="type === 'cover'"
|
||||||
|
class="cover-trigger flex flex-col align-center"
|
||||||
|
@click="emit('crop', null, 'cover')"
|
||||||
|
>
|
||||||
|
<SvgIcon class="trigger-icon" name="CCrop" color="#585858" size="24" />
|
||||||
|
<div class="trigger-tips">
|
||||||
|
{{ $t("SellerListEdit.cropDesc") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="trigger-img placeholder"></div>
|
||||||
|
<div class="trigger-tips">
|
||||||
|
{{ $t("SellerListEdit.productImageDesc") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.c-svg {
|
||||||
|
width: initial;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
&::after {
|
||||||
|
content: "*";
|
||||||
|
color: #df2b2c;
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image-container {
|
||||||
|
column-gap: 3.5rem;
|
||||||
|
|
||||||
|
.main-image-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sketch-item {
|
||||||
|
width: 11.6rem;
|
||||||
|
height: 20.4rem;
|
||||||
|
border: 0.15rem solid #d1d1d1;
|
||||||
|
border-radius: 1rem;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.cover {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-tool {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.8rem;
|
||||||
|
right: 0.8rem;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #000000;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sketch-img {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.sketch {
|
||||||
|
height: initial;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
padding: 6rem 2rem 0;
|
||||||
|
|
||||||
|
&,
|
||||||
|
.cover-trigger {
|
||||||
|
row-gap: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
text-align: center;
|
||||||
|
color: #585858;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -28,191 +28,32 @@
|
|||||||
</seller-header>
|
</seller-header>
|
||||||
<div class="edit-detail-content flex">
|
<div class="edit-detail-content flex">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="main-image-container flex">
|
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" />
|
||||||
<div
|
<ProductImageList
|
||||||
v-for="type in topImageList"
|
:image-list="prodImgList"
|
||||||
:key="type"
|
:first-selected-index="firstSelectedIndex"
|
||||||
:class="`main-image-item flex flex-col align-center ${type}`"
|
@select="handleSelectProdImg"
|
||||||
>
|
|
||||||
<div class="title" :class="{ required: type !== 'mainProductImage' }">
|
|
||||||
{{ $t(topImageTitleMap[type]) }}
|
|
||||||
</div>
|
|
||||||
<div class="sketch-item flex flex-center" :class="type">
|
|
||||||
<div
|
|
||||||
v-if="previewImageMap[type]"
|
|
||||||
class="crop-tool flex flex-center"
|
|
||||||
@click="handleClickCrop(previewImageMap[type], type)"
|
|
||||||
>
|
|
||||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
v-if="previewImageMap[type]"
|
|
||||||
:src="previewImageMap[type]"
|
|
||||||
class="sketch-img"
|
|
||||||
:class="type"
|
|
||||||
alt=""
|
|
||||||
/>
|
/>
|
||||||
<div v-else class="trigger flex flex-col align-center">
|
<ApparelSketchList
|
||||||
<div
|
:sketch-list="currentListing.sketchList"
|
||||||
v-if="type === 'cover'"
|
@crop="handleClickCrop"
|
||||||
class="cover-trigger flex flex-col align-center"
|
|
||||||
@click="handleClickCrop(null, 'cover')"
|
|
||||||
>
|
|
||||||
<SvgIcon
|
|
||||||
class="trigger-icon"
|
|
||||||
name="CCrop"
|
|
||||||
color="#585858"
|
|
||||||
size="24"
|
|
||||||
/>
|
/>
|
||||||
<div class="trigger-tips">
|
|
||||||
{{ $t("SellerListEdit.cropDesc") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div class="trigger-img placeholder"></div>
|
|
||||||
<div class="trigger-tips">
|
|
||||||
{{ $t("SellerListEdit.productImageDesc") }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="product-image-list-container">
|
|
||||||
<div class="title flex align-center space-between">
|
|
||||||
<div class="title-left">
|
|
||||||
<span class="main-title">{{
|
|
||||||
$t("SellerListEdit.productImageMainTitle")
|
|
||||||
}}</span>
|
|
||||||
<span class="sub-title">{{
|
|
||||||
$t("SellerListEdit.productImageSubTitle")
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="title-right">
|
|
||||||
{{ selectedProdImgs }}/{{ prodImgList.length }} selected
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 }"
|
|
||||||
@click="handleSelectProdImg(index)"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="item.selected"
|
|
||||||
src="@/assets/images/seller/checked.png"
|
|
||||||
class="checked"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<img class="img-src" :src="item.url" alt="" />
|
|
||||||
<div
|
|
||||||
v-if="item.selected && index === firstSelectedIndex"
|
|
||||||
class="main-pic"
|
|
||||||
>
|
|
||||||
main
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="apparel-container">
|
|
||||||
<div class="title">
|
|
||||||
<span class="main-title">{{
|
|
||||||
$t("SellerListEdit.apparelSketchTitle")
|
|
||||||
}}</span>
|
|
||||||
<span class="sub-title">
|
|
||||||
{{ $t("SellerListEdit.apparelSketchSubTitle") }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="sketch-list-container flex">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in selectList[currentIndex].sketchList"
|
|
||||||
:key="index"
|
|
||||||
class="sketch-element flex flex-center"
|
|
||||||
>
|
|
||||||
<img class="img-src" :src="item.url" alt="" />
|
|
||||||
<div
|
|
||||||
class="crop-tool flex flex-center"
|
|
||||||
@click="handleClickCrop(item.url, 'apparel')"
|
|
||||||
>
|
|
||||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="form-container flex flex-col">
|
<ListingForm
|
||||||
<div class="form-item">
|
:product-name="currentListing.productName"
|
||||||
<div class="form-item-label required">
|
:price="currentListing.price"
|
||||||
{{ $t("SellerListEdit.productName") }}
|
:desc="currentListing.desc"
|
||||||
</div>
|
:gender="currentListing.gender"
|
||||||
<div class="form-item-value product-name">
|
:category="currentListing.category"
|
||||||
<a-input
|
:gender-options="genderOptions"
|
||||||
v-model:value="currentListing.productName"
|
:category-options="categoryOptions"
|
||||||
show-count
|
@update:product-name="currentListing.productName = $event"
|
||||||
placeholder="Enter product name"
|
@update:price="currentListing.price = $event"
|
||||||
:bordered="false"
|
@update:desc="currentListing.desc = $event"
|
||||||
:maxlength="60"
|
@update:gender="currentListing.gender = $event"
|
||||||
|
@update:category="currentListing.category = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-item">
|
|
||||||
<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="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
|
|
||||||
multiple
|
|
||||||
: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" v-if="selectList.length > 1">
|
<div class="page-control flex align-center" v-if="selectList.length > 1">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
v-model:current="currentPage"
|
v-model:current="currentPage"
|
||||||
@@ -238,13 +79,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch, onMounted } from "vue"
|
import { computed, ref, onMounted } from "vue"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
import { useI18n } from "vue-i18n"
|
import { useI18n } from "vue-i18n"
|
||||||
import { message } from "ant-design-vue"
|
import { message } from "ant-design-vue"
|
||||||
import SellerHeader from "../../seller-header.vue"
|
import SellerHeader from "../../seller-header.vue"
|
||||||
import Radio from "./components/Radio.vue"
|
|
||||||
import ImageClipDialog from "../../BrandProfile/image-clip-dialog.vue"
|
import ImageClipDialog from "../../BrandProfile/image-clip-dialog.vue"
|
||||||
|
import ApparelSketchList from "./components/ApparelSketchList.vue"
|
||||||
|
import ListingForm from "./components/ListingForm.vue"
|
||||||
|
import ProductImageList from "./components/ProductImageList.vue"
|
||||||
|
import TopImageSection from "./components/TopImageSection.vue"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
import {
|
import {
|
||||||
fetchSketchDetail,
|
fetchSketchDetail,
|
||||||
@@ -252,6 +96,13 @@
|
|||||||
fetchListingDetailById,
|
fetchListingDetailById,
|
||||||
fetchUpdateListing
|
fetchUpdateListing
|
||||||
} from "./api"
|
} from "./api"
|
||||||
|
import type {
|
||||||
|
ListingDetailImage,
|
||||||
|
ListingDetailResponse,
|
||||||
|
ListingItem,
|
||||||
|
RadioOption,
|
||||||
|
StatusType
|
||||||
|
} from "./types"
|
||||||
|
|
||||||
const ROUTER = useRouter()
|
const ROUTER = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -264,31 +115,6 @@
|
|||||||
|
|
||||||
const STORE = useStore()
|
const STORE = useStore()
|
||||||
|
|
||||||
type CategoryOption = {
|
|
||||||
label: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListingItem = {
|
|
||||||
designItemId: number | string | null
|
|
||||||
sketch: string | null
|
|
||||||
mainProductImage: string
|
|
||||||
cover: string
|
|
||||||
productImage: string[]
|
|
||||||
apparelSketch: string[]
|
|
||||||
productName: string
|
|
||||||
price: string
|
|
||||||
desc: string
|
|
||||||
gender: string
|
|
||||||
category: string[]
|
|
||||||
prodImageList: Array<{
|
|
||||||
url: string
|
|
||||||
selected?: boolean
|
|
||||||
}>
|
|
||||||
sketchList: Array<{ url: string | null }>
|
|
||||||
}
|
|
||||||
type StatusType = "draft" | "publish"
|
|
||||||
|
|
||||||
const createListingItem = (
|
const createListingItem = (
|
||||||
sketch: string | null = null,
|
sketch: string | null = null,
|
||||||
designItemId: number | string | null = null
|
designItemId: number | string | null = null
|
||||||
@@ -308,16 +134,9 @@
|
|||||||
sketchList: []
|
sketchList: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const topImageList = ["sketch", "mainProductImage", "cover"] as const
|
|
||||||
const topImageTitleMap: Record<(typeof topImageList)[number], string> = {
|
|
||||||
sketch: "SellerListEdit.sketch",
|
|
||||||
mainProductImage: "SellerListEdit.mainProductImage",
|
|
||||||
cover: "SellerListEdit.cover"
|
|
||||||
}
|
|
||||||
|
|
||||||
const genderOptions = STORE.state.UserHabit?.sex.value || []
|
const genderOptions = STORE.state.UserHabit?.sex.value || []
|
||||||
|
|
||||||
const fallbackCategoryOptions: Record<string, CategoryOption[]> = {
|
const fallbackCategoryOptions: Record<string, RadioOption[]> = {
|
||||||
MALE: STORE.state.UserHabit?.MalePosition || [],
|
MALE: STORE.state.UserHabit?.MalePosition || [],
|
||||||
FEMALE: STORE.state.UserHabit?.FemalePosition || []
|
FEMALE: STORE.state.UserHabit?.FemalePosition || []
|
||||||
}
|
}
|
||||||
@@ -343,21 +162,96 @@
|
|||||||
cover: currentListing.value.cover
|
cover: currentListing.value.cover
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const firstSelectedIndex = ref(null) //显示main标签的图片索引
|
const firstSelectedIndex = ref<number | null>(null) //显示main标签的图片索引
|
||||||
const selectedProdImgs = computed(() => {
|
|
||||||
return prodImgList.value.filter((item) => item.selected).length
|
const getSortedDetailImages = (images: ListingDetailImage[] = []) => {
|
||||||
|
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageSelected = (value: ListingDetailImage["isSelected"]) =>
|
||||||
|
value === true || value === 1 || value === "1"
|
||||||
|
|
||||||
|
const normalizeDetailGender = (value: ListingDetailResponse["designFor"]) => {
|
||||||
|
const gender = String(value || "").toUpperCase()
|
||||||
|
return gender === "MALE" || gender === "FEMALE" ? gender : "FEMALE"
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeDetailCategory = (
|
||||||
|
value: ListingDetailResponse["productCategory"]
|
||||||
|
): ListingItem["category"] => {
|
||||||
|
const categories = Array.isArray(value) ? value : value ? [value] : []
|
||||||
|
const normalized = categories
|
||||||
|
.filter((category) => category !== null && typeof category !== "undefined")
|
||||||
|
.map((category) => String(category).toLowerCase())
|
||||||
|
|
||||||
|
return normalized.length ? normalized : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const createListingItemFromDetail = (detail: ListingDetailResponse): ListingItem => {
|
||||||
|
const listing = createListingItem()
|
||||||
|
|
||||||
|
listing.productName = detail.title || ""
|
||||||
|
listing.price =
|
||||||
|
detail.price === null || typeof detail.price === "undefined" ? "" : String(detail.price)
|
||||||
|
listing.desc = detail.description || ""
|
||||||
|
listing.gender = normalizeDetailGender(detail.designFor)
|
||||||
|
listing.category = normalizeDetailCategory(detail.productCategory)
|
||||||
|
|
||||||
|
getSortedDetailImages(detail.images || []).forEach((image) => {
|
||||||
|
const imageUrl = image.imageUrl || ""
|
||||||
|
if (!imageUrl) return
|
||||||
|
|
||||||
|
if (image.category === "cover") {
|
||||||
|
listing.cover = imageUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.category === "sketch") {
|
||||||
|
listing.sketch = imageUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.category === "mainProductImage") {
|
||||||
|
listing.mainProductImage = imageUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.category === "main_product" || image.category === "product") {
|
||||||
|
listing.prodImageList.push({
|
||||||
|
url: imageUrl,
|
||||||
|
selected: getImageSelected(image.isSelected)
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.category === "apparel") {
|
||||||
|
listing.sketchList.push({ url: imageUrl })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!listing.mainProductImage) {
|
||||||
|
listing.mainProductImage =
|
||||||
|
listing.prodImageList.find((item) => item.selected)?.url || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
listing.productImage = listing.prodImageList.map((item) => item.url)
|
||||||
|
listing.apparelSketch = listing.sketchList
|
||||||
|
.map((item) => item.url)
|
||||||
|
.filter((url): url is string => Boolean(url))
|
||||||
|
|
||||||
|
return listing
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelectProdImg = (index: number) => {
|
const handleSelectProdImg = (index: number) => {
|
||||||
const target = prodImgList.value[index]
|
const target = prodImgList.value[index]
|
||||||
|
|
||||||
const willSelect = !target.selected
|
const willSelect = !target.selected
|
||||||
|
|
||||||
target.selected = willSelect
|
target.selected = willSelect
|
||||||
|
|
||||||
if (willSelect && !currentListing.value.mainProductImage) {
|
if (willSelect && firstSelectedIndex.value === null) {
|
||||||
currentListing.value.mainProductImage = target.url
|
currentListing.value.mainProductImage = target.url
|
||||||
firstSelectedIndex.value = index
|
firstSelectedIndex.value = index
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!willSelect && currentListing.value.mainProductImage === target.url) {
|
if (!willSelect && currentListing.value.mainProductImage === target.url) {
|
||||||
@@ -367,7 +261,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cropType = ref("")
|
const cropType = ref("")
|
||||||
const handleClickCrop = (data, type, list = []) => {
|
const handleClickCrop = (data: any, type: string, paramThree: any = []) => {
|
||||||
|
// 处理来自TopImageSection的调用: (data, type, list)
|
||||||
|
// 处理来自ApparelSketchList的调用: (data, type, index)
|
||||||
|
const index = typeof paramThree === 'number' ? paramThree : undefined
|
||||||
|
const list = Array.isArray(paramThree) ? paramThree : []
|
||||||
|
|
||||||
// console.log(data, type)
|
// console.log(data, type)
|
||||||
// console.log(selectList.value[currentIndex.value])
|
// console.log(selectList.value[currentIndex.value])
|
||||||
let origin = []
|
let origin = []
|
||||||
@@ -392,8 +291,11 @@
|
|||||||
(file) => {
|
(file) => {
|
||||||
// console.log(file)
|
// console.log(file)
|
||||||
uploadFile(file).then((res) => {
|
uploadFile(file).then((res) => {
|
||||||
console.log(res)
|
if (type === "apparel" && typeof index !== "undefined") {
|
||||||
|
selectList.value[currentIndex.value].sketchList[index].url = res
|
||||||
|
} else {
|
||||||
selectList.value[currentIndex.value][type] = res
|
selectList.value[currentIndex.value][type] = res
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ ratio, isPreview: true, title: titleList[type], isProduct: true },
|
{ ratio, isPreview: true, title: titleList[type], isProduct: true },
|
||||||
@@ -450,35 +352,40 @@
|
|||||||
|
|
||||||
const handleSaveForm = async (type: StatusType) => {
|
const handleSaveForm = async (type: StatusType) => {
|
||||||
const paramsList = []
|
const paramsList = []
|
||||||
selectList.value.forEach((item) => {
|
selectList.value.forEach((item: ListingItem) => {
|
||||||
const params = {
|
const params = {
|
||||||
id: null,
|
id: null,
|
||||||
title: selectList.value[currentIndex.value].productName,
|
title: item.productName,
|
||||||
description: selectList.value[currentIndex.value].desc,
|
description: item.desc,
|
||||||
price: selectList.value[currentIndex.value].price,
|
price: item.price,
|
||||||
status: type === "draft" ? 0 : 1,
|
status: type === "draft" ? 0 : 1,
|
||||||
images: [],
|
images: [],
|
||||||
designFor: selectList.value[currentIndex.value].gender.toLowerCase,
|
designFor: item.gender.toLowerCase,
|
||||||
productCategory: selectList.value[currentIndex.value].category
|
productCategory: item.category
|
||||||
}
|
}
|
||||||
//
|
|
||||||
topImageList.forEach((el) => {
|
;["sketch", "cover"].forEach((el) => {
|
||||||
params.images.push({
|
params.images.push({
|
||||||
category: el,
|
category: el,
|
||||||
imageUrl: selectList.value[currentIndex.value][el],
|
imageUrl: item[el],
|
||||||
isSelected: 1
|
isSelected: 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
selectList.value[currentIndex.value].prodImageList.forEach((item) => {
|
if (item.mainProductImage) {
|
||||||
if (item.selected) {
|
|
||||||
params.images.push({
|
params.images.push({
|
||||||
category: "main_product",
|
category: 'main_product',
|
||||||
imageUrl: item.url,
|
imageUrl: item.mainProductImage,
|
||||||
isSelected: Number(item.selected)
|
isSeleted:1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
item.prodImageList.forEach((item) => {
|
||||||
|
params.images.push({
|
||||||
|
category: "product",
|
||||||
|
imageUrl: item.url,
|
||||||
|
isSelected: Number(!!item.selected)
|
||||||
})
|
})
|
||||||
selectList.value[currentIndex.value].sketchList.forEach((item) => {
|
})
|
||||||
|
item.sketchList.forEach((item) => {
|
||||||
params.images.push({
|
params.images.push({
|
||||||
category: "apparel",
|
category: "apparel",
|
||||||
imageUrl: item.url,
|
imageUrl: item.url,
|
||||||
@@ -488,9 +395,14 @@
|
|||||||
paramsList.push(params)
|
paramsList.push(params)
|
||||||
})
|
})
|
||||||
console.log(paramsList)
|
console.log(paramsList)
|
||||||
fetchUpdateListing(paramsList)
|
debugger
|
||||||
|
await fetchUpdateListing(paramsList)
|
||||||
}
|
}
|
||||||
const handleClickMenu = async (status: StatusType) => {
|
const handleClickMenu = async (status: StatusType) => {
|
||||||
|
if (status === "draft" && !selectList.value[currentIndex.value].cover) {
|
||||||
|
message.error("请先完成封面制作")
|
||||||
|
return
|
||||||
|
}
|
||||||
if (status === "publish" && !validatePublishRequired()) return
|
if (status === "publish" && !validatePublishRequired()) return
|
||||||
|
|
||||||
await handleSaveForm(status)
|
await handleSaveForm(status)
|
||||||
@@ -516,14 +428,27 @@
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// fetchListingDetailById(itemId.value).then(res => {
|
}
|
||||||
// console.log('iddetail',res)
|
|
||||||
// })
|
const handleGetDetailById = () => {
|
||||||
|
fetchListingDetailById(itemId.value).then((res: ListingDetailResponse) => {
|
||||||
|
const listing = createListingItemFromDetail(res)
|
||||||
|
const selectedIndex = listing.prodImageList.findIndex((item) => item.selected)
|
||||||
|
|
||||||
|
currentPage.value = 1
|
||||||
|
selectList.value = [listing]
|
||||||
|
firstSelectedIndex.value = selectedIndex === -1 ? null : selectedIndex
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const designItemIds = history.state?.designItemIds || []
|
const data = history.state
|
||||||
|
if (data?.type === "edit") {
|
||||||
itemId.value = history.state?.id || ""
|
itemId.value = history.state?.id || ""
|
||||||
|
handleGetDetailById()
|
||||||
|
} else {
|
||||||
|
const designItemIds = history.state?.designItemIds || []
|
||||||
|
|
||||||
if (!designItemIds.length) return
|
if (!designItemIds.length) return
|
||||||
|
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
@@ -531,8 +456,9 @@
|
|||||||
createListingItem(item.designOutfitUrl, item.designItemId)
|
createListingItem(item.designOutfitUrl, item.designItemId)
|
||||||
)
|
)
|
||||||
const list = designItemIds.map((el) => el.designItemId)
|
const list = designItemIds.map((el) => el.designItemId)
|
||||||
console.log("list", list.length, list)
|
// console.log("list", list.length, list)
|
||||||
handleFetchItemDetial(list)
|
handleFetchItemDetial(list)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -602,362 +528,10 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.required {
|
|
||||||
&::after {
|
|
||||||
content: "*";
|
|
||||||
color: #df2b2c;
|
|
||||||
margin-left: 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
// flex: 1;
|
|
||||||
// min-width: 0;
|
|
||||||
|
|
||||||
.main-image-container {
|
|
||||||
// max-width: 80.2rem;
|
|
||||||
column-gap: 3.5rem;
|
|
||||||
.main-image-item {
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sketch-item {
|
|
||||||
width: 11.6rem;
|
|
||||||
height: 20.4rem;
|
|
||||||
border: 0.15rem solid #d1d1d1;
|
|
||||||
border-radius: 1rem;
|
|
||||||
position: relative;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.cover {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crop-tool {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.8rem;
|
|
||||||
right: 0.8rem;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #000000;
|
|
||||||
z-index: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sketch-img {
|
|
||||||
height: 100%;
|
|
||||||
&.sketch {
|
|
||||||
height: initial;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 100%;
|
|
||||||
padding: 6rem 2rem 0;
|
|
||||||
&,
|
|
||||||
.cover-trigger {
|
|
||||||
row-gap: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
text-align: center;
|
|
||||||
color: #585858;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-list-container {
|
|
||||||
margin-top: 3rem;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-list {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
column-gap: 0.8rem;
|
|
||||||
max-width: 80.2rem;
|
|
||||||
padding-bottom: 1.2rem;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: #d9d9d9;
|
|
||||||
border-radius: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #000000;
|
|
||||||
border-radius: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-item {
|
|
||||||
width: 11.6rem;
|
|
||||||
height: 20.6rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
border: 0.15rem solid #c7c7c7;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
content: "";
|
|
||||||
background-color: #fcfcfc;
|
|
||||||
opacity: 0.7;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
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;
|
|
||||||
line-height: 2.4rem;
|
|
||||||
left: 0.8rem;
|
|
||||||
right: 0.8rem;
|
|
||||||
bottom: 0.8rem;
|
|
||||||
z-index: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
border-radius: 1.2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.apparel-container {
|
|
||||||
margin-top: 3rem;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
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 {
|
|
||||||
// height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crop-tool {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.4rem;
|
|
||||||
right: 0.4rem;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
width: 55.2rem;
|
width: 55.2rem;
|
||||||
flex-shrink: 0;
|
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;
|
|
||||||
position: relative;
|
|
||||||
padding: 1.6rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #000;
|
|
||||||
|
|
||||||
&.no-border {
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.price {
|
|
||||||
column-gap: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-input),
|
|
||||||
:deep(.ant-input-affix-wrapper),
|
|
||||||
:deep(.ant-input-textarea textarea) {
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #000;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
: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 {
|
.page-control {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
|
|||||||
47
src/views/SellerDashboard/MyListings/EditDetail/types.ts
Normal file
47
src/views/SellerDashboard/MyListings/EditDetail/types.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export type RadioOption = {
|
||||||
|
name: string | number
|
||||||
|
value: string | number | boolean
|
||||||
|
key: string
|
||||||
|
optype: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TopImageType = "sketch" | "mainProductImage" | "cover"
|
||||||
|
export type CropType = TopImageType | "apparel"
|
||||||
|
|
||||||
|
export type ListingItem = {
|
||||||
|
designItemId: number | string | null
|
||||||
|
sketch: string | null
|
||||||
|
mainProductImage: string
|
||||||
|
cover: string
|
||||||
|
productImage: string[]
|
||||||
|
apparelSketch: string[]
|
||||||
|
productName: string
|
||||||
|
price: string
|
||||||
|
desc: string
|
||||||
|
gender: string
|
||||||
|
category: string[] | null
|
||||||
|
prodImageList: Array<{
|
||||||
|
url: string
|
||||||
|
selected?: boolean
|
||||||
|
}>
|
||||||
|
sketchList: Array<{ url: string | null }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListingDetailImage = {
|
||||||
|
category?: string | null
|
||||||
|
imageUrl?: string | null
|
||||||
|
isSelected?: boolean | number | string | null
|
||||||
|
sortOrder?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListingDetailResponse = {
|
||||||
|
id?: number | string | null
|
||||||
|
title?: string | null
|
||||||
|
description?: string | null
|
||||||
|
price?: number | string | null
|
||||||
|
designFor?: string | null
|
||||||
|
productCategory?: string | string[] | null
|
||||||
|
images?: ListingDetailImage[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StatusType = "draft" | "publish"
|
||||||
@@ -41,8 +41,11 @@ const getCreateList = ()=>{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const selectCollectionItem = (item:any)=>{
|
const selectCollectionItem = (item:any)=>{
|
||||||
|
console.log(item)
|
||||||
|
if(item.userLikeGroupVO?.groupDetails?.length > 0){
|
||||||
emit('selectCollectionItem',item)
|
emit('selectCollectionItem',item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
getCreateList()
|
getCreateList()
|
||||||
})
|
})
|
||||||
@@ -55,9 +58,12 @@ defineExpose({getCreateList})
|
|||||||
<div class="list">
|
<div class="list">
|
||||||
<div v-for="(item,index) in list" :key="index" class="item" @click="selectCollectionItem(item)">
|
<div v-for="(item,index) in list" :key="index" class="item" @click="selectCollectionItem(item)">
|
||||||
<div class="imgList">
|
<div class="imgList">
|
||||||
<div v-for="(img,index) in item.userLikeGroupVO?.groupDetails" :key="index" class="img">
|
<div v-if="item.userLikeGroupVO?.groupDetails?.length > 0" v-for="(img,index) in item.userLikeGroupVO?.groupDetails" :key="index" class="img">
|
||||||
<img :src="img.url" alt="">
|
<img :src="img.url" alt="">
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="img null">
|
||||||
|
<img src="@/assets/images/seller/selectCollectionNullStatus.png" alt="">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="name">{{item.name}}</div>
|
<div class="name">{{item.name}}</div>
|
||||||
@@ -123,6 +129,12 @@ defineExpose({getCreateList})
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
&.null{
|
||||||
|
background-color: transparent;
|
||||||
|
> img{
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
> img{
|
> img{
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const next = ()=>{
|
|||||||
router.push({
|
router.push({
|
||||||
path:'/home/seller/myListings/edit',
|
path:'/home/seller/myListings/edit',
|
||||||
state: {
|
state: {
|
||||||
id:route.params.collectionId,
|
|
||||||
designItemIds,
|
designItemIds,
|
||||||
|
type:'create'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ const setDomSize = (width: number)=>{
|
|||||||
if(!listingsBoxRef.value)return
|
if(!listingsBoxRef.value)return
|
||||||
let listDom = listingsBoxRef.value.querySelector('.list')
|
let listDom = listingsBoxRef.value.querySelector('.list')
|
||||||
let listItemDom = listDom.querySelector('.item')
|
let listItemDom = listDom.querySelector('.item')
|
||||||
let offsetWidth = listItemDom.getBoundingClientRect().width
|
let offsetWidth = listItemDom?.getBoundingClientRect?.()?.width
|
||||||
let lineNum = Math.floor(width / offsetWidth)
|
let lineNum = Math.floor(width / offsetWidth)
|
||||||
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
||||||
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import contentItem from "./contentItem.vue"
|
|||||||
import selectMenu from '@/component/modules/selectMenu.vue'
|
import selectMenu from '@/component/modules/selectMenu.vue'
|
||||||
import deleteDrafts from './deleteDrafts.vue'
|
import deleteDrafts from './deleteDrafts.vue'
|
||||||
import { Https } from '@/tool/https'
|
import { Https } from '@/tool/https'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
@@ -63,11 +65,14 @@ const domSizeList = ref([
|
|||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const deleteDraftsRef = ref(null)
|
const deleteDraftsRef = ref(null)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const deleteDraft = (item: any)=>{
|
const deleteDraft = (item: any)=>{
|
||||||
deleteDraftsRef.value.open(()=>{
|
deleteDraftsRef.value.open(item,()=>{
|
||||||
|
putListingStatus(item,2).then(()=>{
|
||||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const vObserve = {
|
const vObserve = {
|
||||||
@@ -99,6 +104,7 @@ const getPublishedData = async ()=>{
|
|||||||
}
|
}
|
||||||
await getPublishList(value).then((res)=>{
|
await getPublishList(value).then((res)=>{
|
||||||
if(res.content.length == 0)publishData.isNoData = true
|
if(res.content.length == 0)publishData.isNoData = true
|
||||||
|
publishData.pageNum += 1
|
||||||
list.value.push(...res.content)
|
list.value.push(...res.content)
|
||||||
})
|
})
|
||||||
publishData.isShowMark = false
|
publishData.isShowMark = false
|
||||||
@@ -113,6 +119,7 @@ const getNoPublishedData = async ()=>{
|
|||||||
}
|
}
|
||||||
await getPublishList(value).then((res)=>{
|
await getPublishList(value).then((res)=>{
|
||||||
if(res.content.length == 0)noPublishData.isNoData = true
|
if(res.content.length == 0)noPublishData.isNoData = true
|
||||||
|
noPublishData.pageNum += 1
|
||||||
list2.value.push(...res.content)
|
list2.value.push(...res.content)
|
||||||
})
|
})
|
||||||
noPublishData.isShowMark = false
|
noPublishData.isShowMark = false
|
||||||
@@ -127,28 +134,46 @@ const getPublishList = (data:any)=>{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const putListingStatus = ()=>{
|
const putListingStatus = async (item,status:number)=>{
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
let data = {
|
let data = {
|
||||||
id:'',
|
id:item.id,
|
||||||
status:'',
|
status:status,
|
||||||
}
|
}
|
||||||
Https.axiosPut(Https.httpUrls.putListingStatus,data).then((res:any)=>{
|
Https.axiosPut(Https.httpUrls.putListingStatus,data).then((res:any)=>{
|
||||||
|
resolve(res)
|
||||||
|
}).catch((err:any)=>{
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const draftListing = (item: any)=>{
|
const draftListing = async (item: any)=>{
|
||||||
//数组前面添加item
|
//数组前面添加item
|
||||||
|
await putListingStatus(item,0).then(()=>{
|
||||||
list2.value.unshift(item)
|
list2.value.unshift(item)
|
||||||
list.value = list.value.filter((v: any)=>v.id != item.id)
|
list.value = list.value.filter((v: any)=>v.id != item.id)
|
||||||
|
})
|
||||||
|
message.success('Product moved to drafts and stats reset.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishListing = (item: any)=>{
|
const publishListing = async (item: any)=>{
|
||||||
|
await putListingStatus(item,1).then(()=>{
|
||||||
list.value.unshift(item)
|
list.value.unshift(item)
|
||||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||||
|
})
|
||||||
|
message.success('Item is now live on the Marketplace.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const editListing = (item: any)=>{
|
const editListing = (item: any)=>{
|
||||||
|
router.push({
|
||||||
|
path:'/home/seller/myListings/edit',
|
||||||
|
state: {
|
||||||
|
id:item.id,
|
||||||
|
type:'edit'
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +192,7 @@ const setDomSize = (width: number)=>{
|
|||||||
if(!listingsBoxRef.value)return
|
if(!listingsBoxRef.value)return
|
||||||
let listDom = listingsBoxRef.value.querySelector('.list')
|
let listDom = listingsBoxRef.value.querySelector('.list')
|
||||||
let listItemDom = listDom.querySelector('.item')
|
let listItemDom = listDom.querySelector('.item')
|
||||||
let offsetWidth = listItemDom.getBoundingClientRect().width
|
let offsetWidth = listItemDom?.getBoundingClientRect?.()?.width
|
||||||
let lineNum = Math.floor(width / offsetWidth)
|
let lineNum = Math.floor(width / offsetWidth)
|
||||||
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
||||||
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const {} = toRefs(data);
|
|||||||
<template>
|
<template>
|
||||||
<div class="item" :draging="true" :class="domSize">
|
<div class="item" :draging="true" :class="domSize">
|
||||||
<div class="imgBox">
|
<div class="imgBox">
|
||||||
<img src="" alt="">
|
<img :src="item.cover" alt="">
|
||||||
<div class="maskBtn">
|
<div class="maskBtn">
|
||||||
<div @click="$emit('editListing',item)">
|
<div @click="$emit('editListing',item)">
|
||||||
<svgIcon name="seller-edit" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
<svgIcon name="seller-edit" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
||||||
@@ -50,21 +50,21 @@ const {} = toRefs(data);
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="name">item name</div>
|
<div class="name">{{item.title}}</div>
|
||||||
<div class="price">$1123</div>
|
<div class="price">${{item.price}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="detailItem" v-if="type == 'listings'">
|
<div class="detailItem" v-if="type == 'listings'">
|
||||||
<div class="shopping1">
|
<div class="shopping1">
|
||||||
<i class="fi fi-rr-shopping-bag-add"></i>
|
<i class="fi fi-rr-shopping-bag-add"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>123</span>
|
<span>{{ item.salesVolume || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detailItem" v-if="type == 'listings'">
|
<div class="detailItem" v-if="type == 'listings'">
|
||||||
<div class="eye1">
|
<div class="eye1">
|
||||||
<i class="fi fi-rs-eye"></i>
|
<i class="fi fi-rs-eye"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>123</span>
|
<span>{{ item.viewCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detailItem drafts" v-if="type == 'drafts'" @click="$emit('deleteDraft',item)">
|
<div class="detailItem drafts" v-if="type == 'drafts'" @click="$emit('deleteDraft',item)">
|
||||||
<div class="">
|
<div class="">
|
||||||
@@ -128,6 +128,11 @@ const {} = toRefs(data);
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: var(--itemImgHeight);
|
height: var(--itemImgHeight);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
> img{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
> .maskBtn{
|
> .maskBtn{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -182,6 +187,7 @@ const {} = toRefs(data);
|
|||||||
gap: var(--detailRightItemGap);
|
gap: var(--detailRightItemGap);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
> div{
|
> div{
|
||||||
color: var(--rightColor);
|
color: var(--rightColor);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ const router = useRouter()
|
|||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
let data = reactive({
|
let data = reactive({
|
||||||
})
|
})
|
||||||
|
const item = ref<any>()
|
||||||
const fun = ref(null)
|
const fun = ref(null)
|
||||||
|
|
||||||
let deleteDraftsRef = ref(null)
|
let deleteDraftsRef = ref(null)
|
||||||
|
|
||||||
const open = (deleteFun)=>{
|
const open = (data:any,deleteFun)=>{
|
||||||
|
item.value = data
|
||||||
fun.value = deleteFun
|
fun.value = deleteFun
|
||||||
emit('update:visible', true)
|
emit('update:visible', true)
|
||||||
}
|
}
|
||||||
@@ -35,6 +36,7 @@ const deleteDrafts = ()=>{
|
|||||||
|
|
||||||
|
|
||||||
const cleardata = ()=>{
|
const cleardata = ()=>{
|
||||||
|
item.value = null
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +80,11 @@ const { showAgain } = toRefs(data);
|
|||||||
</div>
|
</div>
|
||||||
<div class="deleteContent">
|
<div class="deleteContent">
|
||||||
<div class="img">
|
<div class="img">
|
||||||
<img src="" alt="">
|
<img :src="item?.value?.cover" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="name">Item Name</div>
|
<div class="name">{{ item?.value?.title }}</div>
|
||||||
<div class="price">HK$392.00 · Draft</div>
|
<div class="price">HK${{ item?.value?.price }} · Draft</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btnBox">
|
<div class="btnBox">
|
||||||
|
|||||||
Reference in New Issue
Block a user