259 lines
8.2 KiB
TypeScript
259 lines
8.2 KiB
TypeScript
import { request } from '../../utils/request'
|
|
import { OutfitItem, ClothesItem, PagedResult } from '../../types/models'
|
|
import { normalizePaged, hasMorePaged } from '../../utils/paged'
|
|
import { logger } from '../../utils/logger'
|
|
|
|
// 日志模块名
|
|
const LOG = 'outfit-edit'
|
|
|
|
const CATEGORIES = [
|
|
{ name: '全部', value: '' },
|
|
{ name: '上装', value: '上装' },
|
|
{ name: '下装', value: '下装' },
|
|
{ name: '连衣裙', value: '连衣裙' },
|
|
{ name: '外套', value: '外套' },
|
|
{ name: '鞋', value: '鞋' },
|
|
{ name: '配饰', value: '配饰' },
|
|
]
|
|
|
|
const PAGE_SIZE = 50
|
|
|
|
Page({
|
|
data: {
|
|
uuid: '',
|
|
isEdit: false,
|
|
view: 'form' as 'form' | 'select',
|
|
outfitName: '',
|
|
outfitDesc: '',
|
|
scene: '',
|
|
selectedUuids: [] as string[],
|
|
selectedItems: [] as ClothesItem[],
|
|
categories: CATEGORIES,
|
|
activeCategory: 0,
|
|
allClothes: [] as ClothesItem[],
|
|
selectLoading: true,
|
|
loadingMore: false,
|
|
hasMore: true,
|
|
page: 1,
|
|
saving: false,
|
|
},
|
|
|
|
onLoad(query: Record<string, string>) {
|
|
const uuid = query.uuid || ''
|
|
logger.debug(LOG, 'onLoad, uuid=', uuid, '编辑态=', !!uuid)
|
|
if (uuid) {
|
|
this.setData({ uuid, isEdit: true })
|
|
this.fetchOutfit(uuid)
|
|
}
|
|
},
|
|
|
|
async fetchOutfit(uuid: string) {
|
|
try {
|
|
const outfit = await request<OutfitItem>({ url: `/outfit/${uuid}` })
|
|
const uuids = outfit.items.map((i) => i.clothes_uuid)
|
|
const clothes = await Promise.all(
|
|
uuids.map(async (id) => {
|
|
try {
|
|
return await request<ClothesItem>({ url: `/clothes/${id}`, showError: false })
|
|
} catch (e: any) {
|
|
logger.warn(LOG, '单品详情拉取失败, uuid=', id, 'code=', e && e.code)
|
|
return { clothes_uuid: id } as ClothesItem
|
|
}
|
|
})
|
|
)
|
|
// 编辑态下也要填充已选快照,避免确认选衣时跨分类数据丢失
|
|
const map = this.getSelectedMap()
|
|
clothes.forEach((it) => {
|
|
map[it.clothes_uuid] = it
|
|
})
|
|
this.setData({
|
|
outfitName: outfit.outfit_name,
|
|
outfitDesc: outfit.outfit_desc,
|
|
scene: outfit.scene,
|
|
selectedUuids: uuids,
|
|
selectedItems: clothes,
|
|
})
|
|
} catch (e: any) {
|
|
logger.warn(LOG, '穿搭详情加载失败, code=', e && e.code)
|
|
}
|
|
},
|
|
|
|
// 已选衣物快照:跨分类切换时列表只保留当前分类,快照保留全部已选项
|
|
getSelectedMap(): Record<string, ClothesItem> {
|
|
if (!(this as any).selectedMap) (this as any).selectedMap = {}
|
|
return (this as any).selectedMap
|
|
},
|
|
|
|
onNameInput(e: WechatMiniprogram.Input) {
|
|
this.setData({ outfitName: e.detail.value })
|
|
},
|
|
onDescInput(e: WechatMiniprogram.Input) {
|
|
this.setData({ outfitDesc: e.detail.value })
|
|
},
|
|
onSceneInput(e: WechatMiniprogram.Input) {
|
|
this.setData({ scene: e.detail.value })
|
|
},
|
|
|
|
openSelect() {
|
|
this.setData({ view: 'select', selectLoading: true, page: 1, hasMore: true })
|
|
this.fetchAllClothes(1)
|
|
},
|
|
|
|
backToForm() {
|
|
this.setData({ view: 'form' })
|
|
},
|
|
|
|
fetchAllClothes(page = 1) {
|
|
const cat = CATEGORIES[this.data.activeCategory].value
|
|
const params: Record<string, any> = { page, page_size: PAGE_SIZE }
|
|
if (cat) params.category_main = cat
|
|
|
|
// 请求序号:切换分类时旧响应可能后到,序号不匹配直接丢弃
|
|
const seq = ((this as any).reqSeq || 0) + 1
|
|
;(this as any).reqSeq = seq
|
|
if (page > 1) this.setData({ loadingMore: true })
|
|
|
|
request<PagedResult<ClothesItem>>({ url: '/clothes', data: params })
|
|
.then((res) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
const { list: raw, total } = normalizePaged<ClothesItem>(res)
|
|
logger.debug(LOG, '衣物列表加载成功, 分类=', cat || '全部', 'page=', page, '条数=', raw && raw.length)
|
|
const list = raw.map((it) => ({
|
|
...it,
|
|
_selected: this.data.selectedUuids.indexOf(it.clothes_uuid) >= 0,
|
|
}))
|
|
const merged = page === 1 ? list : this.data.allClothes.concat(list)
|
|
this.setData({
|
|
allClothes: merged,
|
|
selectLoading: false,
|
|
loadingMore: false,
|
|
page,
|
|
hasMore: hasMorePaged(merged, total, PAGE_SIZE),
|
|
})
|
|
})
|
|
.catch((e: any) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
logger.warn(LOG, '衣物列表加载失败, page=', page, 'code=', e && e.code)
|
|
this.setData({ selectLoading: false, loadingMore: false })
|
|
})
|
|
},
|
|
|
|
onCategoryTap(e: WechatMiniprogram.TouchEvent) {
|
|
const index = Number(e.currentTarget.dataset.index)
|
|
if (index === this.data.activeCategory) return
|
|
this.setData({ activeCategory: index, selectLoading: true, page: 1, hasMore: true })
|
|
this.fetchAllClothes(1)
|
|
},
|
|
|
|
onReachBottom() {
|
|
if (this.data.view !== 'select') return
|
|
if (!this.data.hasMore || this.data.selectLoading || this.data.loadingMore) return
|
|
this.fetchAllClothes(this.data.page + 1)
|
|
},
|
|
|
|
onToggleSelect(e: WechatMiniprogram.CustomEvent) {
|
|
const item = e.detail.item as ClothesItem
|
|
const uuids = this.data.selectedUuids
|
|
const map = this.getSelectedMap()
|
|
if (uuids.indexOf(item.clothes_uuid) >= 0) {
|
|
delete map[item.clothes_uuid]
|
|
this.syncSelect(uuids.filter((u) => u !== item.clothes_uuid))
|
|
return
|
|
}
|
|
if (uuids.length >= 10) {
|
|
logger.debug(LOG, '已选 10 件,达到上限')
|
|
wx.showToast({ title: '最多选择 10 件衣物', icon: 'none' })
|
|
return
|
|
}
|
|
map[item.clothes_uuid] = item
|
|
this.syncSelect([...uuids, item.clothes_uuid])
|
|
},
|
|
|
|
syncSelect(uuids: string[]) {
|
|
this.setData({
|
|
selectedUuids: uuids,
|
|
allClothes: this.data.allClothes.map((it) => ({
|
|
...it,
|
|
_selected: uuids.indexOf(it.clothes_uuid) >= 0,
|
|
})),
|
|
})
|
|
},
|
|
|
|
confirmSelect() {
|
|
const map = this.getSelectedMap()
|
|
// 以快照为准:只从当前分类列表里过滤会丢掉其它分类已选的衣物
|
|
const selectedItems = this.data.selectedUuids.map(
|
|
(u) => map[u] || ({ clothes_uuid: u } as ClothesItem)
|
|
)
|
|
this.setData({ view: 'form', selectedItems })
|
|
},
|
|
|
|
onSave() {
|
|
if (this.data.saving) return
|
|
const name = this.data.outfitName.trim()
|
|
if (!name) {
|
|
wx.showToast({ title: '请填写穿搭名称', icon: 'none' })
|
|
return
|
|
}
|
|
if (!this.data.selectedUuids.length) {
|
|
wx.showToast({ title: '至少选择 1 件衣物', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
const body = {
|
|
outfit_name: name,
|
|
outfit_desc: this.data.outfitDesc,
|
|
scene: this.data.scene,
|
|
items: this.data.selectedUuids.map((u) => ({ clothes_uuid: u })),
|
|
}
|
|
|
|
this.setData({ saving: true })
|
|
const req = this.data.isEdit
|
|
? request({ url: `/outfit/${this.data.uuid}`, method: 'PUT', data: body })
|
|
: request({ url: '/outfit', method: 'POST', data: body })
|
|
req
|
|
.then(() => {
|
|
logger.info(LOG, '穿搭保存成功, 编辑态=', this.data.isEdit, '件数=', body.items.length)
|
|
this.setData({ saving: false })
|
|
wx.showToast({ title: '已保存', icon: 'success' })
|
|
setTimeout(
|
|
() =>
|
|
wx.navigateBack({
|
|
fail: (e) => logger.warn(LOG, 'navigateBack 失败, err=', e && e.errMsg),
|
|
}),
|
|
500,
|
|
)
|
|
})
|
|
.catch((e: any) => {
|
|
logger.warn(LOG, '穿搭保存失败, code=', e && e.code)
|
|
this.setData({ saving: false })
|
|
})
|
|
},
|
|
|
|
onDelete() {
|
|
wx.showModal({
|
|
title: '确认删除这套穿搭?',
|
|
content: '删除后不可恢复,但不会影响衣橱内的单品。',
|
|
confirmText: '确认删除',
|
|
confirmColor: '#FF3B30',
|
|
success: (res) => {
|
|
if (!res.confirm) return
|
|
request({ url: `/outfit/${this.data.uuid}`, method: 'DELETE' })
|
|
.then(() => {
|
|
logger.info(LOG, '穿搭删除成功')
|
|
wx.showToast({ title: '已删除', icon: 'success' })
|
|
setTimeout(
|
|
() =>
|
|
wx.navigateBack({
|
|
fail: (e) => logger.warn(LOG, 'navigateBack 失败, err=', e && e.errMsg),
|
|
}),
|
|
500,
|
|
)
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '穿搭删除失败, code=', e && e.code))
|
|
},
|
|
fail: (e) => logger.warn(LOG, '删除弹窗失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
})
|