This commit is contained in:
quantulr
2023-08-30 17:31:16 +08:00
parent 2a60039aa0
commit 16db12db20
26 changed files with 3117 additions and 1215 deletions

View File

@ -1,8 +1,8 @@
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 口袋九章
# 开发环境配置
VITE_APP_ENV = 'development'
# 若依管理系统/开发环境
# 口袋九章/开发环境
VITE_APP_BASE_API = '/dev-api'

View File

@ -1,10 +1,10 @@
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 口袋九章
# 生产环境配置
VITE_APP_ENV = 'production'
# 若依管理系统/生产环境
# 口袋九章/生产环境
VITE_APP_BASE_API = '/api'
# 是否在打包时开启压缩,支持 gzip 和 brotli

View File

@ -1,10 +1,10 @@
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 口袋九章
# 生产环境配置
VITE_APP_ENV = 'staging'
# 若依管理系统/生产环境
# 口袋九章/生产环境
VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli

View File

@ -7,7 +7,7 @@
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico">
<title>若依管理系统</title>
<title>口袋九章</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,

View File

@ -1,7 +1,7 @@
{
"name": "ruoyi",
"version": "3.8.5",
"description": "若依管理系统",
"description": "口袋九章",
"author": "若依",
"license": "MIT",
"scripts": {
@ -18,8 +18,11 @@
"dependencies": {
"@element-plus/icons-vue": "2.0.10",
"@highlightjs/vue-plugin": "^2.1.0",
"@vueup/vue-quill": "1.1.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "9.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"ant-design-vue": "4.x",
"axios": "0.27.2",
"echarts": "5.4.0",
"element-plus": "2.2.27",
@ -32,6 +35,7 @@
"nprogress": "0.2.0",
"pinia": "2.0.22",
"pinia-plugin-persistedstate": "^3.1.0",
"quill-image-uploader": "^1.3.0",
"v3-infinite-loading": "^1.2.2",
"vue": "3.2.45",
"vue-cropper": "1.0.3",

View File

@ -0,0 +1,70 @@
import request from "@/utils/request";
// 查询员工列表
export function listEmployee(query) {
return request({
url: "/employee/employee/list",
method: "get",
params: query,
});
}
// 查询员工详细
export function getEmployee(userId) {
return request({
url: "/employee/employee/" + userId,
method: "get",
});
}
// 新增员工
export function addEmployee(data) {
return request({
url: "/employee/employee",
method: "post",
data: data,
});
}
// 修改员工
export function updateEmployee(data) {
return request({
url: "/employee/employee",
method: "put",
data: data,
});
}
// 删除员工
export function delEmployee(userId) {
return request({
url: "/employee/employee/" + userId,
method: "delete",
});
}
export const downloadEmployeeTemplate = () =>
request({
url: "/employee/employee/importTemplate",
method: "GET",
responseType: "blob",
});
/**
* 修改员工积分
* @param data
* @returns {*}
*/
export const updateEmployeePoint = (data) =>
request({
url: "/employee/employee/updatePoint",
method: "PUT",
data,
});
export const updateEmployeeStatus = (data) =>
request({
url: "/employee/employee/changeStatus",
method: "PUT",
data,
});

14
src/api/finance/detail.js Normal file
View File

@ -0,0 +1,14 @@
import request from "@/utils/request";
export const financeDetailList = (params) =>
request({ url: "/finance/detail/list", method: "GET", params });
export const deleteFinanceDetail = (financeIds) =>
request({ url: `/finance/detail/${financeIds}`, method: "DELETE" });
export const getFinanceDetail = (financeId) =>
request({ url: `/finance/detail/${financeId}`, method: "GET" });
export const addFinanceDetail = (data) =>
request({ url: `/finance/detail`, method: "POST", data });
export const updateFinanceDetail = (data) =>
request({ url: `/finance/detail`, method: "PUT", data });

View File

@ -71,4 +71,3 @@ export const getRouters = () => {
method: "get",
});
};

106
src/api/product/product.js Normal file
View File

@ -0,0 +1,106 @@
import request from "@/utils/request";
// 查询产品列表
export function listProduct(query) {
return request({
url: "/product/product/list",
method: "get",
params: query,
});
}
// 查询产品详细
export function getProduct(productId) {
return request({
url: "/product/product/" + productId,
method: "get",
});
}
// 新增产品
export function addProduct(data) {
return request({
url: "/product/product",
method: "post",
data: data,
});
}
// 修改产品
export function updateProduct(data) {
return request({
url: "/product/product",
method: "put",
data: data,
});
}
// 删除产品
export function delProduct(productId) {
return request({
url: "/product/product/" + productId,
method: "delete",
});
}
// 添加型号
export const addModel = (data) =>
request({
url: "/product/model",
method: "POST",
data,
});
export const modelList = (params) =>
request({
url: "/product/model/list",
method: "GET",
params,
});
export const getModel = (modelId) =>
request({
url: `/product/model/${modelId}`,
method: "GET",
});
export const updateModel = (data) =>
request({
url: "/product/model",
method: "PUT",
data,
});
export const deleteModel = (modelIds) =>
request({
url: `/product/model/${modelIds}`,
method: "DELETE",
});
export const addSpec = (data) =>
request({
url: "/product/spec",
method: "POST",
data,
});
export const listSpec = (params) =>
request({
url: "/product/spec/list",
method: "GET",
params,
});
export const getSpec = (specId) =>
request({
url: `/product/spec/${specId}`,
method: "GET",
});
export const updateSpec = (data) =>
request({
url: "/product/spec",
method: "PUT",
data,
});
export const deleteSpec = (modelIds) =>
request({
url: `/product/spec/${modelIds}`,
method: "DELETE",
});

15
src/api/product/stock.js Normal file
View File

@ -0,0 +1,15 @@
import request from "@/utils/request";
export const loadStockLogList = (params) =>
request({
url: "/product/stocklog/list",
method: "GET",
params,
});
export const loadStockList = (params) =>
request({
url: "/product/stock/list",
method: "GET",
params,
});

View File

@ -1,166 +1,170 @@
<template>
<div class="editor">
<quill-editor
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
<div
:class="{
disabled: readOnly,
}"
:style="`border: 1px solid #ccc; width: ${width}px`"
>
<Toolbar
:defaultConfig="toolbarConfig"
:editor="editorRef"
:mode="mode"
style="border-bottom: 1px solid #ccc"
/>
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
:style="`min-height: ${minHeight}px; height: ${height}px;
overflow-y: hidden`"
@onBlur="emitBlur"
@onChange="handleChange"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup>
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
<script>
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { getToken } from "@/utils/auth";
import { onBeforeUnmount, ref, shallowRef, toRefs, watch } from "vue";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
const props = defineProps({
/* 编辑器的内容 */
modelValue: {
type: String,
const baseUrl = import.meta.env.VITE_APP_BASE_API;
export default {
components: { Editor, Toolbar },
props: {
readOnly: {
type: Boolean,
default: false,
},
modelValue: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "请输入内容",
},
minHeight: {
type: [String, Number],
default: 300,
},
height: {
type: [String, Number],
default: 300,
},
width: {
type: [String, Number],
default: 820,
},
mode: {
type: String,
default: "default", // or 'simple'
},
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
});
setup(props, context) {
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
const options = ref({
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
// 工具栏配置
toolbar: [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"], // 链接、图片、视频
],
},
placeholder: "请输入内容",
readOnly: props.readOnly,
theme: "snow",
});
// 内容 HTML
const valueHtml = ref("");
watch(
() => props.modelValue,
(val) => {
valueHtml.value = val ?? "";
},
{ immediate: true }
);
const { height } = toRefs(props);
const styles = computed(() => {
let style = {};
if (props.minHeight) {
style.minHeight = `${props.minHeight}px`;
}
if (props.height) {
style.height = `${props.height}px`;
}
return style;
});
const toolbarConfig = {
excludeKeys: [],
};
const editorConfig = {
placeholder: props.placeholder,
readOnly: props.readOnly,
MENU_CONF: {
uploadImage: {
server: `${baseUrl}/file/upload`,
// 自定义增加 http header
fieldName: "file",
headers: {
Authorization: `Bearer ${getToken()}`,
},
customInsert(res, insertFn) {
// res 即服务端的返回结果
// console.log(res);
// 从 res 中找到 url alt href ,然后插图图片
insertFn(`${baseUrl}${res.url}`, null, null);
},
},
},
};
const content = ref("");
watch(
() => props.modelValue,
(v) => {
if (v !== content.value) {
content.value = v === undefined ? "<p></p>" : v;
}
const isEmpty = () => {
return editorRef.value.isEmpty();
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor) => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
const handleChange = (editor) => {
if (editor.isEmpty()) {
context.emit("update:modelValue", "");
} else {
context.emit("update:modelValue", editor.getHtml());
}
};
context.expose({ isEmpty });
const emitBlur = (editor) => {
// context.emit("blur", editorRef.value);
if (editor.isEmpty()) {
context.emit("blur", "");
} else {
context.emit("blur", editor.getHtml());
}
// editorRef.value.emit("blur");
};
return {
editorRef,
valueHtml,
mode: "default", // 或 'simple'
toolbarConfig,
editorConfig,
height,
handleCreated,
handleChange,
// isEmpty,
emitBlur,
};
},
{ immediate: true }
);
};
</script>
<style lang="scss" scoped>
.disabled {
background-color: #f5f7fa;
cursor: not-allowed;
<style>
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
}
:deep(.w-e-text-container) {
background-color: inherit;
// color: green;
opacity: 0.5;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
:deep(.w-e-toolbar) {
user-select: none;
pointer-events: none;
background-color: inherit;
}
}
</style>

View File

@ -0,0 +1,184 @@
<template>
<div class="editor">
<quill-editor
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
</div>
</template>
<script setup>
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import ImageUploader from "quill-image-uploader";
import "quill-image-uploader/dist/quill.imageUploader.min.css";
import request from "@/utils/request";
// Quill.register("modules/imageUploader", ImageUploader);
const props = defineProps({
/* 编辑器的内容 */
modelValue: {
type: String,
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
});
const options = ref({
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
// 工具栏配置
toolbar: [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"], // 链接、图片、视频
],
},
placeholder: "请输入内容",
readOnly: props.readOnly,
});
const styles = computed(() => {
let style = {};
if (props.minHeight) {
style.minHeight = `${props.minHeight}px`;
}
if (props.height) {
style.height = `${props.height}px`;
}
return style;
});
const content = ref("");
watch(
() => props.modelValue,
(v) => {
if (v !== content.value) {
content.value = v === undefined ? "<p></p>" : v;
}
},
{ immediate: true }
);
</script>
<style>
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
</style>

View File

@ -4,11 +4,14 @@
fit="cover"
:style="`width:${realWidth};height:${realHeight};`"
:preview-src-list="realSrcList"
preview-teleported
append-to-body="true"
>
<template #error>
<div class="image-slot">
<el-icon><picture-filled /></el-icon>
<el-icon>
<picture-filled />
</el-icon>
</div>
</template>
</el-image>
@ -72,13 +75,16 @@ const realHeight = computed(() =>
border-radius: 5px;
background-color: #ebeef5;
box-shadow: 0 0 5px 1px #ccc;
:deep(.el-image__inner) {
transition: all 0.3s;
cursor: pointer;
&:hover {
transform: scale(1.2);
}
}
:deep(.image-slot) {
display: flex;
justify-content: center;

View File

@ -153,7 +153,7 @@ function handleExceed() {
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName });
uploadList.value.push({ name: res.fileName, url: res.url });
uploadedSuccessfully();
} else {
number.value--;

View File

@ -60,7 +60,7 @@ defineProps({
},
});
const title = ref("若依管理系统");
const title = ref("口袋九章");
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
</script>

View File

@ -9,6 +9,7 @@ import hljsVuePlugin from "@highlightjs/vue-plugin";
import ElementPlus from "element-plus";
import locale from "element-plus/lib/locale/lang/zh-cn"; // 中文语言
import "@/assets/styles/index.scss"; // global css
import "ant-design-vue/dist/reset.css";
import App from "./App";
import store from "./store";
import router from "./router";

View File

@ -92,7 +92,6 @@ const useUserStore = defineStore("user", {
resolve();
});
},
},
});

22
src/utils/dict.js Normal file
View File

@ -0,0 +1,22 @@
import { getDicts } from "@/api/system/dict/data";
/**
* 获取字典数据
*/
export function useDict(...args) {
const res = ref({});
return (() => {
args.forEach((d, index) => {
res.value[d] = [];
getDicts(d).then((resp) => {
res.value[d] = resp.data.map((p) => ({
label: p.dictLabel,
value: p.dictValue,
elTagType: p.listClass,
elTagClass: p.cssClass,
}));
});
});
return toRefs(res.value);
})();
}

View File

@ -0,0 +1,533 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<!-- <el-form-item label="用户ID" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户ID"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>-->
<el-form-item label="用户名称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入用户名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入手机号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['employee:employee:add']"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleImport"
>导入员工
</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['employee:employee:edit']"
>修改
</el-button>
</el-col>-->
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['employee:employee:remove']"
>删除
</el-button>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table
@row-click="handleCancelEditPoint"
v-loading="loading"
:data="employeeList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="用户ID" align="center" prop="userId"/>-->
<el-table-column label="用户名称" align="center" prop="nickname" />
<el-table-column label="手机号码" align="center" prop="phone" />
<el-table-column label="状态" align="center" prop="status">
<template #default="{ row }">
<el-switch
@click="handleUpdateStatus(row)"
inactive-value="0"
active-value="1"
:model-value="row.status"
/>
</template>
</el-table-column>
<el-table-column label="总积分" align="center" prop="point" width="240">
<template #default="{ row }">
<el-row
justify="center"
v-if="pointEditing && form.userId === row.userId"
align="middle"
>
<el-col :span="12">
<el-input-number
style="width: 100%"
@click.stop
size="small"
v-model="form.point"
/>
</el-col>
<el-col :span="12">
<el-button
link
@click.stop="submitPoint"
icon="Check"
type="success"
/>
</el-col>
</el-row>
<el-row v-else>
<el-col :span="12">
<div style="text-align: center">{{ row.point }}</div>
</el-col>
<el-col :span="12">
<el-button
link
icon="Edit"
@click.stop="handleUpdatePoint($event, row)"
type="primary"
/>
</el-col>
</el-row>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" />
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template #default="scope">
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
v-hasPermi="['employee:employee:edit']">修改
</el-button>-->
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['employee:employee:remove']"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改员工对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form
ref="employeeRef"
:model="form"
:rules="rules"
label-width="80px"
>
<el-form-item label="用户名称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入用户名称" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 员工导入对话框 -->
<el-dialog
draggable
v-model="upload.open"
:title="upload.title"
append-to-body
width="400px"
>
<div v-show="upload.isUploading" class="upload-progress">
<el-progress type="circle" :percentage="50" :show-text="false" />
<p>正在导入员工数据,请耐心等待......</p>
</div>
<el-upload
v-show="!upload.isUploading"
ref="uploadRef"
:action="upload.url"
:auto-upload="false"
:disabled="upload.isUploading"
:headers="upload.headers"
:limit="1"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
accept=".xlsx, .xls"
drag
>
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<ol style="text-align: start; margin-left: 20px">
<!-- <li>(*)为必填项</li>-->
<!-- <li>部门,角色,岗位请填数字编号</li>-->
<!-- <li>角色,岗位若存在多个,请以英文逗号隔开</li>-->
<!-- <li>禁止任何的换行或空格</li>-->
<!-- <li style="color: #f00; font-size: 13px">-->
<!-- 请手动更改单元格格式为文本格式-->
<!-- </li>-->
<li>
<span>仅允许导入xlsxlsx格式文件</span>
<el-link
:underline="false"
style="font-size: 12px; vertical-align: baseline"
type="primary"
@click="importTemplate"
>下载模板
</el-link>
</li>
</ol>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Employee">
import {
listEmployee,
getEmployee,
delEmployee,
addEmployee,
updateEmployee,
downloadEmployeeTemplate,
updateEmployeePoint,
updateEmployeeStatus,
} from "@/api/employee/employee";
import { getToken } from "@/utils/auth";
import { saveAs } from "file-saver";
import modal from "@/plugins/modal";
const { proxy } = getCurrentInstance();
const employeeList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const pointEditing = ref(false);
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userId: null,
nickname: null,
point: null,
},
rules: {
userId: [{ required: true, message: "用户ID不能为空", trigger: "blur" }],
nickname: [
{ required: true, message: "用户名称不能为空", trigger: "blur" },
],
point: [{ required: true, message: "总积分不能为空", trigger: "blur" }],
},
});
const { queryParams, form, rules } = toRefs(data);
/*** 用户导入参数 */
const upload = reactive({
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + "/employee/employee/importData",
});
/** 查询员工列表 */
function getList() {
loading.value = true;
listEmployee(queryParams.value).then((response) => {
employeeList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
function reset() {
form.value = {
userId: null,
nickname: null,
point: null,
phone: null,
status: "0",
};
proxy.resetForm("employeeRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加员工";
}
/** 导入按钮操作 */
function handleImport() {
upload.title = "员工导入";
upload.open = true;
}
const importTemplate = async () => {
const blob = await downloadEmployeeTemplate();
saveAs(blob, `employee_${new Date().getTime()}.xlsx`);
};
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true;
};
/** 文件上传成功处理 */
const handleFileSuccess = (response, file, fileList) => {
upload.open = false;
upload.isUploading = false;
proxy.$refs["uploadRef"].handleRemove(file);
proxy.$alert(
"<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
response.msg +
"</div>",
"导入结果",
{ dangerouslyUseHTMLString: true }
);
getList();
};
/** 提交上传文件 */
function submitFileForm() {
proxy.$refs["uploadRef"].submit();
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _userId = row.userId || ids.value;
getEmployee(_userId).then((response) => {
form.value = response.data;
open.value = true;
title.value = "修改员工";
});
}
const handleUpdatePoint = (event, row) => {
event.stopPropagation();
reset();
form.value.userId = row.userId;
form.value.point = row.point;
pointEditing.value = true;
};
/** 提交按钮 */
function submitForm() {
proxy.$refs["employeeRef"].validate((valid) => {
if (valid) {
if (form.value.userId != null) {
updateEmployee(form.value).then((response) => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addEmployee(form.value).then((response) => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _userIds = row.userId || ids.value;
proxy.$modal
.confirm('是否确认删除员工编号为"' + _userIds + '"的数据项?')
.then(function () {
return delEmployee(_userIds);
})
.then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}
const handleUpdateStatus = (row) => {
modal.confirm("是否确定要更改状态?").then(async () => {
let status;
if (row.status === "1") {
status = "0";
} else {
status = "1";
}
await updateEmployeeStatus({
userId: row.userId,
status,
});
getList();
modal.msgSuccess("修改状态成功");
});
};
const submitPoint = () => {
updateEmployeePoint({
userId: form.value.userId,
point: form.value.point,
})
.then(() => {
modal.msgSuccess("修改积分成功");
})
.finally(() => {
pointEditing.value = false;
getList();
});
};
const handleCancelEditPoint = () => {
reset();
pointEditing.value = false;
};
getList();
</script>
<style lang="scss" scoped>
.upload-progress {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
aspect-ratio: 360/185;
& > p {
text-align: center;
margin-top: 6px;
}
:deep(.el-progress-circle svg) {
animation: spin 1s linear infinite;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,259 @@
<script setup>
import {
addFinanceDetail,
deleteFinanceDetail,
financeDetailList,
getFinanceDetail,
updateFinanceDetail,
} from "@/api/finance/detail";
import {useDict} from "@/utils/dict";
import DictTag from "@/components/DictTag/index.vue";
import {dayjs} from "element-plus";
import modal from "@/plugins/modal";
const open = ref(false);
const loading = ref(true);
const total = ref(0);
const showSearch = ref(true);
const queryRef = ref();
const title = ref("");
const formRef = ref();
const ids = ref([]);
const financeList = ref([]);
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
},
rules: {
event: [{required: true, message: "事项不能为空", trigger: "blur"}],
type: [{required: true, message: "类型不能为空", trigger: "change"}],
date: [{required: true, message: "日期不能为空", trigger: "change"}],
oppositeCompany: [{required: true, message: "对方账户不能为空", trigger: "blur"}],
amount: [{required: true, message: "金额不能为空", trigger: "blur"}],
},
});
const {form, queryParams, rules} = toRefs(data);
const {finance_type} = useDict("finance_type");
const getList = () => {
loading.value = true;
financeDetailList().then((resp) => {
financeList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
queryRef.value?.resetFields();
handleQuery();
}
// 表单重置
function reset() {
form.value = {
financeId: null,
date: null,
type: null,
event: null,
amount: null,
oppositeCompany: null,
};
formRef.value?.resetFields();
}
/** 新增按钮操作 */
function handleAdd(row) {
reset();
title.value = "添加事项";
open.value = true;
}
/** 删除按钮操作 */
function handleDelete(row) {
const _financeIds = row.financeId || ids.value;
modal
.confirm('是否确认删除编号为"' + _financeIds + '"的数据项?')
.then(function () {
return deleteFinanceDetail(_financeIds);
})
.then(() => {
getList();
modal.msgSuccess("删除成功");
})
.catch(() => {
});
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _financeId = row.financeId || ids.value;
getFinanceDetail(_financeId).then((response) => {
form.value = response.data;
open.value = true;
title.value = "修改事项";
});
}
const submitForm = () => {
formRef.value?.validate((valid) => {
if (valid) {
if (form.value.financeId != null) {
updateFinanceDetail(form.value).then((response) => {
modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addFinanceDetail(form.value).then((response) => {
modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
};
const cancel = () => {
open.value = false;
reset();
};
getList();
</script>
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="事项" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入事项"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd"
>新增
</el-button>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table :data="financeList">
<el-table-column align="center" label="事项" prop="event"/>
<el-table-column align="center" label="类别">
<template #default="{ row }">
<dict-tag :options="finance_type" :value="row.type"/>
</template>
</el-table-column>
<el-table-column align="center" label="日期">
<template #default="{ row }">
{{ dayjs(row.date).format("YYYY-MM-DD") }}
</template>
</el-table-column>
<el-table-column align="center" label="对方账户" prop="oppositeCompany"/>
<el-table-column align="center" label="金额" prop="amount"/>
<el-table-column align="center" label="操作">
<template #default="{ row }">
<el-button
icon="edit"
@click="handleUpdate(row)"
link
size="small"
type="primary"
>修改
</el-button
>
<el-button
icon="delete"
@click="handleDelete(row)"
link
size="small"
type="primary"
>删除
</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="事项" prop="event">
<el-input v-model="form.event" placeholder="请输入事项"/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type">
<el-option
v-for="option in finance_type"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item label="日期" prop="date">
<el-date-picker
v-model="form.date"
type="date"
placeholder="请选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="对方账户" prop="oppositeCompany">
<el-input
v-model="form.oppositeCompany"
placeholder="请输入对方账户"
/>
</el-form-item>
<el-form-item label="金额" prop="amount">
<el-input-number v-model="form.amount" placeholder="请输入金额"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@ -3,87 +3,87 @@
<div class="login-card">
<div class="login-banner">
<!-- 主标题 -->
<!-- <h2 class="title">若依后台管理系统</h2>-->
<!-- <h2 class="title">若依后台管理系统</h2>-->
<!-- 副标题 -->
<!-- <p class="sub-title">前后端分离的后台管理系统架构</p>-->
<el-image class="banner-image" :src="loginLeftBanner"/>
<!-- <p class="sub-title">前后端分离的后台管理系统架构</p>-->
<el-image class="banner-image" :src="loginLeftBanner" />
</div>
<el-form
ref="loginRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
ref="loginRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">管理系统</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
auto-complete="off"
placeholder="账号"
size="large"
type="text"
v-model="loginForm.username"
auto-complete="off"
placeholder="账号"
size="large"
type="text"
>
<template #prefix>
<svg-icon class="el-input__icon input-icon" icon-class="user"/>
<svg-icon class="el-input__icon input-icon" icon-class="user" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
auto-complete="off"
placeholder="密码"
size="large"
type="password"
@keyup.enter="handleLogin"
v-model="loginForm.password"
auto-complete="off"
placeholder="密码"
size="large"
type="password"
@keyup.enter="handleLogin"
>
<template #prefix>
<svg-icon
class="el-input__icon input-icon"
icon-class="password"
class="el-input__icon input-icon"
icon-class="password"
/>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="captchaEnabled" prop="code">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
size="large"
style="width: 63%"
@keyup.enter="handleLogin"
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
size="large"
style="width: 63%"
@keyup.enter="handleLogin"
>
<template #prefix>
<svg-icon
class="el-input__icon input-icon"
icon-class="validCode"
class="el-input__icon input-icon"
icon-class="validCode"
/>
</template>
</el-input>
<div class="login-code">
<img :src="codeUrl" class="login-code-img" @click="getCode"/>
<img :src="codeUrl" class="login-code-img" @click="getCode" />
</div>
</el-form-item>
<el-checkbox
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码
</el-checkbox>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="large"
style="width: 100%"
type="primary"
@click.prevent="handleLogin"
:loading="loading"
size="large"
style="width: 100%"
type="primary"
@click.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
<div v-if="register" style="float: right">
<router-link :to="'/register'" class="link-type"
>立即注册
>立即注册
</router-link>
</div>
</el-form-item>
@ -99,29 +99,29 @@
<script setup>
import loginLeftBanner from "@/assets/images/login-left-banner.png";
import {getCodeImg} from "@/api/login";
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import {decrypt, encrypt} from "@/utils/jsencrypt";
import { decrypt, encrypt } from "@/utils/jsencrypt";
import useUserStore from "@/store/modules/user";
import {getCurrentInstance, ref} from "vue";
import {useRouter} from "vue-router";
import { getCurrentInstance, ref } from "vue";
import { useRouter } from "vue-router";
const userStore = useUserStore();
const router = useRouter();
const {proxy} = getCurrentInstance();
const { proxy } = getCurrentInstance();
const loginForm = ref({
username: "admin",
password: "admin123",
username: "",
password: "",
rememberMe: false,
code: "",
uuid: "",
});
const loginRules = {
username: [{required: true, trigger: "blur", message: "请输入您的账号"}],
password: [{required: true, trigger: "blur", message: "请输入您的密码"}],
code: [{required: true, trigger: "change", message: "请输入验证码"}],
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
code: [{ required: true, trigger: "change", message: "请输入验证码" }],
};
const codeUrl = ref("");
const loading = ref(false);
@ -137,11 +137,11 @@ function handleLogin() {
loading.value = true;
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, {expires: 30});
Cookies.set("username", loginForm.value.username, { expires: 30 });
Cookies.set("password", encrypt(loginForm.value.password), {
expires: 30,
});
Cookies.set("rememberMe", loginForm.value.rememberMe, {expires: 30});
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
} else {
// 否则移除
Cookies.remove("username");
@ -150,17 +150,17 @@ function handleLogin() {
}
// 调用action的登录方法
userStore
.login(loginForm.value)
.then(() => {
router.push({path: redirect.value || "/"});
})
.catch(() => {
loading.value = false;
// 重新获取验证码
if (captchaEnabled.value) {
getCode();
}
});
.login(loginForm.value)
.then(() => {
router.push({ path: redirect.value || "/" });
})
.catch(() => {
loading.value = false;
// 重新获取验证码
if (captchaEnabled.value) {
getCode();
}
});
}
});
}
@ -168,7 +168,7 @@ function handleLogin() {
function getCode() {
getCodeImg().then((res) => {
captchaEnabled.value =
res.captchaEnabled === undefined ? true : res.captchaEnabled;
res.captchaEnabled === undefined ? true : res.captchaEnabled;
register.value = res.register === undefined ? true : res.register;
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img;
@ -184,13 +184,12 @@ function getCookie() {
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password:
password === undefined ? loginForm.value.password : decrypt(password),
password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
};
}
getCode()
getCode();
getCookie();
</script>
@ -216,7 +215,7 @@ getCookie();
display: flex;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
0 8px 10px -6px rgb(0 0 0 / 0.1);
.login-banner {
padding: 0 20px 0;

View File

@ -0,0 +1,558 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['product:product:add']"
>新增
</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['product:product:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['product:product:remove']"
>删除
</el-button>
</el-col>-->
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<!-- <el-table row-key="rowKey"
ref="tableRef"
lazy
:load="loadChildren"
v-loading="loading"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:data="productList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="名称" prop="name">
&lt;!&ndash; <template #default="{row}">
<el-button link @click="loadChildren(row)" icon="ArrowRight"/>
{{ row.name }}
</template>&ndash;&gt;
</el-table-column>
<el-table-column label="图片" align="center" prop="pic" width="100">
<template #default="{row}">
<image-preview v-if="row.specId" :src="row.pic" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="{row}">
<el-button v-if="!row.specId" link type="primary" icon="Plus" @click="handleAdd(row)"
v-hasPermi="['product:product:add']">新增{{
row.modelId ? "规格" : row.productId ? "型号" : "规格"
}}
</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)"
v-hasPermi="['product:product:edit']">修改
</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row)"
v-hasPermi="['product:product:remove']">删除
</el-button>
</template>
</el-table-column>
</el-table>-->
<a-table
:columns="[
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '图片',
dataIndex: 'pic',
key: 'pic',
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
},
{
title: '操作',
dataIndex: 'operation',
width: '260px',
},
]"
@expand="handleExpandTable"
:data-source="productList"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'pic'">
<image-preview
v-if="record.specId"
:src="record.pic"
:width="50"
:height="50"
/>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<el-button
v-if="!record.specId"
link
type="primary"
icon="Plus"
@click="handleAdd(record)"
v-hasPermi="['product:product:add']"
>新增{{
record.modelId ? "规格" : record.productId ? "型号" : "规格"
}}
</el-button>
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(record)"
v-hasPermi="['product:product:edit']"
>修改
</el-button>
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(record)"
v-hasPermi="['product:product:remove']"
>删除
</el-button>
</template>
</template>
</a-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改产品对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="productRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item
v-if="
(editMode === 1 && form.specId) || (editMode === 0 && form.modelId)
"
label="图片"
prop="pic"
>
<image-upload v-model="form.pic" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入内容"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Product">
import {
listProduct,
getProduct,
delProduct,
addProduct,
updateProduct,
addModel,
modelList,
addSpec,
listSpec,
updateSpec,
updateModel,
getModel,
getSpec,
deleteSpec,
deleteModel,
} from "@/api/product/product";
import { handleTree } from "@/utils/ruoyi";
import { nextTick } from "vue";
import { Table as ATable } from "ant-design-vue";
const { proxy } = getCurrentInstance();
const productList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const editMode = ref(0); // 0 新增, 1 修改
const tableRef = ref();
const parentRow = ref();
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
productId: null,
name: null,
parentId: null,
pic: null,
createUser: null,
createTime: null,
updateUser: null,
updateTime: null,
remark: null,
},
rules: {
productId: [{ required: true, message: "不能为空", trigger: "blur" }],
name: [{ required: true, message: "名称不能为空", trigger: "blur" }],
parentId: [{ required: true, message: "父ID不能为空", trigger: "blur" }],
pic: [{ required: true, message: "图片不能为空", trigger: "blur" }],
createUser: [
{ required: true, message: "创建者不能为空", trigger: "blur" },
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" },
],
updateUser: [
{ required: true, message: "更新者不能为空", trigger: "blur" },
],
updateTime: [
{ required: true, message: "更新时间不能为空", trigger: "blur" },
],
remark: [],
},
});
const { queryParams, form, rules } = toRefs(data);
const handleExpandTable = (expanded, record) => {
expanded && loadChildren(record);
};
const loadChildren = (row) => {
console.log(tableRef.value);
if (row.modelId) {
/*加载规格*/
listSpec({ modelId: row.modelId }).then(({ rows }) => {
row.children = rows.map((el) => ({
...el,
key: el.specId,
}));
});
} else if (row.productId) {
/*加载型号*/
modelList({
productId: row.productId,
}).then(({ rows }) => {
row.children = rows.map((el) => ({
...el,
key: el.modelId,
children: [],
}));
});
}
};
/** 查询产品列表 */
function getList() {
loading.value = true;
listProduct(queryParams.value).then((response) => {
productList.value = response.rows.map((el) => ({
...el,
key: el.productId,
children: [],
}));
total.value = response.total;
loading.value = false;
});
}
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
function reset() {
form.value = {
productId: null,
name: null,
parentId: null,
pic: null,
};
proxy.resetForm("productRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.productId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
function findInTreeByKey(tree, key) {
for (const node of tree) {
if (node.key === key) {
return node;
}
if (node.children) {
const result = findInTreeByKey(node.children, key);
if (result) {
return result;
}
}
}
return null;
}
/** 新增按钮操作 */
function handleAdd(row) {
editMode.value = 0;
reset();
if (row.modelId) {
/*添加规格*/
title.value = "添加规格";
form.value.modelId = row.modelId;
form.value.productId = row.productId;
} else if (row.productId) {
/*添加型号*/
title.value = "添加型号";
form.value.productId = row.productId;
} else {
/*添加产品*/
title.value = "添加产品";
}
open.value = true;
}
/** 修改按钮操作 */
async function handleUpdate(row) {
editMode.value = 1;
reset();
if (row.specId) {
form.value = (await getSpec(row.specId)).data;
title.value = "修改规格";
} else if (row.modelId) {
form.value = (await getModel(row.modelId)).data;
title.value = "修改型号";
} else {
form.value = (await getProduct(row.productId)).data;
title.value = "修改产品";
}
open.value = true;
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["productRef"].validate((valid) => {
if (valid) {
if (editMode.value) {
if (form.value.specId) {
/*修改规格*/
updateSpec(form.value).then(() => {
proxy.$modal.msgSuccess("修改规格成功");
open.value = false;
listSpec({ modelId: form.value.modelId }).then(({ rows }) => {
const model = findInTreeByKey(
productList.value,
form.value.modelId
);
if (model) {
model.children = rows.map((el) => ({
...el,
key: el.specId,
}));
}
});
});
} else if (form.value.modelId) {
/*修改型号*/
updateModel(form.value).then(() => {
proxy.$modal.msgSuccess("修改型号成功");
open.value = false;
modelList({ productId: form.value.productId }).then(({ rows }) => {
const product = findInTreeByKey(
productList.value,
form.value.productId
);
if (product) {
product.children = rows.map((el) => ({
...el,
key: el.modelId,
children: [],
}));
}
});
});
} else {
updateProduct(form.value).then((response) => {
proxy.$modal.msgSuccess("修改产品成功");
open.value = false;
getList();
});
}
} else {
if (form.value.modelId) {
addSpec(form.value).then(() => {
proxy.$modal.msgSuccess("规格新增成功");
open.value = false;
listSpec({ modelId: form.value.modelId }).then(({ rows }) => {
const model = findInTreeByKey(
productList.value,
form.value.modelId
);
if (model) {
model.children = rows.map((el) => ({
...el,
key: el.specId,
}));
}
});
});
} else if (form.value.productId) {
/*新增型号*/
addModel(form.value).then(() => {
proxy.$modal.msgSuccess("型号新增成功");
open.value = false;
modelList({ productId: form.value.productId }).then(({ rows }) => {
const product = findInTreeByKey(
productList.value,
form.value.productId
);
if (product) {
product.children = rows.map((el) => ({
...el,
key: el.modelId,
children: [],
}));
}
});
});
} else {
addProduct(form.value).then(() => {
proxy.$modal.msgSuccess("产品新增成功");
open.value = false;
getList();
});
}
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _productIds = row.specId || row.modelId || row.productId || ids.value;
proxy.$modal
.confirm('是否确认删除编号为"' + _productIds + '"的数据项?')
.then(function () {
if (row.specId) {
return deleteSpec(_productIds);
} else if (row.modelId) {
return deleteModel(_productIds);
} else {
return delProduct(_productIds);
}
})
.then(() => {
if (row.specId) {
listSpec({ modelId: form.value.modelId }).then(({ rows }) => {
const model = findInTreeByKey(productList.value, form.value.modelId);
if (model) {
model.children = rows.map((el) => ({
...el,
key: el.specId,
}));
}
});
} else if (row.modelId) {
modelList({ productId: form.value.productId }).then(({ rows }) => {
const product = findInTreeByKey(
productList.value,
form.value.productId
);
if (product) {
product.children = rows.map((el) => ({
...el,
key: el.modelId,
children: [],
}));
}
});
} else {
getList();
}
proxy.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}
getList();
</script>

View File

@ -0,0 +1,126 @@
<script setup>
import { loadStockList, loadStockLogList } from "@/api/product/stock";
import { reactive, toRefs } from "vue";
import { dayjs } from "element-plus";
import ImagePreview from "@/components/ImagePreview/index.vue";
const queryRef = ref();
const showSearch = ref(false);
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
type: 1,
},
});
const total = ref(0);
const { queryParams } = toRefs(data);
const stockLogList = ref([]);
const getStockList = () => {
if (queryParams.value.type === 0) {
loadStockList(queryParams.value).then((resp) => {
total.value = resp.total;
stockLogList.value = resp.rows;
});
} else {
loadStockLogList(queryParams.value).then((resp) => {
total.value = resp.total;
stockLogList.value = resp.rows;
});
}
};
function handleQuery() {
queryParams.value.pageNum = 1;
getStockList();
}
/** 重置按钮操作 */
function resetQuery() {
queryRef.value?.resetFields();
handleQuery();
}
getStockList();
</script>
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type">
<el-option label="库存" :value="0" />
<el-option label="入库" :value="1" />
<el-option label="出库" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-radio-group @change="handleQuery" v-model="queryParams.type">
<el-radio-button :label="0">库存</el-radio-button>
<el-radio-button :label="1">入库</el-radio-button>
<el-radio-button :label="2">出库</el-radio-button>
</el-radio-group>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getStockList"
></right-toolbar>
</el-row>
<el-table :data="stockLogList">
<el-table-column align="center" label="产品" prop="productName" />
<el-table-column align="center" label="型号" prop="modelName" />
<el-table-column align="center" label="规格" prop="specName" />
<el-table-column
align="center"
v-if="queryParams.type === 0"
label="库存"
prop="stock"
/>
<el-table-column align="center" v-else label="数量" prop="total" />
<el-table-column
v-if="queryParams.type > 0"
align="center"
label="图片"
prop="date"
>
<template #default="{ row }">
<image-preview :width="50" :height="50" :src="row.provePic" />
</template>
</el-table-column>
<el-table-column
v-if="queryParams.type > 0"
align="center"
label="日期"
prop="date"
>
<template #default="{ row }">
{{ row.date ? dayjs(row.date).format("YYYY-MM-DD") : "-" }}
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getStockList"
/>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -6,7 +6,7 @@
:rules="registerRules"
class="register-form"
>
<h3 class="title">若依后台管理系统</h3>
<h3 class="title">口袋九章</h3>
<el-form-item prop="username">
<el-input
v-model="registerForm.username"

View File

@ -27,7 +27,7 @@ export default defineConfig(({ mode, command }) => {
server: {
port: 80,
host: true,
open: true,
open: false,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
"/dev-api": {