Commit 68906031 by wuqian

卡片组件优化

parent 2e2b72b4
<template>
<div class="commodity-card">
<div class="commodity-card-image">
<div class="before"></div>
<div class="image">
<img
:src="
typeof cardItem.variantImage === 'string'
? cardItem.variantImage
: ''
"
/>
</div>
<div class="img_top_left">
<span class="select-icon"></span>
</div>
<div class="img_top_right">
<slot name="top_right"></slot>
</div>
<div class="img_bottom_left">
<slot name="bottom_left"></slot>
</div>
<div class="img_bottom_right">
<slot name="operations"></slot>
</div>
</div>
<div class="commodity-card-info">
<slot name="images"></slot>
<slot name="info"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { PodProductList, CardOrderData } from '@/types/api/podOrder'
defineProps({
cardItem: {
type: Object as PropType<PodProductList | CardOrderData>,
required: true,
},
})
</script>
<style lang="scss" scoped>
.commodity-card {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.commodity-card-image {
position: relative;
.img_top_left {
position: absolute;
top: 5px;
left: 5px;
}
.img_top_right {
position: absolute;
top: 5px;
right: 5px;
}
.img_bottom_left {
position: absolute;
bottom: 2px;
left: 5px;
}
.img_bottom_right {
position: absolute;
bottom: 5px;
right: 5px;
:deep(.svg-icon) {
width: 2em;
height: 2em;
}
}
}
.before {
height: 0;
padding-top: 120%;
}
.image {
position: absolute;
inset: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
}
.color-image {
.color-image-item {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
}
.commodity-card-info {
flex: 1;
padding: 10px;
font-size: 14px;
background: linear-gradient(
90deg,
rgba(228, 228, 228, 0.4) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.commodity-card-name-price {
height: 30px;
line-height: 30px;
}
.commodity-card-sku {
display: flex;
align-items: center;
gap: 5px;
background-color: #fff;
}
.select-icon {
position: relative;
display: block;
width: 16px;
height: 16px;
border: 1px solid #cccccc;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.4) inset;
border-radius: 8px;
background: #fff;
}
.commodity-card.active .select-icon {
background: #4168ff;
position: relative;
border-color: #4168ff;
box-shadow: none;
font-size: 18px;
top: -1px;
left: -1px;
}
.commodity-card.active .select-icon::after {
position: absolute;
content: '';
top: 0px;
left: 4px;
width: 4px;
height: 8px;
border-width: 2px;
border-style: solid;
border-color: transparent #fff #fff transparent;
transform: rotate(45deg) scale(0.8);
}
</style>
<template>
<div class="commodity-card">
<div class="commodity-card-image">
<div class="before"></div>
<div class="image">
<img :src="cardItem.mainImage" />
</div>
<div class="commodity-card-image-select">
<span class="select-icon"></span>
</div>
<div class="operation-icon">
<slot name="operations"></slot>
</div>
</div>
<div class="commodity-card-info">
<div class="color-image flex flex-gap-10">
<div
v-for="c in cardItem.imageList"
:key="c.id"
class="color-image-item"
>
<img :src="c.imagePath" />
</div>
</div>
<slot name="time"></slot>
<div
v-if="cardItem.productName"
class="commodity-card-name-price flex flex-justify-space-between"
>
<slot name="productName"></slot>
<slot name="price"></slot>
</div>
<div class="commodity-card-sku" @click.stop="copy(cardItem.sku ?? '')">
<img src="../assets/images/id.png" height="20" />
<span>{{ cardItem.sku }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import type { TypesettingListData } from '../types/api/typesetting'
defineProps({
cardItem: {
type: Object as PropType<TypesettingListData>,
required: true,
},
})
const copy = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
</script>
<style lang="scss" scoped>
.commodity-card-image {
position: relative;
}
.before {
height: 0;
padding-top: 120%;
}
.image {
position: absolute;
inset: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
}
.color-image {
.color-image-item {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
}
.commodity-card-info {
padding: 10px;
font-size: 14px;
background: linear-gradient(
90deg,
rgba(228, 228, 228, 0.4) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.commodity-card-name-price {
height: 30px;
line-height: 30px;
}
.commodity-card-sku {
display: flex;
align-items: center;
gap: 5px;
background-color: #fff;
}
.commodity-card-image-select {
position: absolute;
top: 5px;
left: 5px;
}
.select-icon {
position: relative;
display: block;
width: 16px;
height: 16px;
border: 1px solid #cccccc;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.4) inset;
border-radius: 8px;
background: #fff;
}
.commodity-card.active .select-icon {
background: #4168ff;
position: relative;
border-color: #4168ff;
box-shadow: none;
font-size: 18px;
top: -1px;
left: -1px;
}
.commodity-card.active .select-icon::after {
position: absolute;
content: '';
top: 0px;
left: 4px;
width: 4px;
height: 8px;
border-width: 2px;
border-style: solid;
border-color: transparent #fff #fff transparent;
transform: rotate(45deg) scale(0.8);
}
.operation-icon {
position: absolute;
bottom: 5px;
right: 5px;
display: flex;
align-items: center;
:deep(.svg-icon) {
width: 2em;
height: 2em;
}
}
</style>
<template>
<div class="commodity-card" :class="{ active: active }">
<div class="commodity-card-image">
<div class="before"></div>
<div class="image">
<img :src="mainImageSrc" />
</div>
<!-- 左上角 -->
<div class="img_top_left">
<span v-if="showSelectIcon" class="select-icon"></span>
<slot name="top_left"></slot>
</div>
<!-- 右上角 -->
<div class="img_top_right">
<slot name="top_right"></slot>
</div>
<!-- 左下角 -->
<div class="img_bottom_left">
<slot name="bottom_left"></slot>
</div>
<!-- 右下角 -->
<div class="img_bottom_right">
<slot name="operations"></slot>
</div>
</div>
<div class="commodity-card-info">
<!-- 颜色图片列表 -->
<div
v-if="showImageList && hasImageList"
class="color-image flex flex-gap-10"
>
<div
v-for="(img, index) in imageList"
:key="index"
class="color-image-item"
>
<img :src="getItemImagePath(img)" />
</div>
</div>
<!-- 时间信息插槽 -->
<slot name="time"></slot>
<!-- 名称和价格 -->
<div
v-if="showProductInfo && cardItem?.productName"
class="commodity-card-name-price flex flex-justify-space-between"
>
<slot name="productName">
<span>{{ cardItem?.productName }}</span>
</slot>
<slot name="price"></slot>
</div>
<!-- SKU信息 -->
<div
v-if="showSku && cardItem?.sku"
class="commodity-card-sku"
@click.stop="copy(String(cardItem?.sku))"
>
<img src="../assets/images/id.png" height="20" />
<span>{{ cardItem?.sku }}</span>
</div>
<slot name="images"></slot>
<!-- 自定义信息 -->
<slot name="info"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, withDefaults } from 'vue'
import { ElMessage } from 'element-plus'
import { PodProductList, CardOrderData } from '@/types/api/podOrder'
import type { TypesettingListData } from '../types/api/typesetting'
// 定义通用字段接口,用于处理动态属性
interface CommonFields {
[key: string]: unknown;
variantImage?: string;
mainImage?: string;
sku?: string;
productName?: string;
}
// 定义图片列表项接口
interface ImageListItem {
[key: string]: unknown;
imagePath?: string;
}
// 创建一个工具类型,用于使用字符串索引访问对象属性
type IndexableObject = Record<string, unknown>;
// 扩展现有类型以确保它们有通用字段
type CardItem = PodProductList | CardOrderData | TypesettingListData | CommonFields;
// 定义 props 类型
interface Props {
cardItem: CardItem;
active?: boolean;
showSelectIcon?: boolean;
showSku?: boolean;
showProductInfo?: boolean;
showImageList?: boolean;
imageField?: string;
imageListField?: string;
imagePathField?: string;
}
// 定义默认值
const props = withDefaults(defineProps<Props>(), {
active: false,
showSelectIcon: true,
showSku: false,
showProductInfo: false,
showImageList: false,
imageField: 'variantImage',
imageListField: 'imageList',
imagePathField: 'imagePath',
})
// 获取主图片源
const mainImageSrc = computed<string>(() => {
const item = props.cardItem as IndexableObject;
// 使用索引访问避免联合类型的属性访问问题
if (
props.imageField === 'variantImage' &&
typeof item[props.imageField] === 'string'
) {
return item[props.imageField] as string;
}
if (
props.imageField === 'mainImage' &&
typeof item[props.imageField] === 'string'
) {
return item[props.imageField] as string;
}
// 默认返回空字符串
return '';
})
// 获取图片列表
const imageList = computed<ImageListItem[]>(() => {
const item = props.cardItem as IndexableObject;
const list = item[props.imageListField];
return Array.isArray(list) ? list as ImageListItem[] : [];
})
// 判断是否有图片列表
const hasImageList = computed<boolean>(() => {
return imageList.value.length > 0;
})
// 获取图片列表项的图片路径
function getItemImagePath(item: IndexableObject): string {
return (item[props.imagePathField] as string) || '';
}
const copy = (text: string) => {
navigator.clipboard.writeText(text);
ElMessage.success('复制成功');
}
</script>
<style lang="scss" scoped>
.commodity-card {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.commodity-card-image {
position: relative;
.img_top_left {
position: absolute;
top: 5px;
left: 5px;
}
.img_top_right {
position: absolute;
top: 5px;
right: 5px;
}
.img_bottom_left {
position: absolute;
bottom: 5px;
left: 5px;
}
.img_bottom_right {
position: absolute;
bottom: 5px;
right: 5px;
display: flex;
align-items: center;
:deep(.svg-icon) {
width: 2em;
height: 2em;
}
}
}
.before {
height: 0;
padding-top: 120%;
}
.image {
position: absolute;
inset: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
}
.color-image {
.color-image-item {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
}
.commodity-card-info {
flex: 1;
padding: 10px;
font-size: 14px;
background: linear-gradient(
90deg,
rgba(228, 228, 228, 0.4) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.commodity-card-name-price {
height: 30px;
line-height: 30px;
}
.commodity-card-sku {
display: flex;
align-items: center;
gap: 5px;
background-color: #fff;
cursor: pointer;
}
.select-icon {
position: relative;
display: block;
width: 16px;
height: 16px;
border: 1px solid #cccccc;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.4) inset;
border-radius: 8px;
background: #fff;
}
.commodity-card.active .select-icon {
background: #4168ff;
position: relative;
border-color: #4168ff;
box-shadow: none;
font-size: 18px;
top: -1px;
left: -1px;
}
.commodity-card.active .select-icon::after {
position: absolute;
content: '';
top: 0px;
left: 4px;
width: 4px;
height: 8px;
border-width: 2px;
border-style: solid;
border-color: transparent #fff #fff transparent;
transform: rotate(45deg) scale(0.8);
}
</style>
......@@ -39,7 +39,12 @@
style="width: 100px"
placeholder="客户"
>
<el-option v-for="item in userMarkList" :key="item" :value="item" :label="item"></el-option>
<el-option
v-for="item in userMarkList"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</ElFormItem>
<ElFormItem label="SKU">
......@@ -153,11 +158,11 @@
>打印生产单</ElButton
>
</span>
<!-- <span v-if="status === 'TO_BE_CONFIRMED'" class="item">-->
<!-- <ElButton type="success" dark @click="exportManuscript"-->
<!-- >导出生产单</ElButton-->
<!-- >-->
<!-- </span>-->
<!-- <span v-if="status === 'TO_BE_CONFIRMED'" class="item">-->
<!-- <ElButton type="success" dark @click="exportManuscript"-->
<!-- >导出生产单</ElButton-->
<!-- >-->
<!-- </span>-->
<span class="item">
<ElButton type="warning" @click="addInternalTag"
>添加内部标签</ElButton
......@@ -199,10 +204,16 @@
<span v-if="status === 'WAIT_SHIPMENT'" class="item">
<ElButton type="warning" @click="completeDelivery">完成发货</ElButton>
</span>
<span v-if="['TO_BE_CONFIRMED','IN_PRODUCTION'].includes(status)" class="item">
<span
v-if="['TO_BE_CONFIRMED', 'IN_PRODUCTION'].includes(status)"
class="item"
>
<ElButton type="danger" @click="voidedBtn">作废</ElButton>
</span>
<span v-if="['WAIT_SHIPMENT','IN_PRODUCTION'].includes(status)" class="item">
<span
v-if="['WAIT_SHIPMENT', 'IN_PRODUCTION'].includes(status)"
class="item"
>
<ElButton type="warning" is-dark @click="applyForReplacement">
申请补胚</ElButton
>
......@@ -232,9 +243,12 @@
class="card-list_item"
@click="cardClick(cardItem)"
>
<CardWrapper
<CommonCard
:card-item="cardItem"
:class="{ active: isSelectStatused(cardItem) }"
:active="isSelectStatused(cardItem)"
:show-sku="false"
:show-product-info="false"
:image-field="'variantImage'"
@contextmenu.prevent="(v) => rightClick(v, cardItem)"
>
<!-- <template #top_right> 工厂类型 </template> -->
......@@ -361,7 +375,9 @@
:offset="0"
style="text-align: right"
>
<span v-if="['INVALID','TO_BE_CONFIRMED'].includes(status)">
<span
v-if="['INVALID', 'TO_BE_CONFIRMED'].includes(status)"
>
数量:{{ cardItem?.num }}
</span>
<template v-else>
......@@ -517,10 +533,9 @@
</span>
</el-col>
</el-row>
</div>
</template>
</CardWrapper>
</CommonCard>
</div>
</div>
</div>
......@@ -564,7 +579,7 @@
/></el-icon>
</div>
<div
v-if="['PART_SHIPPING','TO_BE_RECEIPT' ].includes(status)"
v-if="['PART_SHIPPING', 'TO_BE_RECEIPT'].includes(status)"
class="operation-item"
>
<el-button
......@@ -774,7 +789,10 @@
</div>
<div class="pagination">
<div>
<span>已选择 <span style="color: red">{{selection.length}}</span> 条数据</span>
<span
>已选择
<span style="color: red">{{ selection.length }}</span> 条数据</span
>
</div>
<el-pagination
v-model:current-page="currentPage"
......@@ -788,7 +806,6 @@
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
<el-dialog
......@@ -910,7 +927,9 @@
</el-dialog>
<right-menu
ref="rightMenuRef"
:show_copy_shop_number="['IN_PRODUCTION','TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status)"
:show_copy_shop_number="
['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status)
"
@change="rightChange"
/>
<fastProduction
......@@ -1021,10 +1040,16 @@ import {
ShipmentForm,
} from '@/types/api/order'
import fastProduction from '../fastProduction.vue'
import CardWrapper from '@/components/CardPods.vue'
import { CustomColumn } from '@/types/table'
// import CardWrapper from '@/components/CardPods.vue'
import { useValue } from '@/utils/hooks/useValue'
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
reactive,
ref,
} from 'vue'
import dayjs from 'dayjs'
import useElTableColumnWidth from '@/utils/hooks/useElTableColumnWidth'
import ProductInfo from '../ProductInfo.vue'
......@@ -1041,11 +1066,13 @@ import {
CardOrderData,
} from '@/types/api/podOrder'
import { ArrowUp, ArrowDown, EditPen } from '@element-plus/icons-vue'
import { getLogisticsCompanyList,getUserMarkList } from '@/api/common'
import { getLogisticsCompanyList, getUserMarkList } from '@/api/common'
import { ElButton, type FormRules } from 'element-plus'
import { showConfirm } from '@/utils/ui'
import { filePath } from '@/api/axios'
import { CustomColumn } from '@/types/table'
import LogList from '@/components/LogList.vue'
import CommonCard from '@/components/CommonCard.vue'
const tableRef = ref()
const loading = ref(false)
const currentPage = ref(1)
......@@ -1089,16 +1116,18 @@ const rightClick = (e: MouseEvent, item: PodProductList | CardOrderData) => {
el: e,
})
}
const handleSizeChange = (size:number)=>{
const handleSizeChange = (size: number) => {
pageSize.value = size
loadDiffList()
}
const handleCurrentChange = (page:number)=>{
const handleCurrentChange = (page: number) => {
currentPage.value = page
loadDiffList()
}
const rightChange = async (code: string) => {
const flag = ['IN_PRODUCTION','TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status.value)
const flag = ['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(
status.value,
)
if (code === 'check_all') {
if (flag) {
selection.value = JSON.parse(JSON.stringify(CardOrderList.value))
......@@ -1325,19 +1354,23 @@ const loadCardList = async () => {
}
const loadOrderList = async () => {
try {
const res = await getOrderList({
...searchForm.value,
status: status.value,
timeType: searchForm.value.timeType,
startTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[0]
: null,
endTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[1]
: null
},currentPage.value,pageSize.value)
const res = await getOrderList(
{
...searchForm.value,
status: status.value,
timeType: searchForm.value.timeType,
startTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[0]
: null,
endTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[1]
: null,
},
currentPage.value,
pageSize.value,
)
tableData.value = res.data.records
total.value = res.data.total
} catch (error) {
......@@ -1420,7 +1453,7 @@ const fastClose = () => {
detailVisible.value = false
}
// 根据不同状态调用不同接口
const loadDiffList = async () => {
const loadDiffList = async () => {
if (
[
'IN_PRODUCTION',
......@@ -1485,6 +1518,7 @@ const tableColumns = computed<CustomColumn<CardOrderData[]>>(() => {
},
]
})
// const {
// loading,
// currentPage,
......@@ -1617,7 +1651,9 @@ const refreshProduct = async () => {
return
}
const ids = []
if (['IN_PRODUCTION','TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status.value)) {
if (
['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status.value)
) {
ids.push(
...selection.value.map(
(item: CardOrderData | PodProductList) => item.podOrderId as number,
......@@ -1648,7 +1684,7 @@ const openAll = (row: CardOrderData) => {
}
// 表格和卡片的选中值:有两种约束
const selection = ref<(CardOrderData | PodProductList)[]>([])
const tableData = ref<(OrderData)[]>([])
const tableData = ref<OrderData[]>([])
const handleSelectionChange = (s: CardOrderData[]) => {
selection.value = s
}
......@@ -1691,7 +1727,7 @@ const applyForReplacement = async () => {
})
}
let input = {
value:''
value: '',
}
try {
input = await ElMessageBox.prompt('', '补胚数量', {
......@@ -1733,7 +1769,9 @@ const downloadMaterial = async () => {
})
}
const ids = []
if (['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status.value)) {
if (
['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status.value)
) {
ids.push(
...selection.value.map((item: CardOrderData | PodProductList) => item.id),
)
......@@ -2360,7 +2398,7 @@ onBeforeUnmount(() => {
font-size: 14px;
}
}
.pagination{
.pagination {
display: flex;
align-items: center;
}
......
......@@ -78,9 +78,13 @@
class="card-list_item"
@click="cardClick(cardItem)"
>
<CardWrapper
<CommonCard
:card-item="cardItem"
:class="{ active: isSelectStatused(cardItem) }"
:active="isSelectStatused(cardItem)"
:show-sku="true"
:show-product-info="true"
:show-image-list="true"
image-field="mainImage"
>
<template #productName>
<span class="commodity-card-name">{{
......@@ -178,7 +182,7 @@
</div>
</div>
</template>
</CardWrapper>
</CommonCard>
</div>
</div>
</div>
......@@ -255,7 +259,6 @@ import type {
TypesettingListData,
LogList,
} from '@/types/api/typesetting'
import CardWrapper from '@/components/CardWrapper.vue'
import { useValue } from '@/utils/hooks/useValue'
import usePageList from '@/utils/hooks/usePageList'
import {
......@@ -270,6 +273,7 @@ import reviewModel from './reviewModel.vue'
import submitReviewModal from './submitReviewModal.vue'
import dayjs from 'dayjs'
import { showConfirm } from '@/utils/ui'
import CommonCard from '@/components/CommonCard.vue'
const treeData = ref<TreeData[]>([])
const treeRef = ref()
const treeId = ref('201')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment