feat: 商品管理接口

feature/task1.0.0__0514__ch
向文可 2 years ago
parent bc9638e74c
commit c6f5ddb3f9

@ -0,0 +1,91 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/mall/product/admin/product/page',
method: 'get',
params,
});
};
export const detail = (id) => {
return request({
url: '/mall/product/admin/product/' + id,
method: 'get',
});
};
export const create = (data) => {
return request({
url: '/mall/product/admin/product',
method: 'post',
data,
});
};
export const update = (data) => {
return request({
url: '/mall/product/admin/product/' + data.id,
method: 'put',
data,
});
};
export const remove = (params) => {
return request({
url: '/mall/product/admin/product',
method: 'delete',
params,
});
};
export const searchAttrs = (id) => {
return request({
url: '/mall/product/admin/productAttributeGroup/' + id,
method: 'get',
});
};
export const createAttrs = (data) => {
return request({
url: '/mall/product/admin/productAttributeGroup',
method: 'post',
data,
});
};
export const updateAttrs = (data) => {
return request({
url: '/mall/product/admin/productAttributeGroup/' + data.id,
method: 'post',
data,
});
};
export const removeAttrs = (params) => {
return request({
url: '/mall/product/admin/productAttributeGroup',
method: 'delete',
params,
});
};
export const searchAttrsValue = (id) => {
return request({
url: '/mall/product/admin/productAttribute/' + id,
method: 'get',
});
};
export const createAttrsValue = (data) => {
return request({
url: '/mall/product/admin/productAttribute',
method: 'post',
data,
});
};
export const updateAttrsValue = (data) => {
return request({
url: '/mall/product/admin/productAttribute/' + data.id,
method: 'post',
data,
});
};
export const removeAttrsValue = (params) => {
return request({
url: '/mall/product/admin/productAttribute',
method: 'delete',
params,
});
};

@ -28,7 +28,7 @@
const editor = ref(null);
const options = {
bounds: '.el-editor',
debug: 'warn',
debug: 'error',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
@ -137,7 +137,7 @@
margin-left: 20px;
border: 1px solid #d1d5db;
&.--app {
width: 320px;
width: 375px;
}
&.--pc {
width: 640px;

@ -1,4 +1,5 @@
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, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
code: 'ProductManagement',
@ -11,6 +12,24 @@ const state = () => ({
opts: {
init: false,
category: [],
status: [
{
label: '全部商品',
value: 0,
},
{
label: '已上架',
value: 1,
},
{
label: '未上架',
value: 2,
},
{
label: '库存紧张',
value: 3,
},
],
tag: [
{
label: '爆款',
@ -53,14 +72,15 @@ const mutations = {
setOpts: (state, data) => (state.opts = data),
};
const actions = {
search: async ({ state, commit }) => {
search: async ({ state, commit, rootGetters }) => {
let data = { ...state.condition };
data.productStatus = (data.productStatus || []).join(',');
if (data.productStatus === '0') {
delete data.productStatus;
}
let res = await api.search(data);
commit('setList', res);
let res = await api.search({ ...rootGetters['local/page'](state.code), ...data });
commit('setList', res?.records || []);
commit('setTotal', res?.total || 0);
if (!res) {
ElMessage.error('查询商品列表失败');
}
@ -70,14 +90,11 @@ const actions = {
commit('setOpts', {
...state.opts,
init: true,
category: await api.search(),
category: await categoryAPI.search(),
});
},
detail: async ({ state, dispatch }, id) => {
if (!state.list.length) {
await dispatch('search');
}
let res = state.list.find((item) => item.id === id);
detail: async (context, id) => {
let res = await api.detail(id);
if (!res) {
ElMessage.error('加载详情失败');
}

@ -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.searchAttrs(id);
if (!res) {
ElMessage.error('查询商品属性分组列表失败');
}
return res || [];
},
save: async ({ dispatch }, data) => {
let save = data.id ? api.updateAttrs : api.createAttrs;
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.remove({ id: ids.join(',') });
if (res) {
ElMessage.success('删除成功');
dispatch('search');
} else {
ElMessage.error('删除失败');
}
} catch (e) {
console.info('取消删除', e);
}
}
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -2,9 +2,9 @@
<div v-loading="loading" class="step-container">
<el-scrollbar class="step-content">
<el-form ref="refsForm" label-width="110px" :model="form" :rules="rules">
<el-form-item label="商品分类" prop="category">
<el-form-item label="商品分类" prop="categoryId">
<el-cascader
v-model="form.category"
v-model="form.categoryId"
:options="opts.category"
:props="{
checkStrictly: true,
@ -16,25 +16,28 @@
}"
/>
</el-form-item>
<el-form-item label="商品分类" prop="category">
<el-input v-model="form.category" maxlength="50" />
<el-form-item label="商品名称" prop="name">
<el-input v-model="form.name" maxlength="50" />
</el-form-item>
<el-form-item label="商品备注" prop="category">
<el-input v-model="form.category" type="textarea" />
<el-form-item label="商品备注" prop="remark">
<el-input v-model="form.remark" :autosize="{ minRows: 3, maxRows: 3 }" type="textarea" />
</el-form-item>
<el-form-item label="商品上架" prop="category">
<el-switch v-model="form.category" />
<el-form-item label="商家推荐" prop="recommend">
<el-switch v-model="form.recommend" />
</el-form-item>
<el-form-item label="商品标签" prop="category">
<el-checkbox-group v-model="form.tag" :opts="opts.tag" />
</el-form-item>
<el-form-item label="限购设置" prop="limit">
<el-form-item label="限购设置" prop="singleBuyLimit">
<el-radio-group v-model="form.limit" :opts="opts.limit" />
<el-input-number v-show="form.limit === 1" v-model="form.limitValue" />
<el-input-number v-show="form.limit === 1" v-model="form.singleBuyLimit" />
</el-form-item>
<el-form-item label="邮费设置" prop="postage">
<el-form-item label="邮费设置" prop="remoteAreaPostage">
<el-radio-group v-model="form.postage" :opts="opts.postage" />
<el-input-number v-show="form.postage === 0" v-model="form.postageValue" />
<el-input-number v-show="form.postage === 0" v-model="form.remoteAreaPostage" />
</el-form-item>
<el-form-item label="商品图片" prop="pictureList">
<el-upload-image v-model="form.pictureList" config-id="product" :limit="5" />
</el-form-item>
<el-form-item label="商品详情" prop="detail">
<el-editor v-model="form.detail" />
</el-form-item>
</el-form>
</el-scrollbar>
@ -57,14 +60,41 @@
refsForm: null,
currentTab: 'APP',
form: {
id: '123',
id: null,
categoryId: null,
name: null,
remark: null,
recommend: false,
singleBuyLimit: 0,
limit: 0,
limitValue: 0,
postage: 0,
postageValue: 10,
remoteAreaPostage: 10,
pictureList: [],
detail: null,
isEnable: false,
},
rules: {
categoryId: [{ required: true, message: '商品分类不能为空' }],
name: [{ required: true, message: '商品名称不能为空' }],
remark: [{ required: true, message: '商品备注不能为空' }],
recommend: [{ required: true, message: '商家推荐不能为空' }],
singleBuyLimit: [{ required: true, message: '限购设置不能为空' }],
remoteAreaPostage: [{ required: true, message: '邮费设置不能为空' }],
pictureList: [{ required: true, message: '商品图片不能为空' }],
detail: [{ required: true, message: '商品详情不能为空' }],
},
rules: {},
});
watch(
() => state.form.limit,
(value) => {
if (value) {
state.form.singleBuyLimit = 10;
} else {
state.form.singleBuyLimit = 0;
}
},
{ immediate: true }
);
const opts = computed(() => store.state.product.opts);
if (!unref(opts).init) {
store.dispatch('product/load');
@ -81,6 +111,7 @@
state.loading = true;
try {
await state.refsForm.validate();
await store.dispatch('product/save', state.form);
router.push({
name: 'UpdateProduct',
params: {

@ -1,91 +1,75 @@
<template>
<div v-loading="loading" class="step-container">
<el-scrollbar class="step-content">
<el-form ref="refsForm" label-width="110px" :model="form" :rules="rules">
<el-form-item label="商品属性" prop="category">
<ul class="flex">
<li class="flex">
<span class="label">属性规格:</span>
<el-input v-model="skuName">
<template #append>
<el-button @click="handleAddSku"><el-icon name="Check" /></el-button>
</template>
</el-input>
</li>
<li v-for="(sku, i) in form.skus" :key="i" class="flex">
<div class="left">
<span class="label">{{ sku.name }}:</span>
<el-tag
v-for="(item, j) in sku.values"
:key="i + '' + j"
closable
@close="handleDelValue(sku, j)"
>
{{ item }}
</el-tag>
<el-input v-model="skuValue[i]">
<template #append>
<el-button @click="handleAddValue(sku, i)"><el-icon name="Check" /></el-button>
</template>
</el-input>
</div>
<div class="right">
<el-button :disabled="i === 0" type="text" @click="handleMove(i, i - 1)">
上移
</el-button>
<el-button
:disabled="i === form.skus.length - 1"
type="text"
@click="handleMove(i, i + 1)"
>
下移
</el-button>
<el-button type="text" @click="handleDelSku(i)"></el-button>
</div>
</li>
</ul>
<el-table border :data="form.skuInfos" style="width: 100%">
<el-table-column
v-for="(sku, index) in form.skus"
:key="index"
:label="sku.name"
:prop="'attr' + index"
/>
<el-table-column label="售价(元)" prop="sales">
<template #default="{ row }">
<el-input-number v-model="row.sales" />
<ul class="flex">
<li class="flex">
<span class="label">属性规格:</span>
<el-input v-model="skuName">
<template #append>
<el-button @click="handleAddSku"><el-icon name="Check" /></el-button>
</template>
</el-input>
</li>
<li v-for="(sku, i) in form.skus" :key="i" class="flex">
<div class="left">
<span class="label">{{ sku.name }}:</span>
<el-tag
v-for="(item, j) in sku.values"
:key="i + '' + j"
closable
@close="handleDelValue(sku, j)"
>
{{ item }}
</el-tag>
<el-input v-model="skuValue[i]">
<template #append>
<el-button @click="handleAddValue(sku, i)"><el-icon name="Check" /></el-button>
</template>
</el-table-column>
<el-table-column label="商品库存" prop="inventory">
<template #default="{ row }">
<el-input-number v-model="row.inventory" />
</template>
</el-table-column>
<el-table-column label="成本价(元)" prop="price">
<template #default="{ row }">
<el-input-number v-model="row.price" />
</template>
</el-table-column>
<el-table-column label="库存预警值" prop="warn">
<template #default="{ row }">
<el-input-number v-model="row.warn" />
</template>
</el-table-column>
<el-table-column label="SKU编号" prop="no" />
<el-table-column label="启用" prop="enabled">
<template #default="{ row }">
<el-switch v-model="row.enabled" />
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item label="商品图片" prop="picture">
<el-upload-image v-model="form.picture" config-id="product" :limit="5" />
</el-form-item>
<el-form-item label="商品详情" prop="desc">
<el-editor v-model="form.desc" />
</el-form-item>
</el-form>
</el-input>
</div>
<div class="right">
<el-button :disabled="i === 0" type="text" @click="handleMove(i, i - 1)">上移</el-button>
<el-button :disabled="i === form.skus.length - 1" type="text" @click="handleMove(i, i + 1)">
下移
</el-button>
<el-button type="text" @click="handleDelSku(i)"></el-button>
</div>
</li>
</ul>
<el-table border :data="form.skuInfos" style="width: 100%">
<el-table-column
v-for="(sku, index) in form.skus"
:key="index"
:label="sku.name"
:prop="'attr' + index"
/>
<el-table-column label="售价(元)" prop="sales">
<template #default="{ row }">
<el-input-number v-model="row.sales" />
</template>
</el-table-column>
<el-table-column label="商品库存" prop="inventory">
<template #default="{ row }">
<el-input-number v-model="row.inventory" />
</template>
</el-table-column>
<el-table-column label="成本价(元)" prop="price">
<template #default="{ row }">
<el-input-number v-model="row.price" />
</template>
</el-table-column>
<el-table-column label="库存预警值" prop="warn">
<template #default="{ row }">
<el-input-number v-model="row.warn" />
</template>
</el-table-column>
<el-table-column label="SKU编号" prop="no" />
<el-table-column label="启用" prop="enabled">
<template #default="{ row }">
<el-switch v-model="row.enabled" />
</template>
</el-table-column>
</el-table>
</el-scrollbar>
<div class="step-footer">
<el-button type="primary" @click="handleSave"></el-button>
@ -109,23 +93,17 @@
skuName: null,
skuValue: [],
form: {
id: '123',
id: null,
skus: [],
skuInfos: [],
picture: [],
desc: null,
},
rules: {},
});
const opts = computed(() => store.state.product.opts);
if (!unref(opts).init) {
store.dispatch('product/load');
}
const handleLoad = async () => {
const id = route.params.id;
if (id && id !== state.form.id) {
let res = await store.dispatch('product/detail', id);
Object.assign(state.form, res);
state.form.id = id;
state.form.skus = await store.dispatch('productAttrsGroup/search', id);
}
};
onActivated(handleLoad);
@ -194,7 +172,6 @@
return {
...toRefs(state),
opts,
handleSave,
handleAddSku,
handleDelSku,
@ -215,13 +192,8 @@
flex: 1;
overflow: auto;
.flex {
display: flex;
.el-form-item {
width: 50%;
}
}
ul.flex {
width: 100%;
display: flex;
flex-direction: column;
.el-input {
width: 160px;

@ -32,20 +32,13 @@
<el-form-item label="商品名称" prop="name">
<el-input v-model="state.condition.name" />
</el-form-item>
<el-form-item label="商品分类" prop="category">
<el-form-item label="商品分类" prop="categoryId">
<el-select
v-model="state.condition.category"
v-model="state.condition.categoryId"
:config="{ label: 'name', value: 'id' }"
:opts="opts.category"
/>
</el-form-item>
<el-form-item label="标签" prop="status">
<el-select
v-model="state.condition.tag"
:config="{ label: 'name', value: 'id' }"
:opts="opts.tag"
/>
</el-form-item>
</el-form>
</template>
</TableList>
@ -70,13 +63,9 @@
/* 查询订单 */
const state = reactive({
condition: {
orderNo: null,
userPhone: null,
orderSource: null,
productStatus: [0],
dateRange: [],
startTime: null,
endTime: null,
name: null,
categoryId: null,
productStatus: [],
},
});
watch(
@ -95,13 +84,9 @@
);
const handleReset = () => {
state.condition = {
orderNo: null,
userPhone: null,
orderSource: null,
productStatus: [0],
dateRange: [],
startTime: null,
endTime: null,
name: null,
categoryId: null,
productStatus: [],
};
};
const handleStatus = (status) => {
@ -129,6 +114,7 @@
await store.dispatch('product/search');
loading.value = false;
};
onActivated(handleSearch);
/* 新增 */
const handleCreate = () => {
@ -160,17 +146,18 @@
});
};
/* 商品标签 */
const handleTag = (row, tag) => {
row[tag] = true;
};
/* SKU */
const handleSku = (row) => {
console.info(row);
router.push({
name: 'UpdateProduct',
params: {
id: row.id,
step: 2,
},
});
};
/* SKU */
/* 上下架 */
const handleEnabled = (row) => {
console.info(row);
};
@ -188,7 +175,7 @@
label: '商品图片',
minWidth: 100,
slots: {
default: ({ row }) => <ElImage src={row.picture} alt={row.name} height="100px" />,
default: ({ row }) => <ElImage src={row.mainPicture} alt={row.name} height="100px" />,
},
},
{
@ -198,43 +185,20 @@
},
{
label: '价格',
prop: 'payAmount',
prop: 'startingPrice',
minWidth: 120,
},
{
label: '标签',
width: 160,
slots: {
default: ({ row }) => (
<div>
<ElSwitch value={row.hot} onInput={() => handleTag(row, 'hot')} active-text="" />
<ElSwitch
value={row.recommend}
onInput={() => handleTag(row, 'recommend')}
active-text="推荐"
/>
<ElSwitch
value={row.special}
onInput={() => handleTag(row, 'special')}
active-text="特价"
/>
</div>
),
},
},
{
label: '排序',
width: 120,
prop: 'orderSourceDesc',
},
{
label: 'SKU库存',
width: 120,
slots: {
default: ({ row }) => (
<ElButton type="text" onClick={() => handleSku(row)}>
<ElIcon name="Edit" />
</ElButton>
<div>
<div>{row.totalStock || 0}</div>
<ElButton type="text" onClick={() => handleSku(row)}>
<ElIcon name="Edit" size="20" />
</ElButton>
</div>
),
},
},
@ -242,15 +206,9 @@
label: '上/下架',
width: 120,
slots: {
default: ({ row }) => <ElSwitch value={row.isEnabled} onInput={() => handleEnabled(row)} />,
default: ({ row }) => <ElSwitch v-model={row.isEnable} onChange={() => handleEnabled(row)} />,
},
},
{
label: '销量',
prop: 'submitTime',
width: 180,
sortable: true,
},
{
label: '操作',
fixed: 'right',
@ -258,12 +216,12 @@
slots: {
default: ({ row }) => (
<div>
<ElButton type="text" onClick={() => handleDetail(row)}>
查看
</ElButton>
<ElButton type="text" onClick={() => handleUpdate(row)}>
编辑
</ElButton>
<ElButton type="text" onClick={() => handleDetail(row)}>
预览
</ElButton>
<ElButton type="text" onClick={() => handleRemove(row)}>
删除
</ElButton>

Loading…
Cancel
Save