feat: 商品分类

feature/task1.0.0__0514__ch
向文可 2 years ago
parent 9578d3ccb4
commit 596ab4a404

@ -1,9 +1,37 @@
import request from '@/utils/request';
// OSS签名
export function sign(serviceName, configId) {
// return request({
// url: '/oss/oss/generateOssSignature',
// method: 'POST',
// data: {
// serviceName,
// configId,
// },
// });
console.info(serviceName, configId);
return {
accessId: 'LTAI4GHRNb5Xn2w5NeHVbR4c',
policy: 'eyJleHBpcmF0aW9uIjoiMjAyMi0wNC0xNVQyMDowODoyNi4zMTlaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJ0ZXN0LyJdXX0=',
signature: 'okaB3sNp3vzyfM0S3ypudaUAZ+0=',
dir: 'test/',
host: 'https://msb-edu-dev.oss-cn-beijing.aliyuncs.com',
expire: '1650053306',
};
}
// 上传文件
export function upload(data) {
export async function upload(serviceName, configId, file) {
let oss = await sign(serviceName, configId);
let data = new FormData();
data.append('name', file.name);
data.append('key', `${oss.dir}${'${filename}'}`);
data.append('policy', oss.policy);
data.append('OSSAccessKeyId', oss.accessId);
data.append('Signature', oss.signature);
data.append('success_action_status', 200);
data.append('file', file);
return request({
url: '/ks-admin/local/upload/file',
url: oss.host,
method: 'POST',
data,
});

@ -0,0 +1,36 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/mall/product/admin/productCategory',
method: 'get',
params,
});
};
export const create = (data) => {
return request({
url: '/mall/product/admin/productCategory',
method: 'post',
data,
});
};
export const update = (data) => {
return request({
url: '/mall/product/admin/productCategory',
method: 'put',
data,
});
};
export const remove = (params) => {
return request({
url: '/mall/product/admin/productCategory',
method: 'delete',
params,
});
};
export const sort = (data) => {
return request({
url: '/mall/product/admin/productCategory/updateSort',
method: 'post',
data,
});
};

@ -2,9 +2,10 @@
<div class="upload-box">
<el-upload
v-bind="props"
action=""
:before-upload="handleBeforeUpload"
:file-list="imgList"
:headers="headers"
:http-request="handleUpload"
list-type="picture-card"
:on-exceed="handleExceed"
:on-preview="handlePreview"
@ -24,26 +25,17 @@
</div>
</template>
<script setup lang="jsx">
import config from '@/configs';
import { upload } from '@/api/file';
import { ElMessage } from '@/plugins/element-plus';
import 'element-plus/es/components/image/style/css';
const store = useStore();
const props = defineProps({
action: {
configId: {
type: String,
default: config.baseURL + '/edu-oss/oss/fileUpload',
required: true,
},
data: {
type: Object,
default() {
return { service: 'msb-edu-course' };
},
},
headers: {
type: Object,
default() {
return {};
},
serviceName: {
type: String,
default: 'mall-product',
},
drag: {
type: Boolean,
@ -71,8 +63,6 @@
},
});
const emits = defineEmits(['update:modelValue']);
let headers = { ...props.headers };
headers['Authorization'] = store.state.local.token;
const imgList = ref([]);
const attrs = useAttrs();
watch(
@ -81,7 +71,7 @@
if (
unref(imgList)
.map((item) => item.url)
.join(',') !== value.join(',')
.join(',') !== value?.join(',')
) {
imgList.value = (value instanceof Array ? value : [value])
.filter((item) => item)
@ -133,6 +123,9 @@
}
return res;
};
const handleUpload = ({ file }) => {
return upload(props.serviceName, props.configId, file);
};
const fmtSize = computed(() => {
const units = ['byte', 'KB', 'MB', 'GB', 'TB'];
let res = props.size,

@ -0,0 +1,12 @@
<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import { ElSwitch } from 'element-plus/es/components/switch/index';
import 'element-plus/es/components/switch/style/css';
const props = defineProps({});
const attrs = useAttrs();
const slots = useSlots();
const render = () => <ElSwitch {...props} {...attrs} v-slots={slots} />;
</script>
<style lang="less" scoped></style>

@ -40,6 +40,37 @@ export default [
},
],
},
{
path: 'category',
name: 'CategoryManagement',
component: () => import('@/views/sales/category/index.vue'),
meta: {
title: '商品分类',
icon: 'barcode-box-fill',
},
children: [
{
path: 'create',
name: 'CreateCategory',
component: () => import('@/views/sales/category/form.vue'),
meta: {
title: '创建分类',
icon: 'barcode-box-fill',
hidden: true,
},
},
{
path: 'update/:id',
name: 'UpdateCategory',
component: () => import('@/views/sales/category/form.vue'),
meta: {
title: '编辑分类',
icon: 'barcode-box-fill',
hidden: true,
},
},
],
},
],
},
];

@ -0,0 +1,100 @@
import * as api from '@/api/sales/category.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
code: 'CategoryManagement',
condition: {
orderIs: [],
},
list: [],
total: 0,
summary: [],
opts: {
init: false,
visible: [
{ label: '显示', value: true },
{ label: '隐藏', value: false },
],
},
});
const getters = {};
const mutations = {
setCode: (state, data) => (state.code = data),
setCondition: (state, data) => (state.condition = data),
setList: (state, data) => (state.list = data),
setTotal: (state, data) => (state.total = data),
setSummary: (state, data) => (state.summary = data),
setOpts: (state, data) => (state.opts = data),
};
const actions = {
search: async ({ state, commit }) => {
let res = await api.search(state.condition);
commit('setList', res);
if (!res) {
ElMessage.error('查询商品分类列表失败');
}
return res;
},
load: async ({ commit }) => {
commit('setOpts', {
init: true,
visible: [
{ label: '显示', value: true },
{ label: '隐藏', value: false },
],
});
},
detail: async ({ state, dispatch }, id) => {
if (!state.list.length) {
await dispatch('search');
}
let res = state.list.find((item) => item.id === id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
save: async ({ dispatch }, data) => {
let save = data.id ? api.update : api.create;
let res = await save(data);
if (res) {
ElMessage.success('保存成功');
dispatch('search');
} else {
ElMessage.error('保存失败');
}
return res;
},
sort: async (context, data) => {
let res = await api.sort(data);
if (res) {
ElMessage.success(`移动前序号:${data.oldSort + 1};移动后序号:${data.currentSort + 1}`);
} else {
ElMessage.error('保存排序失败');
}
return res;
},
remove: async ({ dispatch }, ids) => {
if (!ids.length) {
ElMessage.warning('请选择要删除的数据');
} else {
try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
let res = await api.remove({ ids: ids.join(',') });
if (res) {
ElMessage.success('删除成功');
dispatch('search');
} else {
ElMessage.error('删除失败');
}
} catch (e) {
console.info('取消删除', e);
}
}
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -0,0 +1,117 @@
<template>
<div class="form-container">
<el-form
ref="refsForm"
v-loading="loading"
class="form-content"
label-width="100px"
:model="form"
:rules="rules"
>
<el-form-item label="上级分类" prop="parentId">
<el-cascader
v-model="form.parentId"
:options="list"
:props="{
emitPath: false,
label: 'name',
value: 'id',
}"
/>
<p class="tips">不选择分类默认为顶级分类</p>
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="分类图片" prop="picture">
<el-upload-image v-model="form.picture" config-id="product-category/" />
</el-form-item>
<el-form-item label="是否显示" prop="isEnable">
<el-radio-group v-model="form.isEnable" :opts="opts.visible" />
</el-form-item>
</el-form>
<div class="form-footer">
<el-button @click="handleCancel"></el-button>
<el-button :disabled="loading" :loading="submitting" type="primary" @click="handleSave"></el-button>
</div>
</div>
</template>
<script setup>
/* 全局 */
const store = useStore();
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
/* 表单 */
const loading = ref(false);
const submitting = ref(false);
const refsForm = ref(null);
const form = reactive({
id: null,
parentId: null,
name: null,
picture: null,
isEnable: true,
});
const rules = reactive({
name: [{ required: true, message: '分类名称不能为空' }],
picture: [{ required: true, message: '分类图片不能为空' }],
isEnable: [{ required: true, message: '是否显示不能为空' }],
});
const list = computed(() => store.state.category.list);
if (!unref(list).length) {
store.dispatch('category/search');
}
const opts = computed(() => store.state.category.opts);
if (!unref(opts).init) {
store.dispatch('category/load');
}
/* 数据 */
const handleLoad = async () => {
if (route.params.id) {
const id = +route.params.id;
if (form.id !== id) {
let res = await store.dispatch('category/detail', id);
Object.assign(form, res);
}
}
};
onActivated(handleLoad);
/* 交互 */
const handleSave = async () => {
submitting.value = true;
try {
await unref(refsForm).validate();
let data = { ...unref(form) };
let res = await store.dispatch('category/save', data);
if (res) {
if (!data.id) {
unref(refsForm).resetFields();
}
handleClose();
}
} catch (e) {
console.info('取消保存', e);
}
submitting.value = false;
};
const handleCancel = async () => {
try {
await proxy.$confirm('确定要放弃未保存的数据继续离开吗?', '数据未保存');
handleClose();
} catch (e) {
console.info('取消关闭', e);
}
};
const handleClose = () => {
router.push({ name: 'CategoryManagement' });
};
</script>
<style lang="less" scoped>
.tips {
color: @color-white2;
margin-left: @layout-space;
}
</style>

@ -0,0 +1,158 @@
<template>
<div class="list-container">
<TableList
v-loading="loading"
:code="code"
:config="config"
:data="list"
:operation="['search', 'create']"
:reset="handleReset"
sortable
title="分类"
:total="total"
@create="handleCreate"
@row-sort="handleSort"
@search="handleSearch"
>
<template #search>
<el-form inline>
<el-form-item label="是否显示" prop="visible">
<el-select v-model="state.condition.visible" :opts="opts.visible" />
</el-form-item>
</el-form>
</template>
</TableList>
</div>
</template>
<script setup lang="jsx">
import ElButton from '@/components/extra/ElButton.vue';
import ElSwitch from '@/components/extra/ElSwitch.vue';
const router = useRouter();
const store = useStore();
const loading = ref(false);
const code = computed(() => store.state.category.code);
const list = computed(() => store.state.category.list);
const total = computed(() => store.state.category.total);
const opts = computed(() => store.state.category.opts);
if (!unref(opts).init) {
store.dispatch('category/load');
}
/* 查询订单 */
const state = reactive({
condition: {
visible: null,
},
});
watch(
() => state.condition,
(value) => {
store.commit('category/setCondition', _.cloneDeep(value));
},
{ immediate: true, deep: true }
);
const handleReset = () => {
state.condition = {
visible: null,
};
};
const handleSearch = async () => {
loading.value = true;
await store.dispatch('category/search');
loading.value = false;
};
const handleCreate = () => {
router.push({ name: 'CreateCategory' });
};
const handleUpdate = (row) => {
router.push({ name: 'UpdateCategory', params: { id: row.id } });
};
const handleVisible = (row) => {
router.push({ name: 'UpdateCategory', params: { id: row.id } });
};
const handleRemove = async (rows) => {
loading.value = true;
await store.dispatch(
'category/remove',
rows.map((item) => item.id)
);
loading.value = false;
};
const handleSort = async (currentSort, oldSort) => {
loading.value = true;
await store.dispatch('category/sort', { id: unref(list)[currentSort].id, currentSort, oldSort });
loading.value = false;
};
/* 列表配置 */
const config = reactive({
page: false,
columns: [
{
label: '分类名称',
prop: 'name',
minWidth: 300,
fixed: 'left',
},
{
label: '图片',
minWidth: 300,
slots: {
default: ({ row }) => <ElImage src={row.picture} alt={row.name} height="100px" />,
},
},
{
label: '是否显示',
width: 120,
slots: {
default: ({ row }) => (
<ElSwitch value={row.isEnable} onChange={(visible) => handleVisible(row, visible)} />
),
},
},
{
label: '操作',
fixed: 'right',
width: 300,
slots: {
default: ({ row }) => (
<div>
<ElButton type="text" onClick={() => handleCreate(row)}>
新增下级
</ElButton>
<ElButton type="text" onClick={() => handleCreate(row)}>
转移商品
</ElButton>
<ElButton type="text" onClick={() => handleUpdate(row)}>
编辑
</ElButton>
<ElButton type="text" onClick={() => handleRemove([row])}>
删除
</ElButton>
</div>
),
},
},
],
});
</script>
<style lang="less" scoped>
.list-container {
display: flex;
flex-direction: column;
.common-list {
flex-shrink: 1;
}
.order-status {
display: flex;
margin-bottom: @layout-space;
li {
+ li {
margin-left: @layout-space;
}
}
}
}
</style>
Loading…
Cancel
Save