Merge remote-tracking branch 'origin/master'

This commit is contained in:
2025-09-22 10:23:20 +08:00
56 changed files with 1914 additions and 899 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 = '/prod-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

@@ -14,7 +14,7 @@
* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast) 版本。
* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)  
* 阿里云折扣场:[点我进入](http://aly.),腾讯云秒杀场:[点我进入](http://txy.)  
## 前端运行
@@ -62,8 +62,8 @@ yarn dev
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址http://vue.ruoyi.vip
文档地址http://doc.ruoyi.vip
演示地址http://vue.
文档地址http://doc.
## 演示图

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.png">
<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.9.0",
"description": "创高家具销售系统",
"description": "创高综合办公系统",
"author": "若依",
"license": "MIT",
"type": "module",

View File

@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询订单下人员工作及扣款信息列表
export function listWorkDeduction(query) {
return request({
url: '/oa/workDeduction/list',
method: 'get',
params: query
})
}
// 查询订单下人员工作及扣款信息详细
export function getWorkDeduction(deductionId) {
return request({
url: '/oa/workDeduction/' + deductionId,
method: 'get'
})
}
// 新增订单下人员工作及扣款信息
export function addWorkDeduction(data) {
return request({
url: '/oa/workDeduction',
method: 'post',
data: data
})
}
// 修改订单下人员工作及扣款信息
export function updateWorkDeduction(data) {
return request({
url: '/oa/workDeduction',
method: 'put',
data: data
})
}
// 删除订单下人员工作及扣款信息
export function delWorkDeduction(deductionId) {
return request({
url: '/oa/workDeduction/' + deductionId,
method: 'delete'
})
}

View File

@@ -1,100 +1,831 @@
// cover some element-ui styles
// ====================== 1. 基础尺寸变量Sass直接处理======================
// 间距/尺寸变量
$--spacing-sm: 4px;
$--spacing-base: 8px;
$--spacing-md: $--spacing-base;
$--spacing-lg: $--spacing-base * 2;
$--form-item-margin: $--spacing-base;
$--btn-height: 24px;
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
// ====================== 2. CSS尺寸变量与Sass变量同步======================
:root {
/* 间距/尺寸体系 */
--spacing-sm: #{$--spacing-sm};
--spacing-base: #{$--spacing-base};
--spacing-md: #{$--spacing-md};
--spacing-lg: #{$--spacing-lg};
--form-item-margin: #{$--form-item-margin};
--btn-height: #{$--btn-height};
}
// ====================== 3. Element UI适配 - 尺寸变量 =======================
// 尺寸变量
$--spacing-sm: $--spacing-sm !default;
$--spacing-base: $--spacing-base !default;
$--spacing-md: $--spacing-md !default;
$--spacing-lg: $--spacing-lg !default;
$--form-item-margin: $--form-item-margin !default;
$--btn-height: $--btn-height !default;
// ====================== 4. 全局尺寸样式 ======================
body {
font-size: 14px;
line-height: 1.5;
margin: 0;
padding: 0;
}
// 标签页内容溢出修复(尺寸相关)
.el-tabs__content {
overflow: visible !important;
}
// ====================== 5. 组件尺寸样式(按优先级合并)======================
// ---------------------- 5.1 按钮(紧凑尺寸)----------------------
@mixin button-variant() {
border-radius: 6px;
transition: all .25s ease;
height: $--btn-height;
padding: 0 var(--spacing-lg);
font-size: 13px;
&:hover,
&:focus {
transform: translateY(-1px);
}
&:active {
transform: translateY(1px);
}
&.is-disabled,
&[disabled] {
cursor: not-allowed;
}
}
// 主按钮(尺寸相关)
.el-button--primary {
@include button-variant();
}
// 功能按钮(尺寸相关)
.el-button--success,
.el-button--warning,
.el-button--danger,
.el-button--info {
@include button-variant();
}
// 文本按钮(尺寸相关)
.el-button--text {
height: auto;
padding: 4px 8px;
}
// 图标按钮(圆形紧凑尺寸)
.el-button.is-circle {
width: 26px;
height: 26px;
padding: 0;
border-radius: 50%;
&:only-child {
padding: 0;
}
}
// 按钮组(无缝衔接尺寸)
.el-button-group {
.el-button {
border-radius: 0;
margin: 0;
&:first-child {
border-radius: 6px 0 0 6px;
}
&:last-child {
border-radius: 0 6px 6px 0;
}
&:not(:last-child) {
border-right: none;
}
}
}
// 中号按钮(尺寸)
.el-button--medium {
padding: 4px 8px !important;
font-size: 12px;
height: $--btn-height - 4px;
}
// 迷你/小号按钮(超紧凑尺寸)
.el-button--mini,
.el-button--small,
.el-button {
padding: 4px 8px !important;
font-size: 12px;
height: $--btn-height - 4px;
&.is-circle {
padding: 4px !important;
}
}
// ---------------------- 5.2 表格(紧凑尺寸)----------------------
.el-table {
border-radius: 8px;
overflow: hidden;
margin-top: $--form-item-margin * 2; // 与表单间距
// 表头紧凑padding
.el-table__header-wrapper {
th.el-table__cell {
padding: 4px !important;
height: auto;
font-size: 13px;
}
}
.el-button + .el-button {
margin-left: 2px;
}
.el-button.el-button--text {
padding: 0 2px !important;
}
// 表体紧凑padding
.el-table__body-wrapper {
.el-table__cell {
padding: 2px 4px !important; // 超紧凑
font-size: 13px;
}
}
// 表尾紧凑padding
.el-table__footer-wrapper {
td {
padding: 4px !important;
}
}
// 适配尺寸类(统一紧凑)
&.el-table--medium .el-table__cell,
&.el-table--mini .el-table__cell {
padding: 0 !important;
}
}
.el-table__fixed-right {
// 表头紧凑padding
.el-table__fixed-header-wrapper {
th.el-table__cell {
padding: 4px !important;
font-size: 13px;
}
}
}
// 自定义表格工具类(补充紧凑)
.small-padding .cell {
padding-left: calc($--spacing-base / 2);
padding-right: calc($--spacing-base / 2);
}
.status-col .cell {
padding: 0 10px !important;
text-align: center;
}
// ---------------------- 5.3 日期范围选择器(尺寸适配)----------------------
.el-date-editor {
height: $--btn-height;
// 基础尺寸
&.el-range-editor {
padding: 0 8px;
height: $--btn-height;
// 图标尺寸
.el-input__icon {
width: 18px;
height: 18px;
line-height: 18px;
}
// 输入框尺寸
.el-range-input {
padding: 0 4px;
height: 100%;
font-size: 13px;
width: 80px;
}
// 分隔符尺寸
.el-range-separator {
padding: 0 4px;
font-size: 13px;
}
// 清除图标间距
.el-range__close-icon {
margin-left: 4px;
}
}
&--medium.el-input__inner {
height: auto;
}
}
.el-range-editor--medium.el-input__inner {
height: $--btn-height !important;
width: auto !important;
.el-range-separator {
padding: 0;
line-height: 16px;
font-size: 13px;
}
}
// 日期选择面板尺寸
.el-picker-panel {
border-radius: 6px;
// 头部导航尺寸
.el-picker-panel__header {
padding: 6px 10px;
.el-picker-panel__icon-btn {
width: 24px;
height: 24px;
line-height: 24px;
border-radius: 4px;
}
}
// 日期表格尺寸
.el-date-table {
border-collapse: separate;
border-spacing: 2px;
th {
padding: 4px 0;
font-size: 12px;
}
td {
padding: 0;
.el-date-table__cell {
width: 28px;
height: 28px;
line-height: 28px;
border-radius: 4px;
margin: 1px;
font-size: 13px;
}
}
}
// 范围选择器底部按钮尺寸
.el-range-picker__footer {
padding: 6px 10px;
button {
height: 24px;
line-height: 22px;
padding: 0 12px;
font-size: 12px;
border-radius: 4px;
}
}
}
// ---------------------- 5.4 表单/输入(统一尺寸)----------------------
// 表单项布局尺寸
.el-form-item {
margin-bottom: $--form-item-margin !important;
font-size: 13px;
// 标签间距
.el-form-item__label {
padding-right: $--spacing-base;
font-size: 13px;
}
// 搜索表单 inline 布局
&.search-form-item {
display: inline-block;
margin-right: $--spacing-base;
vertical-align: middle;
}
}
// 输入框(统一高度)
.el-input {
height: $--btn-height;
.el-input__inner {
border-radius: 0;
height: $--btn-height;
line-height: $--btn-height;
font-size: 13px;
}
// 输入框图标尺寸
.el-input__icon {
height: $--btn-height;
line-height: $--btn-height;
}
// 尺寸适配(统一高度)
&.el-input--small .el-input__inner,
&.el-input--medium .el-input__inner,
&.el-input--large .el-input__inner {
height: $--btn-height;
line-height: $--btn-height;
}
// 输入组尺寸
.el-input-group__append,
.el-input-group__prepend {
border-radius: 0;
font-size: 13px;
}
}
// 文本域尺寸
.el-textarea .el-textarea__inner {
border-radius: 0;
padding: $--spacing-base;
font-size: 13px;
}
// 数字输入框尺寸
.el-input-number {
.el-input__inner {
border-radius: 0;
}
// 增减按钮尺寸
.el-input-number__increase,
.el-input-number__decrease {
transition: all .2s ease;
height: $--btn-height;
line-height: $--btn-height;
}
.el-input-number__increase,
.el-input-number__decrease {
border-radius: 0;
}
// 迷你尺寸
&.el-input-number--small {
.el-input-number__increase,
.el-input-number__decrease {
width: 24px;
height: 24px;
}
}
// 无控制按钮尺寸
&.is-without-controls .el-input__inner {
border-radius: 0;
}
}
// 选择器/下拉框尺寸
.el-select {
.el-input__inner {
border-radius: 0;
}
// 下拉面板尺寸
.el-select-dropdown {
border-radius: 6px;
.el-select-dropdown__item {
padding: 6px 16px;
font-size: 13px;
}
}
}
// ---------------------- 5.5 卡片/对话框(尺寸层级)----------------------
// 卡片尺寸
.el-card {
border-radius: 8px;
overflow: hidden;
// 卡片头部尺寸
.el-card__header {
padding: $--spacing-md $--spacing-lg;
}
// 卡片主体尺寸
.el-card__body {
padding: $--spacing-lg;
}
}
// 对话框尺寸
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 10vh auto 0;
border-radius: 8px;
width: 60% !important; // 自适应宽度
// 对话框头部尺寸
.el-dialog__header {
padding: $--spacing-lg;
}
// 对话框主体尺寸
.el-dialog__body {
padding: $--spacing-lg;
max-height: 60vh;
overflow-y: auto;
}
// 对话框底部尺寸
.el-dialog__footer {
padding: $--spacing-md $--spacing-lg;
text-align: right;
}
}
// 抽屉尺寸
.el-drawer {
border-radius: 8px;
}
// Popover尺寸
.el-popover {
border-radius: 8px;
padding: $--spacing-lg;
// Popover 标题尺寸
.el-popover__title {
padding-bottom: $--spacing-md;
margin-bottom: $--spacing-md;
}
// Popover 底部尺寸
.el-popover__footer {
padding-top: $--spacing-md;
margin-top: $--spacing-md;
text-align: right;
}
}
.el-button-group+.el-popover {
margin-left: 5px;
}
// ---------------------- 5.6 标签页组件el-tabs尺寸 ----------------======
.el-tabs {
// 标签栏尺寸
.el-tabs__header {
margin: 0;
padding: 0;
// 标签导航容器尺寸
.el-tabs__nav {
height: 40px;
line-height: 40px;
padding: 0 var(--spacing-base);
}
// 标签项尺寸
.el-tabs__item {
height: 40px;
line-height: 40px;
font-size: 13px;
transition: all 0.2s ease;
}
// 标签滚动按钮尺寸
.el-tabs__nav-prev,
.el-tabs__nav-next {
width: 32px;
height: 32px;
line-height: 32px;
margin: 4px 0;
border-radius: 4px;
transition: all 0.2s ease;
}
}
// 标签内容区尺寸
.el-tabs__content {
padding: var(--spacing-lg);
border-radius: 0 0 6px 6px;
min-height: 100px; // 确保有基本高度
}
// 卡片类型标签页尺寸
&.el-tabs--card {
.el-tabs__header {
.el-tabs__nav {
border-radius: 6px;
overflow: hidden;
}
.el-tabs__item {
margin: 0 2px;
border-radius: 6px 6px 0 0;
}
}
.el-tabs__content {
border-radius: 0 6px 6px 6px;
margin-top: -1px;
}
}
// 左侧/右侧标签页尺寸
&.el-tabs--left,
&.el-tabs--right {
.el-tabs__header {
margin: 0 var(--spacing-lg) 0 0;
.el-tabs__nav {
flex-direction: column;
height: auto;
}
.el-tabs__item {
width: 100%;
}
}
&.el-tabs--right {
.el-tabs__header {
margin: 0 0 0 var(--spacing-lg);
}
}
.el-tabs__content {
border-radius: 6px;
}
}
// 标签页关闭按钮间距
.el-icon-close {
margin-left: var(--spacing-sm);
transition: all 0.2s ease;
}
// 可编辑标签添加按钮间距
.el-tabs__new-tab {
margin: 0 var(--spacing-sm);
transition: all 0.2s ease;
}
}
// ---------------------- 5.7 日期选择器(尺寸优化)----------------------
.el-date-picker {
.el-picker-panel {
border-radius: 8px;
// 头部尺寸
.el-date-picker__header {
padding: $--spacing-md;
}
// 日期表格尺寸
.el-date-table {
th {
padding: $--spacing-sm;
}
td {
padding: $--spacing-sm;
border-radius: 4px;
transition: all .2s;
}
}
// 快捷选项尺寸
.el-picker-panel__shortcut {
padding: $--spacing-md;
.el-picker-panel__shortcut-btn {
padding: 4px 10px;
border-radius: 4px;
transition: all .2s;
}
}
}
// 范围选择器分隔符尺寸
.el-range-separator {
padding: 0 $--spacing-md;
box-sizing: content-box;
}
// 范围输入框布局
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
}
// ---------------------- 5.8 描述列表组件el-descriptions- 尺寸适配 ======================
.el-descriptions {
width: 100%;
overflow: hidden;
// 描述列表头部尺寸
&__header {
padding: $--spacing-base $--spacing-lg;
font-size: 14px;
}
// 描述列表主体容器尺寸
&__body {
width: 100%;
}
// 描述列表行布局
&__row {
display: flex;
width: 100%;
// 最后一行布局
&:last-child {
border-bottom: none;
}
}
// 描述列表标签项尺寸
.el-descriptions-item__label {
padding: $--spacing-lg;
box-sizing: border-box;
white-space: nowrap;
font-size: 13px;
}
// 描述列表内容项尺寸
&-item__content {
padding: $--spacing-lg;
flex: 1;
box-sizing: border-box;
font-size: 13px;
word-break: break-word;
}
// 带边框模式尺寸
&--border {
border-radius: 8px;
overflow: hidden;
}
// 尺寸适配
&--small {
.el-descriptions__label,
.el-descriptions__content {
padding: calc($--spacing-base / 2) $--spacing-base;
font-size: 12px;
}
.el-descriptions__header {
padding: calc($--spacing-base / 2) $--spacing-base;
font-size: 13px;
}
}
&--large {
.el-descriptions__label,
.el-descriptions__content {
padding: $--spacing-lg $--spacing-lg * 1.5;
font-size: 14px;
}
.el-descriptions__header {
padding: $--spacing-base $--spacing-lg * 1.5;
font-size: 15px;
}
}
// 响应式适配
@media (max-width: 768px) {
.el-descriptions__row {
flex-direction: column;
}
.el-descriptions__label {
width: 100%;
}
}
}
// 菜单尺寸
.el-menu {
.el-menu-item,
.el-submenu__title {
height: 40px !important;
line-height: 40px !important;
}
// 折叠菜单布局
&.el-menu--collapse>div>.el-submenu>.el-submenu__title .el-submenu__icon-arrow {
display: none;
}
}
// ---------------------- 5.10 其他组件(标签/分页/上传)尺寸 ----------------------
// 标签尺寸
.el-tag {
padding: 2px 8px;
border-radius: 2px;
font-size: 12px;
height: auto;
margin-right: 0;
// 表格内标签尺寸
.cell & {
margin-right: 0;
padding: 1px !important;
border-radius: 0 !important;
}
}
// 分页尺寸
.el-pagination {
margin-top: 16px;
padding: 8px 0;
text-align: right;
.el-pager li {
margin: 0 2px;
min-width: 30px;
height: 30px;
line-height: 30px;
border-radius: 4px;
}
// 输入框后缀布局
.el-input__suffix {
position: absolute;
line-height: 0;
}
}
// 分页容器尺寸
div.pagination-container {
padding: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
.el-pagination {
padding: 0 !important;
}
}
// 上传组件尺寸
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0px;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
// 拖拽上传容器尺寸
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block
border-radius: 6px;
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
// 树形组件布局
.el-tree {
.el-tree-node {
&.is-current>.el-tree-node__content {
width: 100%;
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
.el-menu--collapse
> div
> .el-submenu
> .el-submenu__title
.el-submenu__icon-arrow {
display: none;
}
.el-dropdown .el-dropdown-link{
color: var(--el-color-primary) !important;
}
.el-select-dropdown {
z-index: 9999 !important; /* 需大于全屏容器的z-index */
}

View File

@@ -5,52 +5,52 @@
/** 基础通用 **/
.pt5 {
padding-top: 5px;
padding-top: 2px;
}
.pr5 {
padding-right: 5px;
padding-right: 2px;
}
.pb5 {
padding-bottom: 5px;
padding-bottom: 2px;
}
.mt5 {
margin-top: 5px;
margin-top: 2px;
}
.mr5 {
margin-right: 5px;
margin-right: 2px;
}
.mb5 {
margin-bottom: 5px;
margin-bottom: 2px;
}
.mb8 {
margin-bottom: 8px;
margin-bottom: 4px;
}
.ml5 {
margin-left: 5px;
margin-left: 2px;
}
.mt10 {
margin-top: 10px;
margin-top: 5px;
}
.mr10 {
margin-right: 10px;
margin-right: 5px;
}
.mb10 {
margin-bottom: 10px;
margin-bottom: 5px;
}
.ml10 {
margin-left: 10px;
margin-left: 5px;
}
.mt20 {
margin-top: 20px;
margin-top: 10px;
}
.mr20 {
margin-right: 20px;
margin-right: 10px;
}
.mb20 {
margin-bottom: 20px;
margin-bottom: 10px;
}
.ml20 {
margin-left: 20px;
margin-left: 10px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {

View File

@@ -96,18 +96,21 @@
</div>
<div class="module-actions">
<el-button
size="mini"
@click="moveModule(index, 'up')"
:disabled="index === 0"
icon="ArrowUp"
type="primary"
/>
<el-button
size="mini"
@click="moveModule(index, 'down')"
:disabled="index === businessModules.length - 1"
icon="ArrowDown"
type="primary"
/>
<el-button
size="mini"
@click="deleteModule(index)"
icon="Delete"
danger
@@ -261,11 +264,11 @@ export default {
</script>
<style lang="scss" scoped>
/* 外层容器统一样式:亮色背景(默认) */
/* 外层容器统一样式:亮色背景(默认)+ 紧凑化调整 */
.applications-wrapper {
padding: 20px;
padding: 16px; /* 整体内边距从20px减至16px */
/* 亮色主题变量 */
/* 亮色主题变量(保持原配色,仅调整尺寸相关) */
--color-text-primary: #111827;
--color-text-secondary: #4b5563;
--color-text-tertiary: #6b7280;
@@ -278,13 +281,13 @@ export default {
--color-border-primary: #dee2e6;
--shadow-card: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
--shadow-card-hover: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
--shadow-app-item: 0 2px 4px rgba(0, 0, 0, 0.08);
--shadow-card: 0 2px 8px 0 rgba(0, 0, 0, 0.06); /* 弱化阴影,适配紧凑布局 */
--shadow-card-hover: 0 3px 12px 0 rgba(0, 0, 0, 0.08);
--shadow-app-item: 0 1px 3px rgba(0, 0, 0, 0.06);
/* 一、常用应用区域样式 */
/* 一、常用应用区域:核心紧凑化 */
.business-area {
margin-bottom: 32px;
margin-bottom: 24px; /* 底部间距从32px减至24px */
}
/* 常用应用标题栏 */
@@ -292,11 +295,11 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
margin-bottom: 12px; /* 标题与列表间距从16px减至12px */
}
.business-area-title {
font-size: 18px;
font-size: 16px; /* 标题字体从18px减至16px */
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
@@ -305,34 +308,38 @@ export default {
.edit-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
gap: 4px; /* 图标与文字间距从6px减至4px */
padding: 4px 10px; /* 按钮内边距从6px12px减至4px10px */
background: var(--color-bg-primary);
border: none;
border-radius: 6px;
border-radius: 4px; /* 圆角从6px减至4px */
color: var(--color-text-primary);
cursor: pointer;
font-size: 14px;
font-size: 13px; /* 按钮字体从14px减至13px */
transition: all 0.2s;
&:hover {
background: var(--color-bg-secondary);
color: var(--color-text-primary);
}
:deep(svg) {
font-size: 14px; /* 按钮图标缩小 */
}
}
/* 常用应用列表 */
/* 常用应用列表:网格密度提升 */
.business-modules {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 24px;
grid-template-columns: repeat(6, 1fr); /* 保持6列通过gap压缩空间 */
gap: 16px; /* 卡片间距从24px减至16px */
}
.business-module {
display: flex;
align-items: center;
padding: 16px;
border-radius: 12px;
padding: 12px; /* 卡片内边距从16px减至12px */
border-radius: 8px; /* 圆角从12px减至8px */
background: var(--color-bg-card);
box-shadow: var(--shadow-card);
transition: all 0.2s;
@@ -340,111 +347,113 @@ export default {
&:hover {
box-shadow: var(--shadow-card-hover);
transform: translateY(-2px);
transform: translateY(-1px); /* hover上移从2px减至1px更克制 */
background: var(--color-bg-secondary);
}
}
.business-module-icon {
width: 40px;
height: 40px;
border-radius: 8px;
width: 32px; /* 图标容器从40px减至32px */
height: 32px;
border-radius: 6px; /* 圆角从8px减至6px */
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 12px;
margin-right: 8px; /* 图标与标题间距从12px减至8px */
background: var(--color-bg-icon);
:deep(svg) {
font-size: 20px;
font-size: 18px; /* 图标字体从20px减至18px */
color: var(--color-text-primary);
}
}
.business-module-title {
font-size: 16px;
font-size: 14px; /* 标题字体从16px减至14px */
font-weight: 500;
color: var(--color-text-primary);
margin: 0;
line-height: 1.3; /* 行高压缩,避免换行 */
}
/* 二、全部应用区域样式 */
/* 二、全部应用区域:密度优化 */
.all-applications-container {
padding: 20px;
border-radius: 8px;
padding: 12px; /* 容器内边距从20px减至12px */
border-radius: 6px; /* 圆角从8px减至6px */
background: var(--color-bg-card);
}
.title {
font-size: 18px;
font-size: 16px; /* 标题字体从18px减至16px */
font-weight: 600;
margin-bottom: 16px;
margin-bottom: 12px; /* 标题与tabs间距从16px减至12px */
color: var(--color-text-primary);
}
/* 应用列表网格 */
/* 应用列表网格:缩小间距 */
.app-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
padding-top: 10px;
margin: 10px 0;
gap: 12px; /* 卡片间距从16px减至12px */
padding-top: 6px; /* 顶部内边距从10px减至6px */
margin: 8px 0; /* 上下外边距从10px减至8px */
}
/* 应用卡片 */
/* 应用卡片:压缩尺寸+紧凑布局 */
.app-item {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
border-radius: 8px;
padding: 20px;
width: 160px;
transition: all 0.3s ease;
border-radius: 6px; /* 圆角从8px减至6px */
padding: 12px; /* 内边距从20px减至12px */
width: 140px; /* 卡片宽度从160px减至140px */
transition: all 0.2s ease; /* 缩短过渡时间 */
background-color: var(--color-bg-primary);
box-shadow: var(--shadow-app-item);
position: relative;
&:hover {
box-shadow: var(--shadow-card-hover);
transform: translateY(-2px);
transform: translateY(-1px); /* 上移从2px减至1px */
background-color: var(--color-bg-secondary);
}
}
/* 应用图标容器 */
/* 应用图标容器:缩小尺寸 */
.app-icon-wrapper {
width: 40px;
height: 40px;
border-radius: 8px;
width: 32px; /* 图标容器从40px减至32px */
height: 32px;
border-radius: 6px; /* 圆角从8px减至6px */
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
margin-right: 8px; /* 图标与文字间距从12px减至8px */
background-color: var(--color-bg-icon);
transition: background-color 0.3s ease;
transition: background-color 0.2s ease;
}
.app-icon {
font-size: 24px;
font-size: 20px; /* 图标字体从24px减至20px */
color: var(--color-text-primary);
}
/* 应用名称 */
/* 应用名称:缩小字体 */
.app-name {
font-size: 14px;
font-size: 13px; /* 字体从14px减至13px */
color: var(--color-text-primary);
flex: 1;
line-height: 1.2; /* 行高压缩 */
}
/* 三点菜单 */
/* 三点菜单:紧凑定位 */
.app-actions {
position: absolute;
top: 8px;
right: 8px;
top: 6px; /* 上定位从8px减至6px */
right: 6px; /* 右定位从8px减至6px */
opacity: 0;
transition: opacity 0.3s ease;
transition: opacity 0.2s ease;
}
.app-item:hover .app-actions {
@@ -454,15 +463,19 @@ export default {
.actions-icon {
cursor: pointer;
display: inline-block;
width: 16px;
height: 16px;
width: 14px; /* 图标容器从16px减至14px */
height: 14px;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-primary);
:deep(svg) {
font-size: 12px; /* 三点图标缩小 */
}
}
/* 三、设置弹窗样式 */
/* 三、设置弹窗:整体压缩 */
.dialog-mask {
position: fixed;
top: 0;
@@ -477,13 +490,13 @@ export default {
}
.setting-dialog {
width: 500px;
border-radius: 12px;
width: 450px; /* 弹窗宽度从500px减至450px */
border-radius: 8px; /* 圆角从12px减至8px */
background: var(--color-bg-card);
}
.dialog-header {
padding: 16px 24px;
padding: 12px 20px; /* 内边距从16px24px减至12px20px */
border-bottom: 1px solid var(--color-border-primary);
display: flex;
justify-content: space-between;
@@ -492,7 +505,7 @@ export default {
.dialog-header h3 {
margin: 0;
font-size: 18px;
font-size: 16px; /* 标题字体从18px减至16px */
color: var(--color-text-primary);
}
@@ -501,31 +514,35 @@ export default {
border: none;
cursor: pointer;
color: var(--color-text-tertiary);
font-size: 18px;
font-size: 16px; /* 关闭图标从18px减至16px */
&:hover {
color: var(--color-text-primary);
}
:deep(svg) {
font-size: 16px;
}
}
.dialog-content {
padding: 24px;
max-height: 400px;
padding: 16px; /* 内边距从24px减至16px */
max-height: 360px; /* 最大高度从400px减至360px */
overflow-y: auto;
background: var(--color-bg-primary);
}
/* 空状态提示 */
/* 空状态提示:压缩 padding */
.empty-tip {
text-align: center;
padding: 40px 0;
padding: 30px 0; /* 内边距从40px减至30px */
color: var(--color-text-tertiary);
font-size: 14px;
font-size: 13px; /* 字体从14px减至13px */
background: var(--color-bg-tertiary);
border-radius: 8px;
border-radius: 6px; /* 圆角从8px减至6px */
}
/* 常用应用编辑列表 */
/* 常用应用编辑列表:缩小行高 */
.module-list {
list-style: none;
padding: 0;
@@ -536,7 +553,7 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
padding: 10px 0; /* 行内边距从12px减至10px */
border-bottom: 1px solid var(--color-border-primary);
&:last-child {
@@ -547,45 +564,45 @@ export default {
.module-info {
display: flex;
align-items: center;
gap: 12px;
gap: 8px; /* 序号与名称间距从12px减至8px */
}
.module-index {
width: 24px;
height: 24px;
width: 20px; /* 序号容器从24px减至20px */
height: 20px;
border-radius: 50%;
background: var(--color-bg-tertiary);
color: var(--color-text-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-size: 11px; /* 序号字体从12px减至11px */
}
.module-name {
font-size: 16px;
font-size: 14px; /* 名称字体从16px减至14px */
color: var(--color-text-primary);
}
.module-actions {
display: flex;
gap: 8px;
gap: 6px; /* 按钮间距从8px减至6px */
}
/* 弹窗底部按钮 */
/* 弹窗底部按钮:紧凑化 */
.dialog-footer {
padding: 16px 24px;
padding: 12px 20px; /* 内边距从16px24px减至12px20px */
border-top: 1px solid var(--color-border-primary);
display: flex;
justify-content: flex-end;
gap: 12px;
gap: 8px; /* 按钮间距从12px减至8px */
}
.cancel-btn, .confirm-btn {
padding: 8px 16px;
border-radius: 6px;
padding: 6px 12px; /* 按钮内边距从8px16px减至6px12px */
border-radius: 4px; /* 圆角从6px减至4px */
cursor: pointer;
font-size: 14px;
font-size: 13px; /* 字体从14px减至13px */
transition: all 0.2s;
border: none;
}
@@ -609,9 +626,9 @@ export default {
}
}
/* 四、Element组件样式穿透调整 */
/* 四、Element组件样式穿透:适配紧凑布局 */
:deep(.el-tabs__header) {
margin-bottom: 12px;
margin-bottom: 8px; /* tabs与内容间距从12px减至8px */
}
:deep(.el-tabs__nav-wrap::after) {
@@ -620,8 +637,10 @@ export default {
}
:deep(.el-tabs__item) {
font-size: 15px;
color: var(--color-text-tertiary);
font-size: 14px; /* 标签字体从15px减至14px */
padding: 0 12px; /* 标签内边距优化 */
height: 32px; /* 标签高度压缩 */
line-height: 32px;
}
:deep(.el-tabs__item.is-active) {
@@ -629,15 +648,16 @@ export default {
}
:deep(.el-dropdown-menu) {
min-width: 120px;
min-width: 110px; /* 下拉菜单宽度从120px减至110px */
background-color: var(--color-bg-card);
border: 1px solid var(--color-border-primary);
border-radius: 4px; /* 圆角从默认减至4px */
}
:deep(.el-dropdown-item) {
padding: 6px 16px;
font-size: 14px;
color: var(--color-text-secondary);
padding: 4px 12px; /* 菜单项内边距从6px16px减至4px12px */
font-size: 13px; /* 字体从14px减至13px */
line-height: 1.4;
&:hover {
background-color: var(--color-bg-secondary);
@@ -645,19 +665,27 @@ export default {
}
:deep(.el-button) {
padding: 6px 12px;
padding: 4px 8px; /* 按钮内边距从6px12px减至4px8px */
color: var(--color-text-secondary);
background-color: var(--color-bg-tertiary);
border: none;
font-size: 13px; /* 字体从14px减至13px */
border-radius: 4px;
&:hover {
background-color: var(--color-bg-secondary);
}
}
/* 迷你按钮额外压缩 */
:deep(.el-button--mini) {
padding: 2px 6px;
font-size: 12px;
}
}
/* 暗色模式:保持配色同步,继承紧凑尺寸样式 */
.applications-wrapper.dark {
/* 暗色主题变量 */
--color-text-primary: #e5e7eb;
--color-text-secondary: #e5e7eb;
--color-text-tertiary: #9ca3af;
@@ -670,9 +698,8 @@ export default {
--color-border-primary: #3a3a4a;
--shadow-card: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
--shadow-card-hover: 0 4px 16px 0 rgba(0, 0, 0, 0.4);
--shadow-app-item: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-card: 0 2px 8px 0 rgba(0, 0, 0, 0.3);
--shadow-card-hover: 0 3px 12px 0 rgba(0, 0, 0, 0.4);
--shadow-app-item: 0 1px 3px rgba(0, 0, 0, 0.3);
}
</style>

View File

@@ -116,9 +116,9 @@
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn">
<el-button type="primary" @click="submitFill">确定</el-button>
<el-button type="warning" @click="clearCron">重置</el-button>
<el-button @click="hidePopup">取消</el-button>
<el-button size="mini" type="primary" @click="submitFill">确定</el-button>
<el-button size="mini" type="warning" @click="clearCron">重置</el-button>
<el-button size="mini" @click="hidePopup">取消</el-button>
</div>
</div>
</div>

View File

@@ -24,11 +24,11 @@ const props = defineProps({
},
width: {
type: [Number, String],
default: "50px"
default: "30px"
},
height: {
type: [Number, String],
default: "50px"
default: "30px"
}
})

View File

@@ -3,19 +3,20 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="mini"
type="primary"
plain
icon="Plus"
size="small"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
size="mini"
type="success"
plain
icon="Edit"
size="small"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
@@ -68,8 +69,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -10,7 +10,7 @@
value-key="productId"
>
<template #empty>
<el-button v-if="canAdd" @click="add" icon="Plus">未搜索到产品点击添加</el-button>
<el-button size="mini" v-if="canAdd" @click="add" icon="Plus">未搜索到产品点击添加</el-button>
<div v-else style="padding: 10px;">未搜索到产品</div>
</template>
<el-option
@@ -70,8 +70,8 @@
</el-form>
<template v-if="activeStep === 0" #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">创建产品</el-button>
<el-button @click="cancel"> </el-button>
<el-button size="mini" :loading="buttonLoading" type="primary" @click="submitForm">创建产品</el-button>
<el-button size="mini" @click="cancel"> </el-button>
</div>
</template>

View File

@@ -1,9 +1,35 @@
<template>
<div v-if="bomInfo.length > 0">
<el-tooltip :content="bomInfo.map(item => item.attrKey + ':' + item.attrValue).join(',')" class="bom-info"
placement="top">
<span>{{bomInfo.map(item => item.attrKey).join(',')}}</span>
</el-tooltip>
<!-- 始终显示带溢出隐藏的文本 -->
<template v-if="props.detail">
<!-- 当detail为true时显示可弹出的popover -->
<el-popover
placement="top"
trigger="hover"
:width="500"
>
<div>
<el-descriptions column="2" border>
<el-descriptions-item
v-for="item in bomInfo"
:key="item.attrKey"
:label="item.attrKey"
>
{{ item.attrValue }}
</el-descriptions-item>
</el-descriptions>
</div>
<template #reference>
<span class="bom-info">{{ bomInfo.map(item => item.attrKey).join(',') }}</span>
</template>
</el-popover>
</template>
<template v-else>
<!-- 当detail为false时仅显示文本不弹出popover -->
<span class="bom-info">{{ bomInfo.map(item => item.attrKey).join(',') }}</span>
</template>
</div>
<div v-else>
@@ -13,6 +39,7 @@
<script setup>
import { ref, watch } from 'vue';
import useProductStore from '@/store/modules/product';
import { ElPopover, ElDescriptions, ElDescriptionsItem } from 'element-plus';
// 定义组件props
const props = defineProps({
@@ -27,6 +54,11 @@ const props = defineProps({
itemId: {
type: [String, Number],
required: false
},
detail: {
type: Boolean,
required: false,
default: true // 默认显示popover
}
});
@@ -44,7 +76,6 @@ const getBomInfo = async () => {
let bomId = props.bomId;
if (!bomId) {
// 这里假设productMap是productStore中的属性
bomId = productStore.productMap?.[props.itemId]?.bomId;
}
@@ -74,10 +105,27 @@ watch(
<style scoped>
.bom-info {
cursor: pointer;
/* 溢出隐藏显示省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100px;
max-width: 150px;
/* 当不需要弹出时,指针样式改为默认 */
&:not(:hover) {
cursor: default;
}
}
/* 弹窗样式优化 */
:deep(.el-popover) {
padding: 10px;
}
/* 描述列表样式优化 */
:deep(.el-descriptions) {
font-size: 14px;
}
:deep(.el-descriptions__label) {
font-weight: 600;
}
</style>

View File

@@ -2,15 +2,15 @@
<div class="top-right-btn" :style="style">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button circle icon="Search" @click="toggleSearch()" />
<el-button size="small" circle icon="Search" @click="toggleSearch()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
<el-button size="small" circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-button size="small" circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" />
<el-button size="small" circle icon="Menu" />
<template #dropdown>
<el-dropdown-menu>
<!-- 全选/反选 按钮 -->

View File

@@ -46,7 +46,7 @@ export default {
},
size: {
type: String,
default: 'mini'
default: 'small'
},
showTop: {
type: Boolean,

View File

@@ -1,17 +1,19 @@
<template>
<div class="dashboard-cards">
<!-- 循环渲染4个统计卡片通过index控制特殊样式第三张无数值第四张无图表 -->
<el-card v-for="(card, index) in dataCards" :key="index" class="stats-card">
<div class="card-header">
<div class="card-info">
<h3 class="card-title">{{ card.title }}</h3>
<!-- 第三卡片不显示value -->
<p class="card-value">{{ card.value }}</p>
<!-- 第三卡片库存排行不显示数值用v-if控制显隐 -->
<p class="card-value" v-if="index !== 2">{{ card.value }}</p>
</div>
<!-- 右侧图标通过动态组件渲染颜色绑定卡片配置 -->
<el-icon class="card-icon" :style="{ color: card.color }">
<component :is="card.icon" />
</el-icon>
</div>
<!-- 第四卡片不显示图表 -->
<!-- 第四卡片系统状态不显示图表用v-if控制显隐 -->
<div class="chart-container" v-if="index !== 3">
<div :ref="el => chartRefs[index] = el" class="chart"></div>
</div>
@@ -20,185 +22,226 @@
</template>
<script setup>
// 1. 导入依赖图标、接口、echarts、Vue工具函数
import { ShoppingCart, Box, List, Monitor } from '@element-plus/icons-vue';
import { overview } from '@/api/oa/dashboard';
import * as echarts from 'echarts';
import { ref, onMounted, nextTick } from 'vue';
// 2. 图表颜色生成函数:支持透明度,适配折线图区域填充
const getColor = (index, alpha = 1) => {
const colors = [
`rgba(59, 130, 246, ${alpha})`,
`rgba(34, 197, 94, ${alpha})`,
`rgba(234, 179, 8, ${alpha})`,
`rgba(168, 85, 247, ${alpha})`
`rgba(59, 130, 246, ${alpha})`, // 蓝色(订单总量)
`rgba(245, 158, 11, ${alpha})`, // 黄色(薪资成本)
`rgba(34, 197, 94, ${alpha})`, // 绿色(库存排行)
`rgba(168, 85, 247, ${alpha})` // 紫色(系统状态)
];
return colors[index % colors.length];
};
// 3. 初始化图表函数仅处理前3张有图表的卡片避免空引用
const initCharts = () => {
nextTick(() => {
// 只初始化前3个卡片的图表
dataCards.value.slice(0, 3).forEach((card, index) => {
const chart = echarts.init(chartRefs.value[index]);
// 判断是否为第三个图表index=2使用柱状图配置
const isBarChart = index === 2;
const chartDom = chartRefs.value[index];
if (!chartDom) return; // 防止DOM未渲染导致报错
const chart = echarts.init(chartDom);
const isBarChart = index === 2; // 第三张用柱状图,其余用折线图
// 图表配置:紧凑化优化(隐藏坐标轴、压缩网格、调整柱子宽度)
const option = {
animation: false,
grid: {
left: 0,
right: 0,
top: 0,
bottom: 0
},
xAxis: {
type: 'category',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false, // 关闭动画提升性能
grid: { left: 0, right: 0, top: 0, bottom: 0 }, // 占满容器
xAxis: { type: 'category', show: false }, // 隐藏X轴
yAxis: { type: 'value', show: false }, // 隐藏Y轴
series: [{
data: card.chartData,
type: isBarChart ? 'bar' : 'line', // 第三个图表用柱状图,其他用折线图
smooth: !isBarChart, // 柱状图不需要平滑效果
showSymbol: false,
lineStyle: !isBarChart ? { // 折线图样式(柱状图不需要)
color: getColor(index)
} : undefined,
// 柱状图颜色配置
itemStyle: isBarChart ? {
color: getColor(index)
} : undefined,
// 折线图区域填充(柱状图不需要)
type: isBarChart ? 'bar' : 'line',
smooth: !isBarChart, // 折线图平滑,柱状图不需要
showSymbol: false, // 隐藏数据点
// 折线图样式:细线更紧凑
lineStyle: !isBarChart ? { color: getColor(index), width: 1.5 } : undefined,
// 柱状图样式:调整宽度+小圆角
itemStyle: isBarChart ? { color: getColor(index), borderRadius: 2 } : undefined,
barWidth: isBarChart ? '65%' : undefined, // 柱子占比65%,减少间距
// 折线图区域填充:增强视觉不占空间
areaStyle: !isBarChart ? {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: getColor(index, 0.2)
}, {
offset: 1,
color: getColor(index, 0.1)
}])
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getColor(index, 0.2) },
{ offset: 1, color: getColor(index, 0.05) }
])
} : undefined
}]
};
chart.setOption(option);
// 监听窗口resize确保图表自适应
window.addEventListener('resize', () => chart.resize());
});
});
};
// 4. 卡片数据初始化:默认值+接口待填充字段
const dataCards = ref([
{
title: '本周订单总量',
value: '2,384',
icon: ShoppingCart,
color: '#3B82F6', // blue-500
color: '#3B82F6',
chartData: [30, 40, 20, 50, 40, 60, 70]
},
{
title: '本周薪资成本',
value: '48',
icon: List,
color: '#F59E0B', // yellow-500
color: '#F59E0B',
chartData: [20, 40, 30, 50, 40, 60, 50]
},
{
title: '库存排行',
// 第三卡片不需要value
value: '-',
value: '-', // 第三卡片默认隐藏数值
icon: Box,
color: '#10B981', // green-500
color: '#10B981',
chartData: [40, 30, 50, 40, 60, 50, 70]
},
{
title: '系统状态',
value: '正常',
icon: Monitor,
color: '#8B5CF6', // purple-500
// 第四卡片不需要图表数据
chartData: []
color: '#8B5CF6',
chartData: [] // 第四卡片图表数据
}
]);
// 5. 图表DOM引用存储前3张卡片的图表容器
const chartRefs = ref([]);
// 6. 页面挂载:请求接口数据+初始化图表
onMounted(() => {
overview().then(res => {
overview()
.then(res => {
// 填充订单总量数据
dataCards.value[0].value = res.data.orderStatistics.weekOrderCount;
dataCards.value[0].chartData = res.data.orderStatistics.weeklyTrend.map(item => item.value);
// 填充薪资成本数据
dataCards.value[1].value = res.data.salaryStatistics.weekSalary;
dataCards.value[1].chartData = res.data.salaryStatistics.weeklyTrend.map(item => item.value);
// 处理库存排行数据
// 填充库存排行图表数据(无数值)
dataCards.value[2].chartData = res.data.stockRanking.map(item => item.quantity);
// 数据更新后初始化图表
initCharts();
})
})
.catch(err => {
console.error('仪表盘数据请求失败:', err);
// 失败时仍初始化默认图表,避免页面空白
initCharts();
});
});
</script>
<style lang="scss">
<style lang="scss" scoped>
// 1. 卡片容器:网格布局紧凑化
.dashboard-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
margin-bottom: 2rem;
gap: 0.75rem; // 间距从1.5rem减至0.75rem减少50%
margin-bottom: 1.25rem; // 底部外间距从2rem减至1.25rem
padding: 0 0.5rem; // 轻微左右内边距,避免贴边
}
// 2. 单个卡片:整体压缩
.stats-card {
border-radius: 0.5rem !important;
border-radius: 0.375rem !important; // 圆角从0.5rem减至0.375rem
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 0.75rem !important; // 内边距从1.5rem减至0.75rem减少50%
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); // 弱化阴影
transform: scale(1);
transition: all 0.3s ease;
transition: all 0.2s ease; // 缩短过渡时间
// hover效果轻微放大不占用过多空间
&:hover {
transform: scale(1.02);
transform: scale(1.01); // 放大比例从1.02减至1.01
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
}
// 3. 卡片头部:垂直压缩
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
margin-bottom: 0.4rem; // 头部与图表间距从1rem减至0.4rem
}
// 4. 标题+数值容器:行高压缩
.card-info {
// 用于包裹标题和值
line-height: 1.15; // 减少垂直占用
}
// 5. 卡片标题:字体缩小+间距压缩
.card-title {
color: #6B7280; // gray-500
font-size: 0.875rem;
margin-bottom: 0.25rem;
color: #6B7280;
font-size: 0.725rem; // 字体从0.875rem减至0.725rem
margin-bottom: 0.1rem; // 与数值间距从0.25rem减至0.1rem
font-weight: 500;
margin: 0 0 0.1rem 0; // 清除默认margin
}
// 6. 卡片数值:字体缩小+行高优化
.card-value {
font-size: 1.5rem;
font-size: 1.15rem; // 字体从1.5rem减至1.15rem
font-weight: 600;
line-height: 1.25; // 避免换行
color: #1F2937;
margin: 0; // 清除默认margin
}
// 7. 卡片图标:尺寸缩小+位置优化
.card-icon {
font-size: 1.5rem;
font-size: 1.15rem; // 图标从1.5rem减至1.15rem
margin-top: 0.05rem; // 轻微上移,与标题对齐
}
// 8. 图表容器:高度压缩
.chart-container {
height: 3rem;
height: 2.2rem; // 图表高度从3rem减至2.2rem减少27%
width: 100%;
margin-top: 0.15rem; // 顶部微小间距,避免贴边
overflow: hidden; // 防止图表溢出
}
// 9. 图表DOM占满容器
.chart {
width: 100%;
height: 100%;
}
}
// 第四个卡片没有图表,调整一下底部
// 10. 特殊卡片处理:第四张无图表,清除底部
.stats-card:nth-child(4) .card-header {
margin-bottom: 0;
}
// 11. 特殊卡片处理:第三张无数值,图标居中
.stats-card:nth-child(3) .card-header {
align-items: center; // 避免顶部留白
}
// 12. 响应式适配:小屏幕保持紧凑
@media (max-width: 1200px) {
.dashboard-cards {
gap: 0.6rem; // 进一步缩小间距
}
.card-title {
font-size: 0.7rem;
}
.card-value {
font-size: 1.1rem;
}
}
</style>

View File

@@ -135,8 +135,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="60px">
<el-form-item label="汇报标题" prop="reportTitle">
<el-input
v-model="queryParams.reportTitle"
@@ -113,8 +113,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -115,8 +115,8 @@
<el-table v-loading="loading" :data="expressList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="物流号" align="center" prop="expressCode"/>
<el-table-column label="数据状态" align="center" prop="status">
<el-table-column label="物流号" align="center" prop="expressCode"/>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status===0" type="warning">未确认</el-tag>
<el-tag v-if="scope.row.status===1" type="primary">进行中</el-tag>
@@ -139,9 +139,9 @@
<ExpressRemainTime :planDate="scope.row.planDate" :status="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="对方姓名" align="center" prop="supplyName"/>
<el-table-column label="对方姓名" align="center" prop="supplyName" width="90"/>
<el-table-column label="负责人" align="center" prop="ownerName"/>
<el-table-column label="负责人手机" align="center" prop="ownerPhone"/>
<el-table-column label="手机" align="center" prop="ownerPhone"/>
<el-table-column label="计划到货时间" align="center" prop="planDate" width="180">
<template #default="scope">
<span>{{parseTime(scope.row.planDate,'{y}-{m}-{d}')}}</span>
@@ -152,7 +152,7 @@
<span>{{scope.row.updateTime}}</span>
</template>
</el-table-column>
<el-table-column label="物流公司标识" align="center" prop="expressType">
<el-table-column label="物流公司" align="center" prop="expressType" width="90">
<template #default="scope">
<dict-tag :options="oa_express_type" :value="scope.row.expressType"/>
</template>
@@ -222,8 +222,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -120,8 +120,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -6,8 +6,8 @@
<el-card class="list-container">
<pagination
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<div
@@ -20,38 +20,32 @@
<div style="display: flex;justify-content: space-between;">
<div>
<div class="message-title">
<!-- 已读/未读标记这里用一个简单的小圆点来表示也可以根据需求改成文字或图标 -->
<span class="state-dot"></span>
<span>{{ msg.title }}</span>
</div>
<div class="message-brief">{{ msg.state === 0 ? '点击查看' : '已读' }}</div>
</div>
<div>
<el-button icon="Delete" @click="handleDel(msg)" circle></el-button>
<el-button icon="Delete" @click="handleDel(msg)" size="mini"></el-button>
</div>
</div>
</div>
</el-card>
</el-col>
<!-- 右侧显示消息详情或空状态 -->
<el-col :span="16" class="message-detail" v-loading="msgLoading">
<!-- 在右上角放置一个圆形加号按钮点击后打开对话框 -->
<div class="add-feedback-btn">
<el-button
circle
type="primary"
icon="Plus"
@click="dialogVisible = true"
size="medium"
></el-button>
</div>
<!-- 详情内容 -->
<el-card v-if="selectedMessage" class="detail-container">
<!-- v-html 展示富文本内容 -->
<div v-html="selectedMessage.content"></div>
</el-card>
<div v-else class="detail-empty">
@@ -64,27 +58,28 @@
<el-dialog
title="新增反馈"
v-model="dialogVisible"
width="800px"
width="600px"
@close="resetForm"
>
<el-form
ref="formRef"
:model="form"
label-width="80px"
label-width="70px"
:rules="rules"
status-icon
size="small"
>
<el-form-item label="标题" prop="title">
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="反馈内容">
<editor v-model="form.content" :min-height="192"/>
<editor v-model="form.content" :min-height="160"/>
</el-form-item>
</el-form>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAddFeedback">提交</el-button>
<el-button size="small" @click="dialogVisible = false">取消</el-button>
<el-button size="small" type="primary" @click="handleAddFeedback">提交</el-button>
</span>
</el-dialog>
</div>
@@ -98,28 +93,19 @@ export default {
data() {
return {
messageList: [],
// 当前选中的消息
selectedMessage: null,
// 分页查询参数
queryParams: {
pageSize: 10,
pageNum: 1,
},
total: 0,
loading: false,
masLoading:false,
// 新增反馈对话框的显示控制
msgLoading: false,
dialogVisible: false,
// 表单数据
form: {
title: "",
content: "",
// 你可以根据实际需求添加更多字段
},
// 表单校验规则(示例)
rules: {
title: [
{required: true, message: "请输入标题", trigger: "blur"},
@@ -135,7 +121,6 @@ export default {
this.getList();
},
methods: {
// 处理删除
handleDel(row) {
delFeedback(row.feedbackId).then(res => {
this.$message.success("删除成功!");
@@ -143,49 +128,35 @@ export default {
this.selectedMessage = null;
})
},
// 获取反馈列表
getList() {
this.loading = true;
listFeedback(this.queryParams).then((res) => {
// 这里根据你的后端返回结构进行赋值
this.messageList = res.rows;
this.total = res.total;
this.loading = false;
});
},
// 选中某条消息
handleSelectMessage(msg) {
this.msgLoading = true;
this.selectedMessage = msg;
// 将消息状态改为已读
if (msg.state === 0) {
msg.state = 1
toRead(msg).then((res) => {
})
toRead(msg).then((res) => {})
}
this.msgLoading = false;
// 设置当前选中的消息
},
// 提交新增反馈
handleAddFeedback() {
this.$refs.formRef.validate((valid) => {
if (!valid) return; // 如果表单校验不通过直接return
// 调用新增API
if (!valid) return;
addFeedback(this.form).then((res) => {
// 假设请求成功后需要刷新列表
this.$message.success("反馈新增成功!");
this.dialogVisible = false;
this.getList();
}).catch(err => {
// 错误处理
this.$message.error(err.message || "新增失败");
});
});
},
// 对话框关闭时重置表单
resetForm() {
this.$refs.formRef && this.$refs.formRef.resetFields();
},
@@ -195,21 +166,23 @@ export default {
<style scoped>
.message-view-page {
padding: 20px;
padding: 12px;
box-sizing: border-box;
}
.list-container {
height: 600px; /* 根据需要自行调整 */
height: 500px;
overflow-y: auto;
padding: 8px;
}
/* 消息列表 */
.message-item {
cursor: pointer;
padding: 10px;
margin-bottom: 8px;
padding: 6px 8px;
margin-bottom: 4px;
border-bottom: 1px solid #ebeef5;
transition: background-color 0.2s;
}
.message-item:hover {
@@ -218,58 +191,84 @@ export default {
/* 未读消息红点 */
.message-item.unread .state-dot {
background-color: #fa5555; /* 未读的红色小圆点 */
background-color: #fa5555;
}
.message-title {
display: flex;
align-items: center;
font-size: 13px;
line-height: 1.4;
}
/* 小圆点标记 */
.state-dot {
display: inline-block;
width: 8px;
height: 8px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #cccccc; /* 已读时的灰色小圆点 */
margin-right: 8px;
background-color: #cccccc;
margin-right: 6px;
}
.message-brief {
color: #666;
font-size: 12px;
font-size: 11px;
margin-top: 2px;
}
/* 右侧详情 */
.message-detail {
padding-left: 20px;
position: relative; /* 为了定位“加号按钮” */
padding-left: 12px;
position: relative;
}
/* 在右上角放置一个圆形加号按钮 */
/* 加号按钮 */
.add-feedback-btn {
position: absolute;
top: 0;
right: 0;
margin: 10px;
margin: 6px;
}
.detail-container {
height: 600px; /* 根据需要自行调整 */
height: 500px;
overflow-y: auto;
padding: 12px;
}
.detail-empty {
display: flex;
align-items: center;
justify-content: center;
height: 600px; /* 根据需要自行调整 */
height: 500px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
/* 对话框底部按钮居中或居右,可自行调整 */
/* 对话框样式 */
::v-deep .el-dialog__header {
padding: 10px 15px;
}
::v-deep .el-dialog__body {
padding: 10px 15px;
}
::v-deep .el-dialog__footer {
padding: 8px 15px;
}
::v-deep .el-form-item {
margin-bottom: 10px;
}
::v-deep .el-input__inner {
height: 30px;
line-height: 30px;
font-size: 13px;
}
.dialog-footer {
text-align: right;
}

View File

@@ -141,8 +141,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -1,9 +1,9 @@
<template>
<div class="login" :class="{ 'dark': isDark }">
<div class="login-container">
<div class="login-right">
<!-- <div class="login-right">
<img :src="RightImage" alt="" class="right-img" />
</div>
</div> -->
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">
{{ title }}
@@ -30,7 +30,7 @@
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleLogin">
<el-button :loading="loading" size="large" type="primary" style="width:100%; height: 40px;" @click.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
@@ -42,9 +42,9 @@
</div>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
</div>
<!-- <div class="el-login-footer">
<span>Copyright © 2018-2025 All Rights Reserved.</span>
</div> -->
</div>
</template>
@@ -54,7 +54,7 @@ import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from '@/store/modules/user'
import useProductStore from '@/store/modules/product'
import RightImage from '@/assets/images/right.png'
// import RightImage from '@/assets/logo/logo.png'
import { useDark } from "@vueuse/core"
const isDark = useDark()
@@ -161,10 +161,11 @@ getCookie()
align-items: center;
height: 100%;
background-size: cover;
background-image: url('@/assets/images/back.jpg');
/* 亮色主题变量 */
--color-bg-primary: #f5f7fa;
--color-bg-form: #ffffff;
--color-bg-primary: #f5f7fa90;
--color-bg-form: #f5f7fa90;
--color-bg-input: #f0f2f5;
--color-border-input: #dcdfe6;
--color-text-primary: #303133;
@@ -197,7 +198,12 @@ getCookie()
justify-content: center;
align-items: center;
box-sizing: border-box;
width: 60%;
width: 30%;
border-radius: 6px;
backdrop-filter: blur(10px);
background: var(--color-bg-form);
padding: 25px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 25px 25px 5px 25px;
}
@@ -215,9 +221,9 @@ getCookie()
z-index: 1;
flex: 3;
border-radius: 6px;
background: var(--color-bg-form);
// background: var(--color-bg-form);
padding: 25px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
// box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.el-input {
height: 40px;
@@ -243,6 +249,7 @@ getCookie()
.login-right {
display: flex;
padding: 30px;
height: 55vh;
flex: 4;
}

View File

@@ -115,8 +115,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -76,7 +76,7 @@
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<!-- 添加或修改资金日记账对话框 -->

View File

@@ -5,8 +5,6 @@
<!-- 左侧使用klp-list组件占6列 -->
<el-col :span="6" style="display: table-cell;">
<!-- 搜索表单 - 精简搜索项 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"
class="mb-4">
<el-row :gutter="10">
<el-col :span="16">
<el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable @change="handleQuery" />
@@ -15,7 +13,6 @@
<el-button type="primary" plain icon="Plus" size="small" @click="handleAdd" class="w-full">新增订单</el-button>
</el-col>
</el-row>
</el-form>
<!-- klp-list组件 -->
<klp-list :list-data="orderList" :model-value="selectedIds" title-field="orderCode" list-key="orderId"
@@ -29,8 +26,8 @@
</klp-list>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" class="mt-4" layout="total, pager" />
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" class="mt-4" layout="total, pager" />
</el-col>
<!-- 右侧详情区占18列 -->

View File

@@ -67,8 +67,12 @@
<!-- 添加或修改退换货管理对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="returnExchangeRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="关联订单明细ID" prop="orderDetailId">
<el-input v-model="form.orderDetailId" placeholder="请输入关联订单明细ID" />
<el-form-item label="订单明细" prop="orderDetailId">
<el-select v-model="form.orderDetailId" placeholder="请选择订单明细">
<el-option v-for="item in orderDetailList" :key="item.detailId" :value="item.detailId">
<ProductInfo :productId="item.productId" />
</el-option>
</el-select>
</el-form-item>
<el-form-item label="客户ID" prop="customerId">
<CustomerSelect v-model="form.customerId" />
@@ -95,7 +99,9 @@
<script setup name="ReturnExchange">
import { listReturnExchange, getReturnExchange, delReturnExchange, addReturnExchange, updateReturnExchange } from "@/api/oa/returnExchange";
import { listOrderDetail } from "@/api/oms/orderDetail";
import CustomerSelect from '@/components/CustomerSelect/index.vue';
import ProductInfo from '@/components/Renderer/ProductInfo.vue';
const { proxy } = getCurrentInstance();
@@ -140,6 +146,11 @@ const data = reactive({
const { queryParams, form, rules } = toRefs(data);
watch(() => props.orderId, () => {
getOrderDetailList();
getList();
}, { immediate: true });
/** 查询退换货管理列表 */
function getList() {
loading.value = true;
@@ -150,6 +161,15 @@ function getList() {
});
}
const orderDetailList = ref([]);
function getOrderDetailList() {
listOrderDetail({
orderId: props.orderId,
}).then(response => {
orderDetailList.value = response.rows;
});
}
// 取消按钮
function cancel() {
open.value = false;

View File

@@ -8,23 +8,23 @@
<el-input v-model="queryParams.incomeType" 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-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" 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-button size="small" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
<el-button size="small" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
<el-button size="small" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
<el-button size="small" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>

View File

@@ -113,8 +113,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -113,8 +113,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -16,14 +16,15 @@
<el-input v-model="queryParams.type" placeholder="请输入类型" />
</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-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="small"
type="primary"
plain
icon="Plus"
@@ -32,6 +33,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="success"
plain
icon="Edit"
@@ -41,6 +43,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="danger"
plain
icon="Delete"
@@ -50,6 +53,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="warning"
plain
icon="Download"

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form :model="queryParams" ref="queryRef" :inline="true" size="small" v-show="showSearch" label-width="60px">
<el-form-item label="薪酬类型" prop="paymentType">
<el-radio-group v-model="queryParams.paymentType" @change="getList">
<el-radio-button label="hourly">计时</el-radio-button>

View File

@@ -5,7 +5,7 @@
<div class="container flex items-center justify-between gap-4">
<!-- 日期选择 -->
<div class="date-filter flex items-center gap-2">
<el-radio-group v-model="selectedDateRange">
<el-radio-group v-model="selectedDateRange" size="small">
<el-radio-button v-for="btn in dateButtons" :key="btn.value" :label="btn.value">
{{ btn.label }}
</el-radio-button>
@@ -25,7 +25,7 @@
</div> -->
<!-- 筛选器 -->
<div class="filters flex items-center gap-4">
<el-dropdown trigger="hover">
<!-- <el-dropdown trigger="hover">
<el-button class="filter-btn whitespace-nowrap">
记录类型<el-icon class="el-icon--right">
<ArrowDown />
@@ -40,7 +40,7 @@
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-dropdown> -->
<!-- <el-dropdown trigger="click">
<el-button class="filter-btn whitespace-nowrap">
状态筛选<el-icon class="el-icon--right">
@@ -123,6 +123,23 @@
<div id="heatmapChart" class="chart-container"></div>
</el-card>
</el-col>
<el-col :span="12" class="mt-6">
<el-card shadow="hover" class="chart-card">
<template #header>
<h3 class="chart-title text-lg font-medium">薪资支出趋势</h3>
</template>
<div id="salaryTrendChart" class="chart-container"></div>
</el-card>
</el-col>
<el-col :span="12" class="mt-6">
<el-card shadow="hover" class="chart-card">
<template #header>
<h3 class="chart-title text-lg font-medium">薪资支出排行按人</h3>
</template>
<div id="salaryRankChart" class="chart-container"></div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 数据表格 -->
@@ -130,7 +147,6 @@
<el-card shadow="hover" class="table-card">
<el-table :data="tableList" class="full-width-table">
<el-table-column v-for="col in tableColumns" :key="col.key" :prop="col.key" :label="col.label">
<template #default="{ row }" v-if="col.key === 'recordType'">
<div class="flex items-center">
<el-icon :class="getTypeIconColor(row.recordType)">
@@ -248,13 +264,21 @@ const statistics = ref([
trend: 8.3,
icon: 'Document',
iconColor: 'text-green-500'
},
{
label: '薪资总支出',
value: '100,000',
trend: 12.5,
icon: 'Money',
iconColor: 'text-blue-500'
}
]);
const setStatistics = ({ totalDuration, totalRecords, averageDuration }) => {
const setStatistics = ({ totalDuration, totalRecords, averageDuration, totalWage }) => {
statistics.value[0].value = totalRecords
statistics.value[1].value = averageDuration
statistics.value[2].value = totalDuration
statistics.value[3].value = totalWage
}
const setTrend = ({ attendanceData, overtimeData, travelData, xAxis }) => {
@@ -444,6 +468,85 @@ const setDuration = ({ durationData, xAxis }) => {
});
}
// 新增1. 薪资支出趋势折线图配置
const setSalaryTrend = ({ xAxis, salaryData }) => {
salaryTrendChart.value.setOption({
animation: false,
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>薪资支出:¥{c}', // 显示日期+薪资(带¥符号)
axisPointer: { type: 'shadow' }
},
legend: { data: ['薪资支出'], left: 'center', top: 0 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: xAxis,
axisLabel: { rotate: 30, margin: 10 } // 日期旋转避免重叠
},
yAxis: {
type: 'value',
name: '薪资(元)',
min: 0,
axisLabel: { formatter: '¥{value}' } // y轴显示¥符号
},
series: [
{
name: '薪资支出',
type: 'line',
data: salaryData,
lineStyle: { color: '#e11d48', width: 2 }, // 红色系(区分其他图表)
symbol: 'circle',
symbolSize: 6,
itemStyle: { color: '#e11d48' }
}
]
});
};
// 新增2. 薪资支出按人柱状图配置(横向,参考出勤排行)
const setSalaryRank = ({ users, salaries }) => {
salaryRankChart.value.setOption({
animation: false,
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>总薪资:¥{c}',
axisPointer: { type: 'shadow' }
},
legend: { data: ['总薪资'], left: 'left', top: 0 },
grid: { left: '15%', right: '8%', bottom: '3%', containLabel: true }, // 左移适配用户名
xAxis: {
type: 'value',
name: '薪资(元)',
min: 0,
axisLabel: { formatter: '¥{value}' }
},
yAxis: {
type: 'category',
data: users,
axisLabel: { rotate: 0, margin: 15 }
},
series: [
{
name: '总薪资',
type: 'bar',
data: salaries,
itemStyle: {
color: '#e11d48',
borderRadius: [0, 4, 4, 0] // 横向柱状图右侧圆角
},
label: {
show: true,
position: 'right',
formatter: '¥{c}', // 标签显示¥符号
fontSize: 12
}
}
]
});
};
const getTypeIcon = (type) => {
const icons = {
attendance: Check,
@@ -461,6 +564,8 @@ const trendChart = ref(null);
const durationChart = ref(null);
const heatmapChart = ref(null);
const attendanceRankChart = ref(null);
const salaryTrendChart = ref(null); // 薪资支出趋势折线图
const salaryRankChart = ref(null); // 薪资支出按人柱状图
const initCharts = () => {
trendChart.value = echarts.init(document.querySelector('#trendChart'));
@@ -468,6 +573,8 @@ const initCharts = () => {
attendanceRankChart.value = echarts.init(document.querySelector('#attendanceRankChart'));
durationChart.value = echarts.init(document.querySelector('#durationChart'));
heatmapChart.value = echarts.init(document.querySelector('#heatmapChart'));
salaryTrendChart.value = echarts.init(document.querySelector('#salaryTrendChart'));
salaryRankChart.value = echarts.init(document.querySelector('#salaryRankChart'));
};
const updateCharts = () => {
@@ -480,6 +587,11 @@ const updateCharts = () => {
setAttendanceRank(attendanceRankData)
const durationData = formatters.duration(list.value)
setDuration(durationData)
// 新增:更新薪资图表
const salaryTrendData = formatters.salaryTrend(list.value);
setSalaryTrend(salaryTrendData);
const salaryRankData = formatters.salaryRank(list.value);
setSalaryRank(salaryRankData);
}
const formatters = {
@@ -489,11 +601,13 @@ const formatters = {
const totalRecords = list.length
// 可能出现NAN所以需要处理
const averageDuration = totalRecords > 0 ? (totalDuration / totalRecords).toFixed(2) : 0
const totalWage = list.reduce((acc, item) => acc + item.wage, 0)
console.log(totalDuration, totalRecords, averageDuration)
return {
totalDuration,
totalRecords,
averageDuration
averageDuration,
totalWage
}
},
trend: (list) => {
@@ -693,6 +807,55 @@ const formatters = {
xAxis: Object.keys(map),
durationData: Object.values(map)
}
},
// 新增1. 薪资支出趋势数据格式化(按日期分组)
salaryTrend: (list) => {
const salaryData = [];
const dateMap = new Map(); // 去重日期并排序
// 按日期累加薪资
list.forEach(item => {
const { recordDate, wage = 0 } = item; // 兼容无wage字段的情况
if (!dateMap.has(recordDate)) dateMap.set(recordDate, true);
const exists = salaryData.find(i => i.date === recordDate);
if (exists) {
exists.salary += parseFloat(wage);
} else {
salaryData.push({ date: recordDate, salary: parseFloat(wage) });
}
});
// 日期排序,确保折线图顺序正确
const xAxis = Array.from(dateMap.keys()).sort();
// 补全缺失日期的薪资为0
const filledSalaryData = xAxis.map(date => {
const item = salaryData.find(i => i.date === date);
return item ? item.salary.toFixed(2) : 0;
});
return { xAxis, salaryData: filledSalaryData };
},
// 新增2. 薪资支出按人排行数据格式化取前10
salaryRank: (list) => {
const userSalary = {};
// 按用户名累加总薪资
list.forEach(item => {
const { nickName, wage = 0 } = item;
userSalary[nickName] = (userSalary[nickName] || 0) + parseFloat(wage);
});
// 按薪资降序排序取前10名
const sortedUsers = Object.entries(userSalary)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
return {
users: sortedUsers.map(item => item[0]), // 用户名
salaries: sortedUsers.map(item => item[1].toFixed(2)) // 总薪资保留2位小数
};
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form :model="queryParams" ref="queryRef" size="small" :inline="true" v-show="showSearch" label-width="60px">
<el-form-item label="员工" prop="employeeName">
<user-select v-model="queryParams.employeeId" placeholder="请选择员工" />
</el-form-item>

View File

@@ -0,0 +1,328 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" size="small" v-show="showSearch" label-width="60px">
<el-form-item label="订单编号" prop="orderId">
<el-select style="width: 150px;" clearable v-model="queryParams.orderId" placeholder="请选择订单编号" filterable :loading="loading">
<el-option v-for="item in orderList" :key="item.orderId" :value="item.orderId" :label="item.orderCode"/>
</el-select>
</el-form-item>
<el-form-item label="人员名字" prop="personName">
<el-input
v-model="queryParams.personName"
placeholder="请输入人员名字"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="扣款原因" prop="deductionReason">
<el-input
v-model="queryParams.deductionReason"
placeholder="请输入扣款原因"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="工作日期" prop="workDate">
<el-date-picker clearable
v-model="queryParams.workDate"
type="date"
value-format="YYYY-MM-DD 00:00:00"
placeholder="请选择工作日期">
</el-date-picker>
</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>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="workDeductionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="订单编号" align="center" prop="orderId" />
<el-table-column label="人员名字" align="center" prop="personName" />
<el-table-column label="工作内容" align="center" prop="workContent" />
<el-table-column label="扣款金额" align="center" prop="deductionAmount" />
<el-table-column label="扣款原因" align="center" prop="deductionReason" />
<el-table-column label="工作日期" align="center" prop="workDate" width="180">
<template #default="scope">
<span>{{ formatterTime(scope.row.workDate) }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="状态0-有效1-无效" align="center" prop="status" /> -->
<el-table-column label="备注" align="center" prop="remark" />
<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)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</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="workDeductionRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="关联订单" prop="orderId">
<el-select style="width: 150px;" v-model="form.orderId" placeholder="请选择关联订单" filterable remote :loading="loading" @remote-change="remoteOrderList">
<!-- 支持远程搜索 -->
<el-option v-for="item in orderList" :key="item.orderId" :value="item.orderId" :label="item.orderCode" />
</el-select>
</el-form-item>
<el-form-item label="人员名字" prop="personName">
<el-input v-model="form.personName" placeholder="请输入人员名字" />
</el-form-item>
<el-form-item label="工作内容">
<el-input v-model="form.workContent" placeholder="请输入工作内容" />
</el-form-item>
<el-form-item label="扣款金额" prop="deductionAmount">
<el-input v-model="form.deductionAmount" placeholder="请输入扣款金额" />
</el-form-item>
<el-form-item label="扣款原因" prop="deductionReason">
<el-input v-model="form.deductionReason" placeholder="请输入扣款原因" />
</el-form-item>
<el-form-item label="工作日期" prop="workDate">
<el-date-picker clearable
v-model="form.workDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择工作日期">
</el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="WorkDeduction">
import { listWorkDeduction, getWorkDeduction, delWorkDeduction, addWorkDeduction, updateWorkDeduction } from "@/api/oms/workDeduction";
import { listOrder } from "@/api/oms/order";
const { proxy } = getCurrentInstance();
const workDeductionList = ref([]);
const open = ref(false);
const buttonLoading = 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 formatterTime = (time) => {
return proxy.parseTime(time, '{y}-{m}-{d}')
}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
orderId: undefined,
personName: undefined,
workContent: undefined,
deductionAmount: undefined,
deductionReason: undefined,
workDate: undefined,
status: undefined,
},
rules: {
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询订单下人员工作及扣款信息列表 */
function getList() {
loading.value = true;
listWorkDeduction(queryParams.value).then(response => {
workDeductionList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
const orderList = ref([]);
function remoteOrderList(value) {
listOrder({
orderCode: value,
pageNum: 1,
pageSize: 1000,
}).then(response => {
orderList.value = response.rows;
});
}
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
function reset() {
form.value = {
deductionId: null,
orderId: null,
personName: null,
workContent: null,
deductionAmount: null,
deductionReason: null,
workDate: null,
status: null,
remark: null,
createTime: null,
createBy: null,
updateTime: null,
updateBy: null,
delFlag: null
};
proxy.resetForm("workDeductionRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.deductionId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加订单下人员工作及扣款信息";
}
/** 修改按钮操作 */
function handleUpdate(row) {
loading.value = true
reset();
const _deductionId = row.deductionId || ids.value
getWorkDeduction(_deductionId).then(response => {
loading.value = false;
form.value = response.data;
open.value = true;
title.value = "修改订单下人员工作及扣款信息";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["workDeductionRef"].validate(valid => {
if (valid) {
buttonLoading.value = true;
if (form.value.deductionId != null) {
updateWorkDeduction(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
}).finally(() => {
buttonLoading.value = false;
});
} else {
addWorkDeduction(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
}).finally(() => {
buttonLoading.value = false;
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _deductionIds = row.deductionId || ids.value;
proxy.$modal.confirm('是否确认删除订单下人员工作及扣款信息编号为"' + _deductionIds + '"的数据项?').then(function() {
loading.value = true;
return delWorkDeduction(_deductionIds);
}).then(() => {
loading.value = true;
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {
}).finally(() => {
loading.value = false;
});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('oa/workDeduction/export', {
...queryParams.value
}, `workDeduction_${new Date().getTime()}.xlsx`)
}
getList();
remoteOrderList()
</script>

View File

@@ -68,8 +68,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -10,6 +10,9 @@
<el-form-item label="负责人" prop="salesManager">
<el-input v-model="queryParams.owner" :multiple="false" placeholder="请填写负责人" />
</el-form-item>
<el-form-item label="展示BOM">
<el-checkbox v-model="showDetail" @change="handleQuery" />
</el-form-item>
<!-- <el-form-item label="是否启用" prop="isEnabled">
<el-select v-model="queryParams.isEnabled" placeholder="请选择是否启用" clearable>
<el-option
@@ -57,7 +60,7 @@
</el-table-column>
<el-table-column label="BOM" align="center">
<template #default="scope">
<BomInfoMini :bomId="scope.row.bomId" />
<BomInfoMini :bomId="scope.row.bomId" :detail="showDetail" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -71,8 +74,13 @@
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<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="800px" append-to-body>
@@ -190,6 +198,8 @@ export default {
itemId: undefined,
installManualDialogVisible: false,
showDetail: false,
};
},
created() {
@@ -217,7 +227,9 @@ export default {
},
/** 查询产品列表 */
getList() {
console.log('getList');
this.loading = true;
console.log(this.queryParams);
listProduct(this.queryParams).then(response => {
this.productList = response.rows;
this.total = response.total;

View File

@@ -18,14 +18,15 @@
/>
</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-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form> -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="small"
type="primary"
plain
icon="Plus"
@@ -34,6 +35,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="success"
plain
icon="Edit"
@@ -43,6 +45,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="danger"
plain
icon="Delete"
@@ -52,6 +55,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="warning"
plain
icon="Download"

View File

@@ -18,14 +18,15 @@
/>
</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-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form> -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="small"
type="primary"
plain
icon="Plus"
@@ -34,6 +35,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="success"
plain
icon="Edit"
@@ -43,6 +45,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="danger"
plain
icon="Delete"
@@ -52,6 +55,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="warning"
plain
icon="Download"

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form :model="queryParams" ref="queryRef" :inline="true" size="small" v-show="showSearch" label-width="60px">
<el-form-item label="采购编号" prop="detailCode">
<el-input
v-model="queryParams.detailCode"
@@ -44,6 +44,7 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="small"
type="primary"
plain
icon="Plus"
@@ -52,6 +53,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="success"
plain
icon="Edit"
@@ -61,6 +63,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="danger"
plain
icon="Delete"
@@ -70,6 +73,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="warning"
plain
icon="Download"

View File

@@ -19,6 +19,7 @@
</el-form-item>
<el-form-item label="供货商类型" prop="typeId">
<el-select
size="small"
style="width: 180px;"
v-model="queryParams.typeId"
placeholder="请选择供货商类型"
@@ -32,14 +33,15 @@
</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-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
size="small"
type="primary"
plain
icon="Plus"
@@ -48,6 +50,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="success"
plain
icon="Edit"
@@ -57,6 +60,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="danger"
plain
icon="Delete"
@@ -66,6 +70,7 @@
</el-col>
<el-col :span="1.5">
<el-button
size="small"
type="warning"
plain
icon="Download"

View File

@@ -70,7 +70,7 @@
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
<span>Copyright © 2018-2025 All Rights Reserved.</span>
</div>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true" size="small" v-show="showSearch">
<el-form-item label="部门名称" prop="deptName">
<el-input
v-model="queryParams.deptName"

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true" size="small" v-show="showSearch">
<el-form-item label="岗位编码" prop="postCode">
<el-input
v-model="queryParams.postCode"

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" size="small" :inline="true" label-width="68px">
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="queryParams.roleName"
@@ -435,7 +435,7 @@ function handleUpdate(row) {
open.value = true
nextTick(() => {
roleMenu.then((res) => {
let checkedKeys = res.checkedKeys
let checkedKeys = res.data.checkedKeys
checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value.setChecked(v, true, false)
@@ -450,7 +450,7 @@ function handleUpdate(row) {
/** 根据角色ID查询菜单树结构 */
function getRoleMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
menuOptions.value = response.menus
menuOptions.value = response.data.menus
return response
})
}
@@ -458,7 +458,7 @@ function getRoleMenuTreeselect(roleId) {
/** 根据角色ID查询部门树结构 */
function getDeptTree(roleId) {
return deptTreeSelect(roleId).then(response => {
deptOptions.value = response.depts
deptOptions.value = response.data.depts
return response
})
}
@@ -553,7 +553,7 @@ function handleDataScope(row) {
deptTreeSelect.then(res => {
nextTick(() => {
if (deptRef.value) {
deptRef.value.setCheckedKeys(res.checkedKeys)
deptRef.value.setCheckedKeys(res.data.checkedKeys)
}
})
})

View File

@@ -77,8 +77,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>

View File

@@ -64,7 +64,7 @@
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<!-- 添加或修改库存对话框保持不变 -->

View File

@@ -149,8 +149,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
@@ -226,14 +226,12 @@
<script>
import { listStockIo, getStockIo, delStockIo, addStockIo, updateStockIo } from "@/api/wms/stockIo";
import { listStockIoDetail } from "@/api/wms/stockIoDetail";
import StockIoDetailPanel from './panels/detail.vue';
import BarcodeGenerator from './panels/barcode.vue';
export default {
name: "StockIo",
dicts: ['stock_biz_type', 'stock_io_type', 'stock_status'],
components: { StockIoDetailPanel, BarcodeGenerator },
components: { StockIoDetailPanel },
data() {
return {
// 按钮loading

View File

@@ -1,424 +0,0 @@
<template>
<div class="barcode-3col-layout">
<!-- 预览区 -->
<div class="barcode-preview-col">
<div class="iframe-wrapper">
<iframe ref="previewIframe" class="barcode-iframe" frameborder="0" :style="iframeStyle"></iframe>
</div>
</div>
<!-- 右侧控制+设置区 -->
<div class="barcode-right-col">
<!-- 控制区 -->
<div class="barcode-control-bar">
<el-tabs v-model="activeTab" stretch>
<el-tab-pane label="排版设置" name="layout" />
<el-tab-pane label="二维码明细" name="detail" />
</el-tabs>
</div>
<!-- 设置区 -->
<div class="barcode-settings-panel">
<el-form v-if="activeTab==='layout'" label-width="80px" size="small" label-position="top">
<!-- 排版设置内容保持不变 -->
<el-form-item label="每行数量">
<el-input-number :controls=false controls-position="right" v-model="perRow" size="mini" :min="1" :max="10" />
</el-form-item>
<el-form-item label="二维码尺寸">
<el-input-number :controls=false controls-position="right" v-model="barcodeWidth" size="mini" :min="60" :max="600" />
</el-form-item>
<el-form-item label="纸张尺寸">
<el-select v-model="paperSize" placeholder="请选择纸张尺寸" style="width: 160px">
<el-option label="A4 (210mm x 297mm)" value="A4" />
<el-option label="A5 (148mm x 210mm)" value="A5" />
<el-option label="A6 (105mm x 148mm)" value="A6" />
<el-option label="自定义" value="custom" />
</el-select>
</el-form-item>
<el-form-item v-if="paperSize==='custom'" label="自定义宽度(mm)">
<el-input-number :controls=false controls-position="right" v-model="customPaperWidth" size="mini" :min="50" :max="500" />
</el-form-item>
<el-form-item v-if="paperSize==='custom'" label="自定义高度(mm)">
<el-input-number :controls=false controls-position="right" v-model="customPaperHeight" size="mini" :min="50" :max="500" />
</el-form-item>
<el-form-item label="方向">
<el-radio-group v-model="paperOrientation">
<el-radio label="portrait">纵向</el-radio>
<el-radio label="landscape">横向</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handlePrint">打印</el-button>
</el-form-item>
</el-form>
<el-form v-else label-width="80px" size="small" label-position="top">
<el-form-item label="二维码明细">
<div v-for="(cfg, idx) in barcodeConfigs" :key="idx" style="margin-bottom: 16px; border: 1px solid #eee; border-radius: 4px; padding: 12px 16px; background: #fafbfc;">
<div style="margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: bold; color: #666;">条码 {{ idx + 1 }}</span>
<el-button
type="text"
size="mini"
@click="saveAsImage(cfg.code, cfg.textTpl || cfg.code, idx)"
icon="Download"
>
另存为图片
</el-button>
<el-button
type="text"
size="mini"
@click="handleDelete(cfg, idx)"
icon="Delete"
>
删除
</el-button>
</div>
<el-form-item label="二维码内容" label-width="70px" style="margin-bottom: 8px;">
<el-input disabled type="textarea" v-model="cfg.code" size="mini" :autosize="{ minRows: 1, maxRows: 3 }" placeholder="请输入条码内容" />
</el-form-item>
<el-form-item label="生成数量" label-width="70px" style="margin-bottom: 8px;">
<el-input-number :controls=false controls-position="right" v-model.number="cfg.count" :min="1" :max="100" size="mini" />
</el-form-item>
<el-form-item label="下方文字" label-width="70px" style="margin-bottom: 0;">
<el-input type="textarea" v-model="cfg.textTpl" size="mini" placeholder="如 箱号" />
</el-form-item>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
import QRCode from 'qrcode';
export default {
name: 'BarcodeGenerator',
props: {
barcodes: {
type: Array,
required: true
}
},
data() {
return {
perRow: 3,
barcodeWidth: 180,
barcodeHeight: 180, // 二维码建议宽高一致
barcodeConfigs: [], // [{code, count, textTpl}]
previewScale: 1, // 预览缩放比例
activeTab: 'layout',
paperSize: 'A4',
paperOrientation: 'portrait',
customPaperWidth: 210,
customPaperHeight: 297
};
},
computed: {
expandedBarcodes() {
let arr = [];
this.barcodeConfigs.forEach(cfg => {
for (let i = 0; i < (cfg.count || 1); i++) {
arr.push(cfg.code);
}
});
return arr;
},
expandedBarcodeTexts() {
let arr = [];
this.barcodeConfigs.forEach(cfg => {
for (let i = 0; i < (cfg.count || 1); i++) {
// 模板替换 {{n}}
let text = cfg.textTpl || cfg.code;
text = text.replace(/\{\{n\}\}/g, i + 1);
arr.push(text);
}
});
return arr;
},
expandedBarcodeTableData() {
return this.expandedBarcodes.map((code, idx) => ({ code, text: this.expandedBarcodeTexts[idx] }));
},
barcodeRows() {
const rows = [];
const arr = this.expandedBarcodes;
for (let i = 0; i < arr.length; i += this.perRow) {
rows.push(arr.slice(i, i + this.perRow));
}
return rows;
},
barcodeTextRows() {
const rows = [];
const arr = this.expandedBarcodeTexts;
for (let i = 0; i < arr.length; i += this.perRow) {
rows.push(arr.slice(i, i + this.perRow));
}
return rows;
},
iframeStyle() {
const { width, height } = this.getPaperPx();
return {
width: width + 'px',
minHeight: height + 'px',
height: height + 'px',
background: '#fff',
border: 'none',
display: 'block',
margin: 0,
padding: 0
};
}
},
watch: {
barcodes: {
handler(newVal) {
// 初始化barcodeConfigs
this.barcodeConfigs = newVal;
this.$nextTick(this.renderPreviewIframe);
},
immediate: true
},
barcodeConfigs: {
handler() { this.$nextTick(this.renderPreviewIframe); },
deep: true
},
perRow() { this.$nextTick(this.renderPreviewIframe); },
barcodeWidth() { this.$nextTick(this.renderPreviewIframe); },
barcodeHeight() { this.$nextTick(this.renderPreviewIframe); },
previewScale() { this.$nextTick(this.renderPreviewIframe); },
paperSize() { this.$nextTick(this.renderPreviewIframe); },
paperOrientation() { this.$nextTick(this.renderPreviewIframe); },
customPaperWidth() { this.$nextTick(this.renderPreviewIframe); },
customPaperHeight() { this.$nextTick(this.renderPreviewIframe); }
},
methods: {
getBarcodeId(row, col) {
return `barcode-canvas-${row}-${col}`;
},
getBarcodeText(row, col, code) {
if (
this.barcodeTextRows[row] &&
typeof this.barcodeTextRows[row][col] !== 'undefined' &&
this.barcodeTextRows[row][col] !== null &&
this.barcodeTextRows[row][col] !== ''
) {
return this.barcodeTextRows[row][col];
}
return code;
},
handleDelete(cfg, idx) {
// this.barcodeConfigs.splice(idx, 1);
this.$emit('delete', cfg, idx);
},
getPaperPx() {
// mm to px, 1mm ≈ 3.78px
let width, height;
if (this.paperSize === 'A4') {
width = 210 * 3.78;
height = 297 * 3.78;
} else if (this.paperSize === 'A5') {
width = 148 * 3.78;
height = 210 * 3.78;
} else if (this.paperSize === 'A6') {
width = 105 * 3.78;
height = 148 * 3.78;
} else {
width = this.customPaperWidth * 3.78;
height = this.customPaperHeight * 3.78;
}
if (this.paperOrientation === 'landscape') {
return { width: height, height: width };
}
return { width, height };
},
getPrintHtml() {
const { width, height } = this.getPaperPx();
let html = `
<html>
<head>
<title>打印二维码</title>
<style>
body { margin: 0; padding: 0; background: #fff; }
.barcode-list { width: ${width}px; min-height: ${height}px; margin: 0 auto; background: #fff; }
.barcode-row { display: flex; margin-bottom: 24px; }
.barcode-item { flex: 1; text-align: center; }
.barcode-text { margin-top: 8px; font-size: 14px; }
html, body { overflow-x: hidden; }
</style>
</head>
<body>
<div class="barcode-list">
`;
this.barcodeRows.forEach((row, rowIndex) => {
html += '<div class="barcode-row">';
row.forEach((code, colIndex) => {
const id = this.getBarcodeId(rowIndex, colIndex);
const text = this.getBarcodeText(rowIndex, colIndex, code);
html += `<div class="barcode-item">
<canvas id="${id}" width="${this.barcodeWidth}" height="${this.barcodeHeight}"></canvas>
<div class="barcode-text">${text}</div>
</div>`;
});
html += '</div>';
});
html += '</div></body></html>';
return html;
},
async renderPreviewIframe() {
const iframe = this.$refs.previewIframe;
if (!iframe) return;
const doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write(this.getPrintHtml());
doc.close();
// 渲染二维码
setTimeout(async () => {
for (let rowIndex = 0; rowIndex < this.barcodeRows.length; rowIndex++) {
const row = this.barcodeRows[rowIndex];
for (let colIndex = 0; colIndex < row.length; colIndex++) {
const code = row[colIndex];
const id = this.getBarcodeId(rowIndex, colIndex);
const el = doc.getElementById(id);
if (el) {
await QRCode.toCanvas(el, code, {
width: this.barcodeWidth,
height: this.barcodeHeight,
margin: 0
});
}
}
}
}, 50);
},
handlePrint() {
const iframe = this.$refs.previewIframe;
if (!iframe) return;
iframe.contentWindow.focus();
iframe.contentWindow.print();
},
/**
* 保存二维码为图片
* @param {string} code 二维码内容
* @param {string} text 下方文字
* @param {number} index 索引,用于生成文件名
*/
async saveAsImage(code, text, index) {
try {
// 创建临时canvas用于绘制二维码和文字
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算总高度(二维码高度 + 文字区域高度)
const textHeight = 30; // 文字区域高度
canvas.width = this.barcodeWidth;
canvas.height = this.barcodeHeight + textHeight;
// 绘制二维码
const qrCanvas = document.createElement('canvas');
qrCanvas.width = this.barcodeWidth;
qrCanvas.height = this.barcodeHeight;
await QRCode.toCanvas(qrCanvas, code, {
width: this.barcodeWidth,
height: this.barcodeHeight,
margin: 0
});
// 将二维码绘制到主canvas
ctx.drawImage(qrCanvas, 0, 0);
// 绘制文字
ctx.fillStyle = '#000';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(text, this.barcodeWidth / 2, this.barcodeHeight + 5);
// 创建图片链接并下载
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
// 生成文件名(使用索引和内容摘要)
const fileName = `二维码_${index + 1}_${code.substring(0, 10)}.png`;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
this.$message.success('图片保存成功');
} catch (error) {
console.error('保存图片失败:', error);
this.$message.error('保存图片失败,请重试');
}
}
},
mounted() {
this.barcodeConfigs = this.barcodes?.map(b => ({ code: b, count: 1, textTpl: b })) || [];
this.renderPreviewIframe();
}
};
</script>
<style scoped>
/* 样式保持不变 */
.barcode-3col-layout {
display: flex;
flex-direction: row;
width: 100%;
min-height: 400px;
}
.barcode-preview-col {
flex: 1 1 0;
min-width: 0;
background: #fff;
border-right: 1px solid #eee;
padding: 24px 0 24px 24px;
display: flex;
flex-direction: column;
height: 90vh;
overflow: auto;
}
.barcode-right-col {
width: 420px;
min-width: 320px;
padding: 16px;
display: flex;
flex-direction: column;
background: #fafbfc;
height: 90vh;
}
.barcode-control-bar {
border-bottom: 1px solid #eee;
background: #fafbfc;
padding: 0 16px;
}
.barcode-settings-panel {
flex: 1;
overflow: auto;
min-height: 0;
max-height: calc(90vh - 48px); /* 48px 约为tabs高度可根据实际调整 */
}
.preview-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
}
.iframe-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
background: #f8f8f8;
overflow: auto;
}
.barcode-iframe {
/* width、height 由:style绑定动态控制 */
min-width: 0;
min-height: 0;
border: none;
background: #fff;
border-radius: 4px;
display: block;
}
</style>

View File

@@ -156,8 +156,8 @@
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 操作按钮 -->

View File

@@ -59,7 +59,7 @@
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<!-- 操作按钮 -->
<div style="margin-top: 20px; text-align: right;">

View File

@@ -81,7 +81,7 @@
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<!-- 添加或修改出入库单主对话框 -->

View File

@@ -11,7 +11,7 @@ export default defineConfig(({ mode, command }) => {
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下vite 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
// 例如 https://www./。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www./admin/,则设置 baseUrl 为 /admin/。
base: VITE_APP_ENV === 'production' ? '/' : '/',
plugins: createVitePlugins(env, command === 'build'),
resolve: {

View File

@@ -6,7 +6,7 @@ export default function createCompression(env) {
if (VITE_BUILD_COMPRESS) {
const compressList = VITE_BUILD_COMPRESS.split(',')
if (compressList.includes('gzip')) {
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
// http://doc./ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
plugin.push(
compression({
ext: '.gz',