打包文件
This commit is contained in:
parent
7e4c1f7daa
commit
1ab653eebe
|
@ -2,13 +2,13 @@
|
||||||
# https://cn.vitejs.dev/guide/env-and-mode.html#modes
|
# https://cn.vitejs.dev/guide/env-and-mode.html#modes
|
||||||
# NODE_ENV = development
|
# NODE_ENV = development
|
||||||
|
|
||||||
VITE_PUBLIC_PATH = ./
|
VITE_PUBLIC_PATH = /shop-admin/
|
||||||
|
|
||||||
# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||||
VITE_ROUTER_HISTORY = "hash"
|
VITE_ROUTER_HISTORY = "hash"
|
||||||
|
|
||||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
||||||
VITE_CDN = true
|
VITE_CDN = false
|
||||||
|
|
||||||
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
||||||
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
|
@ -16,4 +16,4 @@ VITE_CDN = true
|
||||||
VITE_COMPRESSION = "none"
|
VITE_COMPRESSION = "none"
|
||||||
|
|
||||||
# 后端地址
|
# 后端地址
|
||||||
VITE_APP_BASE_API = '/stage-api'
|
VITE_APP_BASE_API = '/shop-back-end'
|
|
@ -8,4 +8,5 @@ package.json
|
||||||
commitlint.config.js
|
commitlint.config.js
|
||||||
postcss.config.js
|
postcss.config.js
|
||||||
tailwind.config.js
|
tailwind.config.js
|
||||||
stylelint.config.js
|
stylelint.config.js
|
||||||
|
build
|
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# shellcheck source=./_/husky.sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx --no-install commitlint --edit "$1"
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
command_exists () {
|
|
||||||
command -v "$1" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Workaround for Windows 10, Git Bash and Pnpm
|
|
||||||
if command_exists winpty && test -t 1; then
|
|
||||||
exec < /dev/tty
|
|
||||||
fi
|
|
|
@ -1,8 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
||||||
"{!(package)*.json}": ["prettier --write--parser json"],
|
|
||||||
"package.json": ["prettier --write"],
|
|
||||||
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
|
|
||||||
"*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],
|
|
||||||
"*.md": ["prettier --write"]
|
|
||||||
};
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
. "$(dirname "$0")/common.sh"
|
|
||||||
|
|
||||||
[ -n "$CI" ] && exit 0
|
|
||||||
|
|
||||||
# Format and submit code according to lintstagedrc.js configuration
|
|
||||||
npm run lint:lint-staged
|
|
||||||
|
|
||||||
npm run lint:pretty
|
|
|
@ -33,5 +33,10 @@
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"iconify",
|
"iconify",
|
||||||
"Qrcode"
|
"Qrcode"
|
||||||
]
|
],
|
||||||
|
"marscode.codeCompletionPro": {
|
||||||
|
"enableCodeCompletionPro": true
|
||||||
|
},
|
||||||
|
"marscode.enableInlineCommand": true,
|
||||||
|
"marscode.chatLanguage": "cn"
|
||||||
}
|
}
|
|
@ -17,15 +17,6 @@ export function viteBuildInfo(): Plugin {
|
||||||
outDir = resolvedConfig.build?.outDir ?? "dist";
|
outDir = resolvedConfig.build?.outDir ?? "dist";
|
||||||
},
|
},
|
||||||
buildStart() {
|
buildStart() {
|
||||||
console.log(
|
|
||||||
bold(
|
|
||||||
green(
|
|
||||||
`👏欢迎使用${blue(
|
|
||||||
"[Agileboot全栈项目]"
|
|
||||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/valarchie/agileboot-back-end`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (config.command === "build") {
|
if (config.command === "build") {
|
||||||
startTime = dayjs(new Date());
|
startTime = dayjs(new Date());
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ export interface CategoryDTO {
|
||||||
createTime?: Date;
|
createTime?: Date;
|
||||||
updaterId?: number;
|
updaterId?: number;
|
||||||
updateTime?: Date;
|
updateTime?: Date;
|
||||||
deleted?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分类请求参数 */
|
/** 分类请求参数 */
|
||||||
|
@ -54,7 +53,7 @@ export const updateCategoryApi = (categoryId: number, data: CategoryRequest) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 删除分类 */
|
/** 删除分类 */
|
||||||
export const deleteCategoryApi = (categoryId: number) => {
|
export const deleteCategoryApi = (categoryId: number | string) => {
|
||||||
return http.request<ResponseData<void>>("delete", `/shop/category/${categoryId}`);
|
return http.request<ResponseData<void>>("delete", `/shop/category/${categoryId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,13 @@ export interface GoodsQuery extends BasePageQuery {
|
||||||
/** 商品DTO */
|
/** 商品DTO */
|
||||||
export interface GoodsDTO {
|
export interface GoodsDTO {
|
||||||
goodsId?: number;
|
goodsId?: number;
|
||||||
goodsName?: string;
|
goodsName: string;
|
||||||
categoryId?: number;
|
categoryId: number;
|
||||||
price?: number;
|
price: number;
|
||||||
stock?: number;
|
stock: number;
|
||||||
status?: number;
|
status: number;
|
||||||
coverImg?: string;
|
coverImg: string;
|
||||||
goodsDetail?: string;
|
goodsDetail: string;
|
||||||
creatorId?: number;
|
creatorId?: number;
|
||||||
createTime?: Date;
|
createTime?: Date;
|
||||||
updaterId?: number;
|
updaterId?: number;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
|
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
|
||||||
);
|
);
|
||||||
} catch (t) {
|
} catch (t) {
|
||||||
console && console.log(t);
|
// console && console.log(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(n = function () {
|
(n = function () {
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
c = document.createElement("div");
|
c = document.createElement("div");
|
||||||
(c.innerHTML = e._iconfont_svg_string_2208059),
|
(c.innerHTML = e._iconfont_svg_string_2208059),
|
||||||
(c = c.getElementsByTagName("svg")[0]) &&
|
(c = c.getElementsByTagName("svg")[0]) &&
|
||||||
(c.setAttribute("aria-hidden", "true"),
|
(c.setAttribute("aria-hidden", "true"),
|
||||||
(c.style.position = "absolute"),
|
(c.style.position = "absolute"),
|
||||||
(c.style.width = 0),
|
(c.style.width = 0),
|
||||||
(c.style.height = 0),
|
(c.style.height = 0),
|
||||||
|
@ -42,11 +42,11 @@
|
||||||
? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
|
? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
|
||||||
? setTimeout(n, 0)
|
? setTimeout(n, 0)
|
||||||
: ((l = function () {
|
: ((l = function () {
|
||||||
document.removeEventListener("DOMContentLoaded", l, !1), n();
|
document.removeEventListener("DOMContentLoaded", l, !1), n();
|
||||||
}),
|
}),
|
||||||
document.addEventListener("DOMContentLoaded", l, !1))
|
document.addEventListener("DOMContentLoaded", l, !1))
|
||||||
: document.attachEvent &&
|
: document.attachEvent &&
|
||||||
((i = n),
|
((i = n),
|
||||||
(o = e.document),
|
(o = e.document),
|
||||||
(a = !1),
|
(a = !1),
|
||||||
v(),
|
v(),
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from "vue";
|
||||||
|
import { FormInstance, FormRules } from "element-plus";
|
||||||
|
import { updateCategoryApi, addCategoryApi } from "@/api/shop/category";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import Confirm from "@iconify-icons/ep/check";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "refresh"]);
|
||||||
|
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const formData = reactive({
|
||||||
|
categoryId: 0,
|
||||||
|
categoryName: "",
|
||||||
|
sort: 0,
|
||||||
|
description: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
categoryName: [{
|
||||||
|
required: true,
|
||||||
|
message: "分类名称必填",
|
||||||
|
trigger: "blur"
|
||||||
|
}],
|
||||||
|
sort: [{
|
||||||
|
required: true,
|
||||||
|
message: "排序值必填",
|
||||||
|
trigger: "blur",
|
||||||
|
type: "number"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
const initForm = () => {
|
||||||
|
if (props.row?.categoryId) {
|
||||||
|
isEdit.value = true;
|
||||||
|
Object.assign(formData, props.row);
|
||||||
|
} else {
|
||||||
|
isEdit.value = false;
|
||||||
|
formData.categoryId = 0;
|
||||||
|
formData.categoryName = "";
|
||||||
|
formData.sort = 0;
|
||||||
|
formData.description = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交处理
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateCategoryApi(formData.categoryId, formData);
|
||||||
|
} else {
|
||||||
|
await addCategoryApi(formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success(`${isEdit.value ? "修改" : "新增"}成功`);
|
||||||
|
emit("refresh");
|
||||||
|
closeDialog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("表单提交失败", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
emit("update:visible", false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听visible变化初始化表单
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
val => {
|
||||||
|
if (val) initForm();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog :title="isEdit ? '编辑分类' : '新增分类'" :model-value="visible" width="500px" @close="closeDialog">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px" label-position="right">
|
||||||
|
<el-form-item label="分类名称" prop="categoryName">
|
||||||
|
<el-input v-model="formData.categoryName" placeholder="请输入分类名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" :min="0" controls-position="right" class="!w-full" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="描述" prop="description">
|
||||||
|
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入分类描述" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="closeDialog">取消</el-button>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
|
||||||
|
确认
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from "vue";
|
||||||
|
import { FormInstance, FormRules } from "element-plus";
|
||||||
|
import { addCategoryApi, updateCategoryApi } from "@/api/shop/category";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import Confirm from "@iconify-icons/ep/check";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "refresh"]);
|
||||||
|
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const formData = reactive({
|
||||||
|
categoryId: 0,
|
||||||
|
categoryName: "",
|
||||||
|
sort: 0,
|
||||||
|
description: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
categoryName: [{ required: true, message: "分类名称必填", trigger: "blur" }],
|
||||||
|
sort: [{
|
||||||
|
required: true,
|
||||||
|
message: "排序值必填",
|
||||||
|
trigger: "blur",
|
||||||
|
type: "number"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
const initFormData = () => {
|
||||||
|
if (props.row) {
|
||||||
|
Object.assign(formData, props.row);
|
||||||
|
isEdit.value = true;
|
||||||
|
} else {
|
||||||
|
formData.categoryId = 0;
|
||||||
|
formData.categoryName = "";
|
||||||
|
formData.sort = 0;
|
||||||
|
formData.description = "";
|
||||||
|
isEdit.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交处理
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateCategoryApi(formData.categoryId, formData);
|
||||||
|
} else {
|
||||||
|
await addCategoryApi(formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success(`${isEdit.value ? "修改" : "新增"}成功`);
|
||||||
|
emit("refresh");
|
||||||
|
emit("update:visible", false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("表单提交失败", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听visible变化初始化表单
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
val => {
|
||||||
|
if (val) {
|
||||||
|
initFormData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog :title="isEdit ? '编辑分类' : '新增分类'" :model-value="visible" width="30%"
|
||||||
|
@close="emit('update:visible', false)">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
||||||
|
<el-form-item label="分类名称" prop="categoryName">
|
||||||
|
<el-input v-model="formData.categoryName" placeholder="请输入分类名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" :min="0" controls-position="right" class="!w-full" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="描述" prop="description">
|
||||||
|
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入分类描述" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="emit('update:visible', false)">取消</el-button>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
|
@ -0,0 +1,216 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { PureTableBar } from "@/components/RePureTableBar";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import {
|
||||||
|
getCategoryListApi,
|
||||||
|
deleteCategoryApi,
|
||||||
|
CategoryDTO
|
||||||
|
} from "@/api/shop/category";
|
||||||
|
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||||
|
import Delete from "@iconify-icons/ep/delete";
|
||||||
|
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||||
|
import Search from "@iconify-icons/ep/search";
|
||||||
|
import Refresh from "@iconify-icons/ep/refresh";
|
||||||
|
import CategoryFormModal from "./category-form-modal.vue";
|
||||||
|
import CategoryEditModal from "./category-edit-modal.vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "ShopCategory"
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const tableRef = ref();
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const currentRow = ref<CategoryDTO>();
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchFormParams = ref({
|
||||||
|
categoryName: "",
|
||||||
|
sort: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分页参数
|
||||||
|
const pagination = ref({
|
||||||
|
pageSize: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loading = ref(false);
|
||||||
|
const dataList = ref([]);
|
||||||
|
const multipleSelection = ref<number[]>([]);
|
||||||
|
|
||||||
|
// API调用
|
||||||
|
const getList = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await getCategoryListApi({
|
||||||
|
...searchFormParams.value,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
pageNum: pagination.value.currentPage
|
||||||
|
});
|
||||||
|
dataList.value = data.rows;
|
||||||
|
pagination.value.total = data.total;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const onSearch = () => {
|
||||||
|
pagination.value.currentPage = 1;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetForm = () => {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页处理
|
||||||
|
const onSizeChange = (val: number) => {
|
||||||
|
pagination.value.pageSize = val;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCurrentChange = (val: number) => {
|
||||||
|
pagination.value.currentPage = val;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除处理
|
||||||
|
const handleDelete = async (row: any) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确认删除分类【${row.categoryName}】?`,
|
||||||
|
"提示",
|
||||||
|
{
|
||||||
|
type: "warning"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await deleteCategoryApi(row.categoryId);
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("删除失败", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBulkDelete = async () => {
|
||||||
|
if (multipleSelection.value.length === 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确认删除选中的${multipleSelection.value.length}项?`,
|
||||||
|
"提示",
|
||||||
|
{ type: "warning" }
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
multipleSelection.value.map(id => deleteCategoryApi(id))
|
||||||
|
);
|
||||||
|
|
||||||
|
ElMessage.success("批量删除成功");
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("批量删除失败", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleEdit = (row: CategoryDTO) => {
|
||||||
|
currentRow.value = row;
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
label: "分类名称",
|
||||||
|
prop: "categoryName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "排序",
|
||||||
|
prop: "sort",
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "描述",
|
||||||
|
prop: "description",
|
||||||
|
showOverflowTooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "创建时间",
|
||||||
|
prop: "createTime",
|
||||||
|
width: 180
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// 初始化加载
|
||||||
|
getList();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<el-form ref="formRef" :inline="true" :model="searchFormParams"
|
||||||
|
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]">
|
||||||
|
<el-form-item label="分类名称:" prop="categoryName">
|
||||||
|
<el-input v-model="searchFormParams.categoryName" placeholder="请输入分类名称" clearable class="!w-[200px]" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序:" prop="sort">
|
||||||
|
<el-input-number v-model="searchFormParams.sort" :min="0" class="!w-[120px]" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm">
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<PureTableBar title="分类列表" @refresh="getList">
|
||||||
|
<template #buttons>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
|
||||||
|
新增分类
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" :icon="useRenderIcon(Delete)" :disabled="multipleSelection.length === 0"
|
||||||
|
@click="handleBulkDelete">
|
||||||
|
批量删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="categoryId"
|
||||||
|
@selection-change="val => multipleSelection = val.map(item => item.categoryId)">
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column v-for="col in columns" :key="col.prop" :prop="col.prop" :label="col.label" :width="col.width"
|
||||||
|
:show-overflow-tooltip="col.showOverflowTooltip" />
|
||||||
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEdit(row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-popconfirm :title="`确认删除【${row.categoryName}】?`" @confirm="handleDelete(row)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="danger" link :icon="useRenderIcon(Delete)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
|
||||||
|
@size-change="onSizeChange" @current-change="onCurrentChange" />
|
||||||
|
</PureTableBar>
|
||||||
|
|
||||||
|
<category-form-modal v-model:visible="modalVisible" @refresh="getList" />
|
||||||
|
<category-edit-modal v-model:visible="editVisible" :row="currentRow" @refresh="getList" />
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from "vue";
|
||||||
|
import { ElMessage, FormRules } from "element-plus";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import { GoodsDTO, updateGoodsApi, addGoodsApi } from "@/api/shop/goods";
|
||||||
|
import { CategoryDTO, getCategoryAllApi } from "@/api/shop/category";
|
||||||
|
import Confirm from "@iconify-icons/ep/check";
|
||||||
|
import Upload from "@iconify-icons/ep/upload";
|
||||||
|
const { VITE_APP_BASE_API } = import.meta.env;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object as () => GoodsDTO,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "refresh"]);
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const formData = reactive<GoodsDTO>({
|
||||||
|
goodsId: 0,
|
||||||
|
goodsName: "",
|
||||||
|
price: 0,
|
||||||
|
stock: 0,
|
||||||
|
status: 1,
|
||||||
|
categoryId: 0,
|
||||||
|
goodsDetail: "",
|
||||||
|
coverImg: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
goodsName: [{ required: true, message: "商品名称必填", trigger: "blur" }],
|
||||||
|
price: [
|
||||||
|
{ required: true, message: "价格必填", trigger: "blur" },
|
||||||
|
{ type: 'number', min: 0, message: '价格不能小于0' }
|
||||||
|
],
|
||||||
|
stock: [
|
||||||
|
{ required: true, message: "库存必填", trigger: "blur" },
|
||||||
|
{ type: 'number', min: 0, message: '库存不能小于0' }
|
||||||
|
],
|
||||||
|
coverImg: [
|
||||||
|
{ required: true, message: "请上传商品封面图", trigger: "change" }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value.validate();
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateGoodsApi(formData.goodsId!, formData);
|
||||||
|
ElMessage.success("商品修改成功");
|
||||||
|
} else {
|
||||||
|
await addGoodsApi(formData);
|
||||||
|
ElMessage.success("商品添加成功");
|
||||||
|
}
|
||||||
|
emit("refresh");
|
||||||
|
closeDialog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("表单操作失败", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
emit("update:visible", false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const categoryOptions = ref<Array<CategoryDTO>>([]);
|
||||||
|
|
||||||
|
// 初始化表单数据和分类数据
|
||||||
|
const initForm = async () => {
|
||||||
|
try {
|
||||||
|
// 加载分类数据
|
||||||
|
const { data } = await getCategoryAllApi();
|
||||||
|
categoryOptions.value = data;
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
if (props.row?.goodsId) {
|
||||||
|
isEdit.value = true;
|
||||||
|
Object.assign(formData, props.row);
|
||||||
|
// 确保数字类型
|
||||||
|
formData.price = Number(formData.price);
|
||||||
|
formData.stock = Number(formData.stock);
|
||||||
|
} else {
|
||||||
|
isEdit.value = false;
|
||||||
|
if (data.length > 0) {
|
||||||
|
formData.categoryId = data[0].categoryId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("数据加载失败", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAvatarSuccess = (response, uploadFile) => {
|
||||||
|
formData.coverImg = response.data.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeAvatarUpload = (rawFile) => {
|
||||||
|
if (!['image/jpeg', 'image/png'].includes(rawFile.type)) {
|
||||||
|
ElMessage.error('封面图必须是 JPG/PNG 格式!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (rawFile.size > 50 * 1024 * 1024) {
|
||||||
|
ElMessage.error('封面图大小不能超过 50MB!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
val => {
|
||||||
|
if (val) initForm();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog :title="isEdit ? '编辑商品' : '新增商品'" :model-value="visible" width="600px" @close="closeDialog">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px" label-position="right">
|
||||||
|
<el-form-item label="商品名称" prop="goodsName">
|
||||||
|
<el-input v-model="formData.goodsName" placeholder="请输入商品名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="封面图" prop="coverImg">
|
||||||
|
<el-upload class="avatar-uploader" :action="VITE_APP_BASE_API + '/file/upload'" :show-file-list="false"
|
||||||
|
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
|
||||||
|
<img v-if="formData.coverImg" :src="formData.coverImg" class="avatar" />
|
||||||
|
<el-icon v-else class="avatar-uploader-icon">
|
||||||
|
<Icon :icon="Upload" />
|
||||||
|
</el-icon>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="价格" prop="price">
|
||||||
|
<el-input-number v-model="formData.price" :min="0" :precision="2" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="库存" prop="stock">
|
||||||
|
<el-input-number v-model="formData.stock" :min="0" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="商品分类" prop="categoryId">
|
||||||
|
<el-select v-model="formData.categoryId" placeholder="请选择商品分类" clearable class="w-full">
|
||||||
|
<el-option v-for="category in categoryOptions" :key="category.categoryId" :label="category.categoryName"
|
||||||
|
:value="category.categoryId" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio :label="1">上架</el-radio>
|
||||||
|
<el-radio :label="2">下架</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="closeDialog">取消</el-button>
|
||||||
|
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
|
||||||
|
确认
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.avatar-uploader {
|
||||||
|
:deep(.el-upload) {
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,13 +2,14 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { PureTableBar } from "@/components/RePureTableBar";
|
import { PureTableBar } from "@/components/RePureTableBar";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import { getGoodsListApi } from "@/api/shop/goods";
|
import { getGoodsListApi, GoodsDTO } from "@/api/shop/goods";
|
||||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||||
import Delete from "@iconify-icons/ep/delete";
|
import Delete from "@iconify-icons/ep/delete";
|
||||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||||
import Search from "@iconify-icons/ep/search";
|
import Search from "@iconify-icons/ep/search";
|
||||||
import Refresh from "@iconify-icons/ep/refresh";
|
import Refresh from "@iconify-icons/ep/refresh";
|
||||||
import GoodsFormModal from "./goods-form-modal.vue";
|
import GoodsFormModal from "./goods-form-modal.vue";
|
||||||
|
import GoodsEditModal from "./goods-edit-modal.vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { deleteGoodsApi } from "@/api/shop/goods";
|
import { deleteGoodsApi } from "@/api/shop/goods";
|
||||||
|
|
||||||
|
@ -37,6 +38,8 @@ const pagination = ref({
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dataList = ref([]);
|
const dataList = ref([]);
|
||||||
const multipleSelection = ref<number[]>([]);
|
const multipleSelection = ref<number[]>([]);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const currentRow = ref<GoodsDTO>();
|
||||||
|
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -115,6 +118,12 @@ const handleBulkDelete = async () => {
|
||||||
const handleSelectionChange = (rows: any[]) => {
|
const handleSelectionChange = (rows: any[]) => {
|
||||||
multipleSelection.value = rows.map(row => row.goodsId);
|
multipleSelection.value = rows.map(row => row.goodsId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEdit = (row: GoodsDTO) => {
|
||||||
|
currentRow.value = row;
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -171,7 +180,7 @@ const handleSelectionChange = (rows: any[]) => {
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="150" fixed="right">
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link :icon="useRenderIcon(EditPen)">
|
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEdit(row)">
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-popconfirm :title="`确认删除【${row.goodsName}】?`" @confirm="handleDelete(row)">
|
<el-popconfirm :title="`确认删除【${row.goodsName}】?`" @confirm="handleDelete(row)">
|
||||||
|
@ -190,6 +199,7 @@ const handleSelectionChange = (rows: any[]) => {
|
||||||
</PureTableBar>
|
</PureTableBar>
|
||||||
<!-- 新增商品弹窗 -->
|
<!-- 新增商品弹窗 -->
|
||||||
<goods-form-modal v-model:visible="modalVisible" @refresh="getList" />
|
<goods-form-modal v-model:visible="modalVisible" @refresh="getList" />
|
||||||
|
<goods-edit-modal v-model:visible="editVisible" :row="currentRow" @refresh="getList" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
Loading…
Reference in New Issue