feat: 产品列表与解决方案列表的排序
All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m4s
All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m4s
- 为产品/解决方案类型添加sort字段用于排序 - 各个栏目的类型按照sort升序排序 - 更新directus类型
This commit is contained in:
@ -19,6 +19,7 @@ export const useProductList = () => {
|
|||||||
{
|
{
|
||||||
translations: ['id', 'name'],
|
translations: ['id', 'name'],
|
||||||
},
|
},
|
||||||
|
'sort',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const useSolutionList = () => {
|
|||||||
'id',
|
'id',
|
||||||
'cover',
|
'cover',
|
||||||
{
|
{
|
||||||
type: ['id', { translations: ['id', 'name'] }],
|
type: ['id', { translations: ['id', 'name'] }, 'sort'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
translations: ['id', 'title', 'summary'],
|
translations: ['id', 'title', 'summary'],
|
||||||
|
|||||||
@ -14,6 +14,7 @@ describe('toProductListView', () => {
|
|||||||
product_type: {
|
product_type: {
|
||||||
id: 1,
|
id: 1,
|
||||||
translations: [{ id: 20, name: 'Type Name' }],
|
translations: [{ id: 20, name: 'Type Name' }],
|
||||||
|
sort: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,7 +23,11 @@ describe('toProductListView', () => {
|
|||||||
name: 'Product Name',
|
name: 'Product Name',
|
||||||
summary: 'Product Summary',
|
summary: 'Product Summary',
|
||||||
cover: 'cover-file-uuid-1234',
|
cover: 'cover-file-uuid-1234',
|
||||||
product_type: 'Type Name',
|
product_type: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Type Name',
|
||||||
|
sort: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,6 +39,7 @@ describe('toProductListView', () => {
|
|||||||
product_type: {
|
product_type: {
|
||||||
id: 20,
|
id: 20,
|
||||||
translations: [],
|
translations: [],
|
||||||
|
sort: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,7 +48,11 @@ describe('toProductListView', () => {
|
|||||||
name: '',
|
name: '',
|
||||||
summary: '',
|
summary: '',
|
||||||
cover: 'cover-file-uuid-1234',
|
cover: 'cover-file-uuid-1234',
|
||||||
product_type: '',
|
product_type: {
|
||||||
|
id: 20,
|
||||||
|
name: '',
|
||||||
|
sort: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 将Directus返回的ProductType数据转换为ProductTypeView视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的ProductType数据
|
||||||
|
* @returns 转换后的ProductTypeView对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toProductTypeView(rawProductType);
|
||||||
|
*/
|
||||||
|
export function toProductTypeView(raw: ProductType): ProductTypeView {
|
||||||
|
const trans = raw.translations?.[0] ?? { name: '' };
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
name: trans.name,
|
||||||
|
sort: raw?.sort ?? 999,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 Directus返回的 Product 数据转换为 ProductListView 视图模型
|
* 将 Directus返回的 Product 数据转换为 ProductListView 视图模型
|
||||||
*
|
*
|
||||||
@ -11,10 +30,8 @@ export function toProductListView(raw: Product): ProductListView {
|
|||||||
const trans = raw.translations?.[0] ?? { name: '', summary: '' };
|
const trans = raw.translations?.[0] ?? { name: '', summary: '' };
|
||||||
|
|
||||||
const type = isObject<ProductType>(raw.product_type)
|
const type = isObject<ProductType>(raw.product_type)
|
||||||
? raw.product_type.translations.length === 0
|
? toProductTypeView(raw.product_type)
|
||||||
? ''
|
: undefined;
|
||||||
: raw.product_type.translations[0].name
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: raw.id,
|
id: raw.id,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ describe('toSolutionListView', () => {
|
|||||||
type: {
|
type: {
|
||||||
id: 1,
|
id: 1,
|
||||||
translations: [{ id: 1, name: 'Type Name' }],
|
translations: [{ id: 1, name: 'Type Name' }],
|
||||||
|
sort: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,7 +21,11 @@ describe('toSolutionListView', () => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
title: 'Solution Title',
|
title: 'Solution Title',
|
||||||
summary: 'Solution Summary',
|
summary: 'Solution Summary',
|
||||||
solution_type: 'Type Name',
|
solution_type: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Type Name',
|
||||||
|
sort: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,6 +36,7 @@ describe('toSolutionListView', () => {
|
|||||||
type: {
|
type: {
|
||||||
id: 1,
|
id: 1,
|
||||||
translations: [],
|
translations: [],
|
||||||
|
sort: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,7 +44,11 @@ describe('toSolutionListView', () => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
title: '',
|
title: '',
|
||||||
summary: '',
|
summary: '',
|
||||||
solution_type: '',
|
solution_type: {
|
||||||
|
id: 1,
|
||||||
|
name: '',
|
||||||
|
sort: 999,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 将 Directus 返回的 SolutionType 数据转换为 SolutionTypeView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 SolutionType 数据
|
||||||
|
* @returns 转换后的 SolutionTypeView 对象
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toSolutionTypeView(rawSolutionType);
|
||||||
|
*/
|
||||||
|
export function toSolutionTypeView(raw: SolutionType): SolutionTypeView {
|
||||||
|
const trans = raw.translations?.[0] ?? { name: '' };
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
name: trans.name,
|
||||||
|
sort: raw?.sort ?? 999,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 Directus 返回的 Solution 数据转换为 SolutionListView 视图模型
|
* 将 Directus 返回的 Solution 数据转换为 SolutionListView 视图模型
|
||||||
*
|
*
|
||||||
@ -16,10 +37,8 @@ export function toSolutionListView(raw: Solution): SolutionListView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const type = isObject<SolutionType>(raw.type)
|
const type = isObject<SolutionType>(raw.type)
|
||||||
? raw.type.translations.length === 0
|
? toSolutionTypeView(raw.type)
|
||||||
? ''
|
: undefined;
|
||||||
: raw.type.translations[0].name
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: raw.id,
|
id: raw.id,
|
||||||
|
|||||||
@ -1,3 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 产品类型视图模型
|
||||||
|
* 用于产品列表页的section渲染与排序
|
||||||
|
*/
|
||||||
|
export interface ProductTypeView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 类型名 **/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 排序字段 **/
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品列表视图模型
|
* 产品列表视图模型
|
||||||
* 用于产品列表(/products)渲染的数据结构
|
* 用于产品列表(/products)渲染的数据结构
|
||||||
@ -13,7 +28,7 @@ export interface ProductListView {
|
|||||||
summary: string;
|
summary: string;
|
||||||
|
|
||||||
/** 产品类型 **/
|
/** 产品类型 **/
|
||||||
product_type: string;
|
product_type: ProductTypeView;
|
||||||
|
|
||||||
/** 产品封面(图片的id) **/
|
/** 产品封面(图片的id) **/
|
||||||
cover: string;
|
cover: string;
|
||||||
|
|||||||
@ -1,3 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 解决方案类型视图模型
|
||||||
|
* 用于解决方案列表页标签栏的渲染与排序
|
||||||
|
*/
|
||||||
|
export interface SolutionTypeView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 类型名 **/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 排序字段 **/
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解决方案列表模型
|
* 解决方案列表模型
|
||||||
* 用于解决方案列表(/solutions)渲染的数据结构
|
* 用于解决方案列表(/solutions)渲染的数据结构
|
||||||
@ -13,7 +28,7 @@ export interface SolutionListView {
|
|||||||
summary: string;
|
summary: string;
|
||||||
|
|
||||||
/** 解决方案类型 **/
|
/** 解决方案类型 **/
|
||||||
solution_type: string;
|
solution_type: SolutionTypeView;
|
||||||
|
|
||||||
/** 解决方案封面(图片id) **/
|
/** 解决方案封面(图片id) **/
|
||||||
cover: string;
|
cover: string;
|
||||||
|
|||||||
@ -8,14 +8,14 @@
|
|||||||
<div class="products-container">
|
<div class="products-container">
|
||||||
<el-collapse v-model="activeNames" class="product-collapse">
|
<el-collapse v-model="activeNames" class="product-collapse">
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
v-for="(group, type) in groupedProducts"
|
v-for="[key, value] in Object.entries(groupedProducts)"
|
||||||
:key="type"
|
:key="key"
|
||||||
:title="type || '未分类'"
|
:title="key || '未分类'"
|
||||||
:name="type || 'no-category'"
|
:name="key || 'no-category'"
|
||||||
>
|
>
|
||||||
<div class="group-list">
|
<div class="group-list">
|
||||||
<product-card
|
<product-card
|
||||||
v-for="product in group"
|
v-for="product in value.data"
|
||||||
:key="product.id"
|
:key="product.id"
|
||||||
:slug="product.id.toString()"
|
:slug="product.id.toString()"
|
||||||
:image-url="getImageUrl(product.cover.toString())"
|
:image-url="getImageUrl(product.cover.toString())"
|
||||||
@ -51,25 +51,23 @@
|
|||||||
productsRaw.value.map((item) => toProductListView(item))
|
productsRaw.value.map((item) => toProductListView(item))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.debug('products: ', products.value);
|
||||||
|
|
||||||
// 按类型分组
|
// 按类型分组
|
||||||
// 兼容 product_type 既可能为对象也可能为字符串
|
|
||||||
const groupedProducts = computed(() => {
|
const groupedProducts = computed(() => {
|
||||||
const groups: Record<string, ProductListView[]> = {};
|
const groups: Record<string, { data: ProductListView[]; sort: number }> =
|
||||||
|
{};
|
||||||
for (const prod of products.value) {
|
for (const prod of products.value) {
|
||||||
let typeKey = '';
|
const typeKey = prod.product_type?.name ?? '';
|
||||||
if (typeof prod.product_type === 'string') {
|
if (!groups[typeKey]) {
|
||||||
typeKey = prod.product_type;
|
groups[typeKey] = { data: [], sort: prod.product_type?.sort ?? 999 };
|
||||||
} else if (
|
|
||||||
prod.product_type &&
|
|
||||||
typeof prod.product_type === 'object' &&
|
|
||||||
'name' in prod.product_type
|
|
||||||
) {
|
|
||||||
typeKey = prod.product_type || '';
|
|
||||||
}
|
}
|
||||||
if (!groups[typeKey]) groups[typeKey] = [];
|
groups[typeKey]?.data.push(prod);
|
||||||
groups[typeKey]?.push(prod);
|
|
||||||
}
|
}
|
||||||
return groups;
|
const sortedGroups = Object.fromEntries(
|
||||||
|
Object.entries(groups).sort(([, a], [, b]) => a.sort - b.sort)
|
||||||
|
);
|
||||||
|
return sortedGroups;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(groupedProducts, () => {
|
watch(groupedProducts, () => {
|
||||||
|
|||||||
@ -19,14 +19,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="(group, type) in groupedSolutions"
|
v-for="[key, value] in Object.entries(groupedSolutions)"
|
||||||
:key="type"
|
:key="key"
|
||||||
:label="type || '未分类'"
|
:label="key || '未分类'"
|
||||||
:name="type || 'no-category'"
|
:name="key || 'no-category'"
|
||||||
>
|
>
|
||||||
<div class="solution-list">
|
<div class="solution-list">
|
||||||
<solution-card
|
<solution-card
|
||||||
v-for="solution in group"
|
v-for="solution in value.data"
|
||||||
:key="solution.id"
|
:key="solution.id"
|
||||||
:document-id="solution.id.toString()"
|
:document-id="solution.id.toString()"
|
||||||
:cover-url="getImageUrl(solution.cover || '')"
|
:cover-url="getImageUrl(solution.cover || '')"
|
||||||
@ -63,22 +63,19 @@
|
|||||||
|
|
||||||
// 按类型分组
|
// 按类型分组
|
||||||
const groupedSolutions = computed(() => {
|
const groupedSolutions = computed(() => {
|
||||||
const gourps: Record<string, SolutionListView[]> = {};
|
const groups: Record<string, { data: SolutionListView[]; sort: number }> =
|
||||||
|
{};
|
||||||
for (const sol of solutions.value) {
|
for (const sol of solutions.value) {
|
||||||
let typeKey = '';
|
const typeKey = sol.solution_type?.name ?? '';
|
||||||
if (typeof sol.solution_type === 'string') {
|
if (!groups[typeKey]) {
|
||||||
typeKey = sol.solution_type;
|
groups[typeKey] = { data: [], sort: sol.solution_type?.sort ?? 999 };
|
||||||
} else if (
|
|
||||||
sol.solution_type &&
|
|
||||||
typeof sol.solution_type === 'object' &&
|
|
||||||
'type' in sol.solution_type
|
|
||||||
) {
|
|
||||||
typeKey = sol.solution_type || '';
|
|
||||||
}
|
}
|
||||||
if (!gourps[typeKey]) gourps[typeKey] = [];
|
groups[typeKey]?.data.push(sol);
|
||||||
gourps[typeKey]?.push(sol);
|
|
||||||
}
|
}
|
||||||
return gourps;
|
const sortedGroups = Object.fromEntries(
|
||||||
|
Object.entries(groups).sort(([, a], [, b]) => a.sort - b.sort)
|
||||||
|
);
|
||||||
|
return sortedGroups;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(error, (value) => {
|
watch(error, (value) => {
|
||||||
|
|||||||
@ -142,6 +142,7 @@ export interface ProductType {
|
|||||||
id: number;
|
id: number;
|
||||||
/** @description 当前产品条目的状态 */
|
/** @description 当前产品条目的状态 */
|
||||||
status?: 'published' | 'draft' | 'archived';
|
status?: 'published' | 'draft' | 'archived';
|
||||||
|
sort?: number | null;
|
||||||
/** @description i18n文本 */
|
/** @description i18n文本 */
|
||||||
translations?: ProductTypesTranslation[] | null;
|
translations?: ProductTypesTranslation[] | null;
|
||||||
}
|
}
|
||||||
@ -231,6 +232,7 @@ export interface SolutionType {
|
|||||||
/** @primaryKey */
|
/** @primaryKey */
|
||||||
id: number;
|
id: number;
|
||||||
status?: 'published' | 'draft' | 'archived';
|
status?: 'published' | 'draft' | 'archived';
|
||||||
|
sort?: number | null;
|
||||||
/** @description i18n字段 */
|
/** @description i18n字段 */
|
||||||
translations?: SolutionTypesTranslation[] | null;
|
translations?: SolutionTypesTranslation[] | null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user