1
This commit is contained in:
59
src/views/shop/shopSpec/components/search.vue
Normal file
59
src/views/shop/shopSpec/components/search.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-input-search
|
||||
v-model:value="keywords"
|
||||
placeholder="搜索规格名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@search="search"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<span>添加规格</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
class="ele-btn-icon"
|
||||
:disabled="!selection?.length"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import type { ShopSpecParam } from '@/api/shop/shopSpec/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopSpecParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const keywords = ref('');
|
||||
|
||||
const search = () => {
|
||||
emit('search', { keywords: keywords.value || undefined });
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
</script>
|
||||
146
src/views/shop/shopSpec/components/shopSpecEdit.vue
Normal file
146
src/views/shop/shopSpec/components/shopSpecEdit.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<!-- 规格编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="isUpdate ? '编辑规格' : '添加规格'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ flex: '80px' }"
|
||||
:wrapper-col="{ flex: '1' }"
|
||||
>
|
||||
<a-form-item label="规格名称" name="specName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入规格名称,如:颜色、尺码、材质"
|
||||
v-model:value="form.specName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="数值越小越靠前"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
placeholder="可选备注说明"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addShopSpec, updateShopSpec } from '@/api/shop/shopSpec';
|
||||
import { ShopSpec } from '@/api/shop/shopSpec/model';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
|
||||
const isUpdate = ref(false);
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data?: ShopSpec | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
const form = reactive<ShopSpec>({
|
||||
specId: undefined,
|
||||
specName: undefined,
|
||||
comments: undefined,
|
||||
status: 0,
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules: Record<string, any> = {
|
||||
specName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写规格名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!formRef.value) return;
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateShopSpec : addShopSpec;
|
||||
saveOrUpdate({ ...form })
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
specId: undefined,
|
||||
specName: undefined,
|
||||
comments: undefined,
|
||||
status: 0,
|
||||
sortNumber: 100
|
||||
});
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
372
src/views/shop/shopSpec/components/specValuePanel.vue
Normal file
372
src/views/shop/shopSpec/components/specValuePanel.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<!-- 规格值管理面板(展开行内嵌) -->
|
||||
<template>
|
||||
<div class="spec-value-panel">
|
||||
<div class="spec-value-header">
|
||||
<span class="label">「{{ spec.specName }}」的规格值</span>
|
||||
<span class="tip">点击标签编辑,拖拽排序</span>
|
||||
</div>
|
||||
|
||||
<div class="spec-value-body">
|
||||
<!-- 已有规格值 tags -->
|
||||
<a-spin :spinning="loading">
|
||||
<div class="tags-wrap">
|
||||
<template v-for="item in values" :key="item.specValueId">
|
||||
<a-tag
|
||||
:closable="true"
|
||||
class="spec-tag"
|
||||
@click="openEditValue(item)"
|
||||
@close.prevent="removeValue(item)"
|
||||
>
|
||||
<span v-if="item.image" class="tag-img-wrap">
|
||||
<img :src="item.image" class="tag-img" alt="" />
|
||||
</span>
|
||||
{{ item.specValue }}
|
||||
<span v-if="item.sortNumber !== undefined" class="tag-sort">{{ item.sortNumber }}</span>
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 添加输入框 -->
|
||||
<template v-if="addingInput">
|
||||
<a-input
|
||||
ref="inputRef"
|
||||
v-model:value="newValueText"
|
||||
size="small"
|
||||
class="tag-input"
|
||||
placeholder="输入规格值后回车"
|
||||
@blur="confirmAdd"
|
||||
@keyup.enter="confirmAdd"
|
||||
@keyup.esc="cancelAdd"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag
|
||||
class="add-tag"
|
||||
@click="startAdd"
|
||||
>
|
||||
<PlusOutlined />
|
||||
添加规格值
|
||||
</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- 规格值编辑弹窗(点击已有 tag 时) -->
|
||||
<a-modal
|
||||
v-model:open="showValueEdit"
|
||||
title="编辑规格值"
|
||||
:width="420"
|
||||
:maskClosable="false"
|
||||
@ok="saveValueEdit"
|
||||
@cancel="showValueEdit = false"
|
||||
>
|
||||
<a-form
|
||||
ref="valueFormRef"
|
||||
:model="editingValue"
|
||||
:rules="valueRules"
|
||||
:label-col="{ flex: '80px' }"
|
||||
:wrapper-col="{ flex: '1' }"
|
||||
style="margin-top: 16px"
|
||||
>
|
||||
<a-form-item label="规格值" name="specValue">
|
||||
<a-input
|
||||
v-model:value="editingValue.specValue"
|
||||
placeholder="如:红色、XL、棉"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图片" name="image">
|
||||
<div class="value-image-row">
|
||||
<a-image
|
||||
v-if="editingValue.image"
|
||||
:src="editingValue.image"
|
||||
:width="56"
|
||||
:height="56"
|
||||
style="object-fit: cover; border-radius: 4px; margin-right: 8px"
|
||||
/>
|
||||
<a-upload
|
||||
:show-upload-list="false"
|
||||
accept="image/*"
|
||||
:before-upload="(file: File) => handleImageUpload(file)"
|
||||
>
|
||||
<a-button size="small">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ editingValue.image ? '更换图片' : '上传图片' }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-button
|
||||
v-if="editingValue.image"
|
||||
size="small"
|
||||
danger
|
||||
style="margin-left: 6px"
|
||||
@click="editingValue.image = ''"
|
||||
>删除</a-button>
|
||||
</div>
|
||||
<div class="ele-text-secondary" style="font-size: 12px; margin-top: 4px">
|
||||
适用于颜色类规格,上传颜色色板图片
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
v-model:value="editingValue.sortNumber"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="数值越小越靠前"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-input v-model:value="editingValue.comments" placeholder="可选" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, nextTick } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons-vue';
|
||||
import type { ShopSpec } from '@/api/shop/shopSpec/model';
|
||||
import type { ShopSpecValue } from '@/api/shop/shopSpecValue/model';
|
||||
import {
|
||||
listShopSpecValue,
|
||||
addShopSpecValue,
|
||||
updateShopSpecValue,
|
||||
removeShopSpecValue
|
||||
} from '@/api/shop/shopSpecValue';
|
||||
import { uploadFile } from '@/api/system/file';
|
||||
|
||||
const props = defineProps<{
|
||||
spec: ShopSpec;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const values = ref<ShopSpecValue[]>([]);
|
||||
|
||||
// 快捷添加
|
||||
const addingInput = ref(false);
|
||||
const newValueText = ref('');
|
||||
const inputRef = ref<any>(null);
|
||||
|
||||
// 编辑弹窗
|
||||
const showValueEdit = ref(false);
|
||||
const valueFormRef = ref<any>(null);
|
||||
const editingValue = reactive<ShopSpecValue>({
|
||||
specValueId: undefined,
|
||||
specId: undefined,
|
||||
specValue: '',
|
||||
image: '',
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
const valueRules: Record<string, any> = {
|
||||
specValue: [{ required: true, message: '请填写规格值', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 加载规格值列表
|
||||
const loadValues = async () => {
|
||||
if (!props.spec.specId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await listShopSpecValue({ specId: props.spec.specId } as any);
|
||||
values.value = data || [];
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '加载规格值失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.spec.specId,
|
||||
(id) => {
|
||||
if (id) loadValues();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 快捷添加 - 开始
|
||||
const startAdd = () => {
|
||||
addingInput.value = true;
|
||||
newValueText.value = '';
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
// 快捷添加 - 确认
|
||||
const confirmAdd = async () => {
|
||||
const val = newValueText.value.trim();
|
||||
if (!val) {
|
||||
cancelAdd();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await addShopSpecValue({
|
||||
specId: props.spec.specId,
|
||||
specValue: val,
|
||||
sortNumber: 100
|
||||
});
|
||||
message.success('添加成功');
|
||||
newValueText.value = '';
|
||||
addingInput.value = false;
|
||||
loadValues();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 快捷添加 - 取消
|
||||
const cancelAdd = () => {
|
||||
addingInput.value = false;
|
||||
newValueText.value = '';
|
||||
};
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEditValue = (item: ShopSpecValue) => {
|
||||
Object.assign(editingValue, {
|
||||
specValueId: item.specValueId,
|
||||
specId: item.specId,
|
||||
specValue: item.specValue,
|
||||
image: item.image || '',
|
||||
comments: item.comments || '',
|
||||
sortNumber: item.sortNumber ?? 100
|
||||
});
|
||||
showValueEdit.value = true;
|
||||
};
|
||||
|
||||
// 保存编辑
|
||||
const saveValueEdit = async () => {
|
||||
try {
|
||||
await valueFormRef.value?.validate();
|
||||
await updateShopSpecValue({ ...editingValue });
|
||||
message.success('修改成功');
|
||||
showValueEdit.value = false;
|
||||
loadValues();
|
||||
} catch (e: any) {
|
||||
if (e?.message) message.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除规格值
|
||||
const removeValue = (item: ShopSpecValue) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确定要删除规格值「${item.specValue}」吗?`,
|
||||
maskClosable: true,
|
||||
onOk: async () => {
|
||||
try {
|
||||
const msg = await removeShopSpecValue(item.specValueId);
|
||||
message.success(msg);
|
||||
loadValues();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '删除失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 图片上传
|
||||
const handleImageUpload = async (file: File) => {
|
||||
try {
|
||||
const res = await uploadFile(file);
|
||||
editingValue.image = res.path;
|
||||
message.success('上传成功');
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '上传失败');
|
||||
}
|
||||
return false; // 阻止 ant-design-vue 自动上传
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.spec-value-panel {
|
||||
padding: 12px 16px 8px 40px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
|
||||
.spec-value-header {
|
||||
margin-bottom: 10px;
|
||||
.label {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
.tip {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.spec-value-body {
|
||||
.tags-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.spec-tag {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 4px 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.tag-img-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.tag-img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-sort {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-left: 2px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 2px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-tag {
|
||||
cursor: pointer;
|
||||
border-style: dashed;
|
||||
color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
font-size: 13px;
|
||||
padding: 4px 10px;
|
||||
|
||||
&:hover {
|
||||
background: #bae7ff;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
width: 120px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.value-image-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
263
src/views/shop/shopSpec/index.vue
Normal file
263
src/views/shop/shopSpec/index.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="specId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
v-model:selection="selection"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.status === 0"
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
size="small"
|
||||
@change="(val: boolean) => toggleStatus(record, val)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要同步更新吗?这将把该规格的最新值更新到所有使用该模板的商品中。"
|
||||
@confirm="syncSpec(record)"
|
||||
>
|
||||
<a class="ele-text-success">同步更新</a>
|
||||
</a-popconfirm>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此规格吗?删除后相关商品规格数据将受影响!"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 展开行:规格值管理面板 -->
|
||||
<template #expandedRowRender="{ record }">
|
||||
<SpecValuePanel :spec="record" />
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopSpecEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import ShopSpecEdit from './components/shopSpecEdit.vue';
|
||||
import SpecValuePanel from './components/specValuePanel.vue';
|
||||
import {
|
||||
pageShopSpec,
|
||||
removeShopSpec,
|
||||
removeBatchShopSpec,
|
||||
updateShopSpec,
|
||||
syncGoodsSpec
|
||||
} from '@/api/shop/shopSpec';
|
||||
import type { ShopSpec, ShopSpecParam } from '@/api/shop/shopSpec/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopSpec[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopSpec | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageShopSpec({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'specId',
|
||||
key: 'specId',
|
||||
align: 'center',
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: '规格名称',
|
||||
dataIndex: 'specName',
|
||||
key: 'specName',
|
||||
align: 'left',
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
align: 'left',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
sorter: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopSpecParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopSpec) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 切换显示/隐藏状态 */
|
||||
const toggleStatus = (row: ShopSpec, val: boolean) => {
|
||||
const newStatus = val ? 0 : 1;
|
||||
updateShopSpec({ ...row, status: newStatus })
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
row.status = newStatus;
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopSpec) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopSpec(row.specId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 同步更新商品规格 */
|
||||
const syncSpec = (row: ShopSpec) => {
|
||||
const hide = message.loading('正在同步更新..', 0);
|
||||
syncGoodsSpec(row.specId!)
|
||||
.then((res) => {
|
||||
hide();
|
||||
const updatedCount = res?.data?.updatedCount ?? 0;
|
||||
message.success(`同步完成,已更新 ${updatedCount} 个商品`);
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '同步失败');
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的规格吗?删除后相关商品规格数据将受影响!',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopSpec(selection.value.map((d) => d.specId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopSpec) => {
|
||||
return {
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopSpec'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user