feat: 商品SKU

feature/task1.0.0__0514__ch
向文可 3 years ago
parent c6f5ddb3f9
commit 4c4e1972de

@ -1,2 +1,2 @@
VITE_BASE_URL=http://k8s-horse-gateway.mashibing.cn/ VITE_BASE_URL=https://k8s-horse-gateway.mashibing.cn/
VITE_REQUEST_TIMEOUT=20000 VITE_REQUEST_TIMEOUT=20000

@ -50,7 +50,7 @@ export const createAttrs = (data) => {
export const updateAttrs = (data) => { export const updateAttrs = (data) => {
return request({ return request({
url: '/mall/product/admin/productAttributeGroup/' + data.id, url: '/mall/product/admin/productAttributeGroup/' + data.id,
method: 'post', method: 'put',
data, data,
}); });
}; };
@ -78,7 +78,7 @@ export const createAttrsValue = (data) => {
export const updateAttrsValue = (data) => { export const updateAttrsValue = (data) => {
return request({ return request({
url: '/mall/product/admin/productAttribute/' + data.id, url: '/mall/product/admin/productAttribute/' + data.id,
method: 'post', method: 'put',
data, data,
}); });
}; };

@ -51,8 +51,7 @@ const actions = {
return res; return res;
}, },
save: async ({ dispatch }, data) => { save: async ({ dispatch }, data) => {
let save = data.id ? api.update : api.create; let res = await api.create(data);
let res = await save(data);
if (res) { if (res) {
ElMessage.success('保存成功'); ElMessage.success('保存成功');
dispatch('search'); dispatch('search');

@ -28,7 +28,7 @@ const actions = {
} else { } else {
try { try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作'); await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
let res = await api.remove({ id: ids.join(',') }); let res = await api.removeAttrs({ id: ids.join(',') });
if (res) { if (res) {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
dispatch('search'); dispatch('search');

@ -0,0 +1,49 @@
import * as api from '@/api/sales/product.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({});
const getters = {};
const mutations = {};
const actions = {
search: async (context, id) => {
let res = await api.searchAttrsValue(id);
if (!res) {
ElMessage.error('查询商品属性分组列表失败');
}
return res || [];
},
save: async ({ dispatch }, data) => {
let save = data.id ? api.updateAttrsValue : api.createAttrsValue;
let res = await save(data);
if (res) {
ElMessage.success('保存成功');
dispatch('search');
} else {
ElMessage.error('保存失败');
}
return res;
},
remove: async ({ dispatch }, ids) => {
if (!ids.length) {
ElMessage.warning('请选择要删除的数据');
} else {
try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
let res = await api.removeAttrsValue({ id: ids.join(',') });
if (res) {
ElMessage.success('删除成功');
dispatch('search');
} else {
ElMessage.error('删除失败');
}
} catch (e) {
console.info('取消删除', e);
}
}
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -1,10 +1,9 @@
import * as api from '@/api/sales/category.js'; import * as categoryAPI from '@/api/sales/category.js';
import * as api from '@/api/sales/product.js';
import { ElMessage } from '@/plugins/element-plus'; import { ElMessage } from '@/plugins/element-plus';
const state = () => ({ const state = () => ({
code: 'ProductPicker', code: 'ProductPicker',
condition: { condition: {},
orderIs: [],
},
list: [], list: [],
total: 0, total: 0,
summary: [], summary: [],
@ -23,9 +22,10 @@ const mutations = {
setOpts: (state, data) => (state.opts = data), setOpts: (state, data) => (state.opts = data),
}; };
const actions = { const actions = {
search: async ({ state, commit }) => { search: async ({ state, commit, rootGetters }) => {
let res = await api.search(state.condition); let res = await api.search({ ...rootGetters['local/page'](state.code), ...state.condition });
commit('setList', res); commit('setList', res?.records || []);
commit('setTotal', res?.total || 0);
if (!res) { if (!res) {
ElMessage.error('查询商品列表失败'); ElMessage.error('查询商品列表失败');
} }
@ -35,7 +35,7 @@ const actions = {
commit('setOpts', { commit('setOpts', {
...state.opts, ...state.opts,
init: true, init: true,
category: await api.search(), category: await categoryAPI.search({ pageIndex: 1, length: 9999 }),
}); });
}, },
}; };

@ -52,6 +52,8 @@
phone: '', phone: '',
password: '', password: '',
verifyCode: '', verifyCode: '',
systemId: 1,
clientId: 2,
}); });
const rules = reactive({ const rules = reactive({

@ -80,6 +80,7 @@
await store.dispatch('limitActivity/search'); await store.dispatch('limitActivity/search');
loading.value = false; loading.value = false;
}; };
onActivated(handleSearch);
/* 表单 */ /* 表单 */
const refsForm = ref(null); const refsForm = ref(null);
@ -191,10 +192,18 @@
slots: { slots: {
default: ({ row }) => ( default: ({ row }) => (
<div> <div>
<ElButton type="text" onClick={() => handleUpdateTime(row)}> <ElButton
disabled={row.activityState !== 1}
type="text"
onClick={() => handleUpdateTime(row)}
>
设置时段 设置时段
</ElButton> </ElButton>
<ElButton type="text" onClick={() => handleUpdateProduct(row)}> <ElButton
type="text"
disabled={row.activityState !== 1}
onClick={() => handleUpdateProduct(row)}
>
设置商品 设置商品
</ElButton> </ElButton>
<ElButton type="text" disabled={row.activityState !== 1} onClick={() => handleCreate(row)}> <ElButton type="text" disabled={row.activityState !== 1} onClick={() => handleCreate(row)}>

@ -33,7 +33,7 @@
</el-form> </el-form>
</template> </template>
</TableList> </TableList>
<ProductPicker ref="refsPicker" /> <ProductPicker ref="refsPicker" @pick="handlePick" />
</div> </div>
</template> </template>
@ -47,9 +47,6 @@
const list = computed(() => store.state.limitProduct.list); const list = computed(() => store.state.limitProduct.list);
const total = computed(() => store.state.limitProduct.total); const total = computed(() => store.state.limitProduct.total);
const opts = computed(() => store.state.limitProduct.opts); const opts = computed(() => store.state.limitProduct.opts);
if (!unref(opts).init) {
store.dispatch('limitProduct/load', route.params.id);
}
/* 查询订单 */ /* 查询订单 */
const state = reactive({ const state = reactive({
@ -68,6 +65,7 @@
}; };
const handleSearch = async () => { const handleSearch = async () => {
loading.value = true; loading.value = true;
await store.dispatch('limitProduct/load', route.params.id);
await store.dispatch('limitProduct/search'); await store.dispatch('limitProduct/search');
loading.value = false; loading.value = false;
}; };
@ -92,6 +90,7 @@
}, },
{ deep: true } { deep: true }
); );
onActivated(handleSearch);
const refsPicker = ref(null); const refsPicker = ref(null);
const handleCreate = async () => { const handleCreate = async () => {
@ -106,6 +105,14 @@
const handleSku = (row) => { const handleSku = (row) => {
console.info(row); console.info(row);
}; };
const handlePick = async (rows) => {
loading.value = true;
await store.dispatch('limitProduct/save', {
activityTimeId: state.condition.activityTimeId,
productId: rows.map((item) => item.id),
});
loading.value = false;
};
/* 列表配置 */ /* 列表配置 */
const config = reactive({ const config = reactive({

@ -75,7 +75,9 @@
}; };
// //
const handlePick = async (rows) => { const handlePick = async (rows) => {
console.info(rows); loading.value = true;
await store.dispatch('recommendProduct/save', { productIdList: rows.map((item) => item.id) });
loading.value = false;
}; };
/* 操作 */ /* 操作 */

@ -1,25 +1,24 @@
<template> <template>
<div v-loading="loading" class="step-container"> <div v-loading="loading" class="step-container">
<el-scrollbar class="step-content"> <el-scrollbar class="step-content">
<ul class="flex"> <el-form>
<li class="flex"> <el-form-item label="属性规格">
<span class="label">属性规格:</span>
<el-input v-model="skuName"> <el-input v-model="skuName">
<template #append> <template #append>
<el-button @click="handleAddSku"><el-icon name="Check" /></el-button> <el-button @click="handleAddSku"><el-icon name="Check" /></el-button>
</template> </template>
</el-input> </el-input>
</li> </el-form-item>
<li v-for="(sku, i) in form.skus" :key="i" class="flex"> <el-form-item v-for="(sku, i) in form.skus" :key="i" :label="sku.name">
<div class="flex">
<div class="left"> <div class="left">
<span class="label">{{ sku.name }}:</span>
<el-tag <el-tag
v-for="(item, j) in sku.values" v-for="(item, j) in sku.values"
:key="i + '' + j" :key="i + '' + j"
closable closable
@close="handleDelValue(sku, j)" @close="handleDelValue(sku, j)"
> >
{{ item }} {{ item.name }}
</el-tag> </el-tag>
<el-input v-model="skuValue[i]"> <el-input v-model="skuValue[i]">
<template #append> <template #append>
@ -34,37 +33,40 @@
</el-button> </el-button>
<el-button type="text" @click="handleDelSku(i)"></el-button> <el-button type="text" @click="handleDelSku(i)"></el-button>
</div> </div>
</li> </div>
</ul> </el-form-item>
</el-form>
<el-table border :data="form.skuInfos" style="width: 100%"> <el-table border :data="form.skuInfos" style="width: 100%">
<el-table-column <el-table-column
v-for="(sku, index) in form.skus" v-for="(sku, index) in form.skus"
:key="index" :key="index"
align="center"
header-align="center"
:label="sku.name" :label="sku.name"
:prop="'attr' + index" :prop="sku.id + ''"
/> />
<el-table-column label="售价(元)" prop="sales"> <el-table-column align="center" header-align="center" label="售价(元)" prop="sales" width="160px">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.sales" /> <el-input-number v-model="row.sales" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="商品库存" prop="inventory"> <el-table-column align="center" header-align="center" label="商品库存" prop="inventory" width="160px">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.inventory" /> <el-input-number v-model="row.inventory" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="成本价(元)" prop="price"> <el-table-column align="center" header-align="center" label="成本价(元)" prop="price" width="160px">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.price" /> <el-input-number v-model="row.price" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="库存预警值" prop="warn"> <el-table-column align="center" header-align="center" label="库存预警值" prop="warn" width="160px">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.warn" /> <el-input-number v-model="row.warn" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="SKU编号" prop="no" /> <el-table-column align="center" header-align="center" label="SKU编号" min-width="100px" prop="no" />
<el-table-column label="启用" prop="enabled"> <el-table-column align="center" header-align="center" label="启用" prop="enabled" width="80px">
<template #default="{ row }"> <template #default="{ row }">
<el-switch v-model="row.enabled" /> <el-switch v-model="row.enabled" />
</template> </template>
@ -103,7 +105,15 @@
const id = route.params.id; const id = route.params.id;
if (id && id !== state.form.id) { if (id && id !== state.form.id) {
state.form.id = id; state.form.id = id;
state.form.skus = await store.dispatch('productAttrsGroup/search', id); let res = await store.dispatch('productAttrsGroup/search', id);
let arr = [];
res.forEach((item) => {
arr.push(
store.dispatch('productAttrsValue/search', item.id).then((res) => (item.values = res))
);
});
await Promise.all(arr);
state.form.skus = res;
} }
}; };
onActivated(handleLoad); onActivated(handleLoad);
@ -114,8 +124,8 @@
(info, sku) => { (info, sku) => {
let res = []; let res = [];
info.forEach((row) => { info.forEach((row) => {
sku.values.forEach((value) => { (sku.values || []).forEach((value) => {
res.push({ ...row, ['attr' + state.form.skus.indexOf(sku)]: value }); res.push({ ...row, [sku.id + '']: value.name });
}); });
}); });
return res; return res;
@ -194,15 +204,10 @@
.flex { .flex {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; justify-content: space-between;
.el-input { .el-input {
width: 160px; width: 160px;
} }
li {
align-items: center;
&:not(:first-child) {
justify-content: space-between;
}
.el-tag { .el-tag {
+ .el-tag { + .el-tag {
margin-left: @layout-space; margin-left: @layout-space;
@ -211,14 +216,9 @@
margin-left: @layout-space; margin-left: @layout-space;
} }
} }
+ li {
margin-top: @layout-space;
}
} }
} .el-input-number {
.label { width: 100%;
word-break: keep-all;
margin-right: @layout-space;
} }
} }
.step-footer { .step-footer {

@ -16,9 +16,9 @@
<el-form-item label="商品名称" prop="name"> <el-form-item label="商品名称" prop="name">
<el-input v-model="state.condition.name" /> <el-input v-model="state.condition.name" />
</el-form-item> </el-form-item>
<el-form-item label="商品分类" prop="category"> <el-form-item label="商品分类" prop="categoryId">
<el-select <el-select
v-model="state.condition.category" v-model="state.condition.categoryId"
:config="{ label: 'name', value: 'id' }" :config="{ label: 'name', value: 'id' }"
:opts="opts.category" :opts="opts.category"
/> />
@ -51,8 +51,9 @@
const state = reactive({ const state = reactive({
condition: { condition: {
name: null, name: null,
category: null, categoryId: null,
}, },
picked: null,
}); });
watch( watch(
() => state.condition, () => state.condition,
@ -64,7 +65,7 @@
const handleReset = () => { const handleReset = () => {
state.condition = { state.condition = {
name: null, name: null,
category: null, categoryId: null,
}; };
}; };
const handleSearch = async () => { const handleSearch = async () => {
@ -77,6 +78,7 @@
// //
const handleShow = () => { const handleShow = () => {
visible.value = true; visible.value = true;
handleSearch();
}; };
// //
const handleClose = () => { const handleClose = () => {
@ -85,6 +87,7 @@
// //
const handlePick = async () => { const handlePick = async () => {
emits('pick', [...unref(refsTable).selection]); emits('pick', [...unref(refsTable).selection]);
handleClose();
}; };
defineExpose({ defineExpose({
show: handleShow, show: handleShow,
@ -108,7 +111,7 @@
label: '商品图片', label: '商品图片',
minWidth: 100, minWidth: 100,
slots: { slots: {
default: ({ row }) => <ElImage src={row.picture} alt={row.name} height="100px" />, default: ({ row }) => <ElImage src={row.mainPicture} alt={row.name} height="100px" />,
}, },
}, },
{ {
@ -118,7 +121,7 @@
}, },
{ {
label: '商品价格', label: '商品价格',
prop: 'price', prop: 'startingPrice',
minWidth: 160, minWidth: 160,
}, },
], ],

@ -22,7 +22,7 @@ export default (configEnv) => {
'/api': { '/api': {
// target: 'http://192.168.10.109:8090/', // 显雨 // target: 'http://192.168.10.109:8090/', // 显雨
// target: 'http://192.168.10.251:8090', // 高玉 // target: 'http://192.168.10.251:8090', // 高玉
target: 'http://k8s-horse-gateway.mashibing.cn/', // 测试地址 target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址
// target: 'https://gateway.mashibing.cn', // 预发地址 // target: 'https://gateway.mashibing.cn', // 预发地址
// target: 'https://gateway.mashibing.com', // 生产环境 // target: 'https://gateway.mashibing.com', // 生产环境
changeOrigin: true, changeOrigin: true,

Loading…
Cancel
Save