增加刀具寿命界面

This commit is contained in:
zhangshun
2026-06-11 17:27:45 +08:00
parent 42dab65fed
commit 2ac9e1f66e
25 changed files with 3395 additions and 590 deletions

View File

@@ -73,7 +73,10 @@ const webpackConfig = merge(baseWebpackConfig, {
}
}
}),
new CssMinimizerPlugin()
new CssMinimizerPlugin({
// TinyMCE 4 静态资源目录下的样式不参与二次压缩,避免老资源和当前压缩器的兼容问题
exclude: /static[\\/]tinymce4\.7\.5[\\/].*\.css$/i
})
],
splitChunks: {
chunks: 'all',

View File

@@ -0,0 +1,45 @@
IF OBJECT_ID(N'dbo.设备管理_刀具寿命_明细_查询', N'P') IS NOT NULL
DROP PROCEDURE dbo._刀具寿命_明细_查询
GO
CREATE PROCEDURE dbo._刀具寿命_明细_查询
@ NVARCHAR(100) = N''
AS
BEGIN
SET NOCOUNT ON
SELECT
LTRIM(RTRIM()) AS ,
LTRIM(RTRIM()) AS ,
ISNULL(, '') AS ,
ISNULL(, '') AS ,
ISNULL(, '') AS ,
ISNULL(, '') AS ,
ISNULL(, '') AS ,
CAST(ISNULL(寿, 0) AS DECIMAL(18, 2)) AS 寿,
CAST(ISNULL(寿, 0) AS DECIMAL(18, 2)) AS 寿,
CAST(ISNULL(1, 0) AS DECIMAL(18, 2)) AS 1,
CAST(ISNULL(寿, 0) - ISNULL(寿, 0) AS DECIMAL(18, 2)) AS 寿,
CASE
WHEN ISNULL(寿, 0) > 0 AND ISNULL(寿, 0) >= ISNULL(寿, 0) THEN N'报废'
WHEN ISNULL(1, 0) > 0 AND ISNULL(寿, 0) >= ISNULL(1, 0) THEN N'预警'
ELSE N'正常'
END AS ,
,
,
,
FROM dbo._刀具设备台账
WHERE ISNULL(LTRIM(RTRIM(@)), '') = ''
OR LTRIM(RTRIM()) = LTRIM(RTRIM(@))
ORDER BY
LTRIM(RTRIM()),
CASE
WHEN ISNULL(寿, 0) > 0 AND ISNULL(寿, 0) >= ISNULL(寿, 0) THEN 3
WHEN ISNULL(1, 0) > 0 AND ISNULL(寿, 0) >= ISNULL(1, 0) THEN 2
ELSE 1
END DESC,
ISNULL(, ''),
ISNULL(, '')
END
GO

View File

@@ -0,0 +1,23 @@
IF OBJECT_ID(N'dbo.设备管理_刀具寿命_机床列表_查询', N'P') IS NOT NULL
DROP PROCEDURE dbo._刀具寿命_机床列表_查询
GO
CREATE PROCEDURE dbo._刀具寿命_机床列表_查询
AS
BEGIN
SET NOCOUNT ON
/*
当前表“设备管理_刀具设备台账”中
1. 设备编号为空
2. 所在工位号有实际值,可作为设备/机床维度使用
*/
SELECT DISTINCT
LTRIM(RTRIM()) AS ,
LTRIM(RTRIM()) AS
FROM dbo._刀具设备台账
WHERE ISNULL(LTRIM(RTRIM()), '') <> ''
ORDER BY LTRIM(RTRIM())
END
GO

View File

@@ -0,0 +1,46 @@
CREATE OR ALTER PROCEDURE [dbo].[_测量数据发动机在线状态_工位JPH_历史]
@ nvarchar(50) = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(max) = null,
@_check bit = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(50) = null
AS
BEGIN
SET NOCOUNT ON
DECLARE @startTime datetime = null
DECLARE @endTime datetime = null
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @startTime = CONVERT(datetime, @ + N' 00:00:00', 120)
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @endTime = CONVERT(datetime, @ + N' 23:59:59', 120)
;WITH StationFilter AS (
SELECT LTRIM(RTRIM(value)) AS
FROM STRING_SPLIT(ISNULL(@, N''), N',')
WHERE LTRIM(RTRIM(value)) <> N''
)
SELECT
= CONVERT(nvarchar(10), CONVERT(datetime, [], 120), 23),
= RIGHT('0' + CAST(DATEPART(HOUR, CONVERT(datetime, [], 120)) AS nvarchar(2)), 2) + N':00',
= COUNT(1)
FROM [dbo].[线] m
WHERE m.[ID] <> 0
AND m.[] IS NOT NULL
AND (@startTime IS NULL OR CONVERT(datetime, m.[], 120) >= @startTime)
AND (@endTime IS NULL OR CONVERT(datetime, m.[], 120) <= @endTime)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR EXISTS (
SELECT 1 FROM StationFilter f WHERE f. = m.[]
))
GROUP BY
CONVERT(nvarchar(10), CONVERT(datetime, m.[], 120), 23),
DATEPART(HOUR, CONVERT(datetime, m.[], 120))
ORDER BY ,
END

View File

@@ -0,0 +1,23 @@
CREATE OR ALTER PROCEDURE [dbo].[_测量数据发动机在线状态_工位JPH_历史_导出]
@ nvarchar(50) = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(max) = null,
@_check bit = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(50) = null
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[_测量数据发动机在线状态_工位JPH_历史]
@ = @,
@ = @,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @
END

View File

@@ -0,0 +1,25 @@
CREATE OR ALTER PROCEDURE [dbo].[_测量数据发动机在线状态_工位JPH_实时]
@ nvarchar(50) = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(max) = null,
@_check bit = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(50) = null
AS
BEGIN
SET NOCOUNT ON
DECLARE @today nvarchar(10) = CONVERT(nvarchar(10), GETDATE(), 23)
EXEC [dbo].[_测量数据发动机在线状态_工位JPH_历史]
@ = @today,
@ = @today,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @
END

View File

@@ -0,0 +1,71 @@
CREATE OR ALTER PROCEDURE [dbo].[_测量数据发动机在线状态_工位节拍统计]
@ nvarchar(50) = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(max) = null,
@_check bit = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(50) = null
AS
BEGIN
SET NOCOUNT ON
DECLARE @startTime datetime = null
DECLARE @endTime datetime = null
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @startTime = CONVERT(datetime, @ + N' 00:00:00', 120)
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @endTime = CONVERT(datetime, @ + N' 23:59:59', 120)
;WITH StationFilter AS (
SELECT LTRIM(RTRIM(value)) AS
FROM STRING_SPLIT(ISNULL(@, N''), N',')
WHERE LTRIM(RTRIM(value)) <> N''
),
SourceData AS (
SELECT
s.[] AS ,
CONVERT(nvarchar(10), CONVERT(datetime, m.[], 120), 23) AS ,
CAST(m.[线] AS decimal(18, 2)) AS 线
FROM [dbo].[线] m
INNER JOIN [dbo].[MES_计划BOM_工位与名称] s
ON m.[] = s.[]
WHERE m.[ID] <> 0
AND m.[] IS NOT NULL
AND m.[线] IS NOT NULL
AND ISNUMERIC(CONVERT(nvarchar(50), m.[线])) = 1
AND (@startTime IS NULL OR CONVERT(datetime, m.[], 120) >= @startTime)
AND (@endTime IS NULL OR CONVERT(datetime, m.[], 120) <= @endTime)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR EXISTS (
SELECT 1 FROM StationFilter f WHERE f. = m.[]
))
),
DailyAgg AS (
SELECT
,
,
CAST(AVG(线) AS decimal(18, 2)) AS
FROM SourceData
GROUP BY ,
),
TotalAgg AS (
SELECT
,
CAST(AVG(线) AS decimal(18, 2)) AS
FROM SourceData
GROUP BY
)
SELECT
d.,
t.,
d.,
d.
FROM DailyAgg d
INNER JOIN TotalAgg t ON d. = t.
ORDER BY d., d.
END

View File

@@ -0,0 +1,140 @@
CREATE OR ALTER PROCEDURE [dbo].[_测量数据发动机在线状态_工位节拍统计_导出]
@ nvarchar(50) = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(max) = null,
@_check bit = null,
@ nvarchar(50) = null,
@_check bit = null,
@ nvarchar(50) = null
AS
BEGIN
SET NOCOUNT ON
DECLARE @startTime datetime = null
DECLARE @endTime datetime = null
DECLARE @cols nvarchar(max) = N''
DECLARE @sql nvarchar(max)
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @startTime = CONVERT(datetime, @ + N' 00:00:00', 120)
IF @ IS NOT NULL AND LTRIM(RTRIM(@)) <> N''
SET @endTime = CONVERT(datetime, @ + N' 23:59:59', 120)
;WITH StationFilter AS (
SELECT LTRIM(RTRIM(value)) AS
FROM STRING_SPLIT(ISNULL(@, N''), N',')
WHERE LTRIM(RTRIM(value)) <> N''
),
SourceData AS (
SELECT
m.[] AS ,
CONVERT(nvarchar(10), CONVERT(datetime, m.[], 120), 23) AS ,
CAST(m.[线] AS decimal(18, 2)) AS 线
FROM [dbo].[线] m
INNER JOIN [dbo].[MES_计划BOM_工位与名称] s
ON m.[] = s.[]
WHERE m.[ID] <> 0
AND m.[] IS NOT NULL
AND m.[线] IS NOT NULL
AND ISNUMERIC(CONVERT(nvarchar(50), m.[线])) = 1
AND (@startTime IS NULL OR CONVERT(datetime, m.[], 120) >= @startTime)
AND (@endTime IS NULL OR CONVERT(datetime, m.[], 120) <= @endTime)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR m.[] = @)
AND (ISNULL(@_check, 0) = 0 OR EXISTS (
SELECT 1 FROM StationFilter f WHERE f. = m.[]
))
),
DailyAgg AS (
SELECT
,
,
CAST(AVG(线) AS decimal(18, 2)) AS
FROM SourceData
GROUP BY ,
)
SELECT @cols = STUFF((
SELECT N',' + QUOTENAME()
FROM (SELECT DISTINCT FROM DailyAgg) d
ORDER BY
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, N'')
IF ISNULL(@cols, N'') = N''
BEGIN
SELECT CAST(N'' AS nvarchar(50)) AS , CAST(0 AS decimal(18, 2)) AS
WHERE 1 = 0
RETURN
END
SET @sql = N'
;WITH StationFilter AS (
SELECT LTRIM(RTRIM(value)) AS 工位号
FROM STRING_SPLIT(ISNULL(@工位列表, N''''), N'','')
WHERE LTRIM(RTRIM(value)) <> N''''
),
SourceData AS (
SELECT
m.[工位号] AS 工位,
CONVERT(nvarchar(10), CONVERT(datetime, m.[离开时间], 120), 23) AS 日期,
CAST(m.[在线时间] AS decimal(18, 2)) AS 在线时间
FROM [dbo].[测量数据发动机在线状态] m
INNER JOIN [dbo].[MES_计划BOM_工位与名称] s
ON m.[工位号] = s.[工位号]
WHERE m.[ID] <> 0
AND m.[离开时间] IS NOT NULL
AND m.[在线时间] IS NOT NULL
AND ISNUMERIC(CONVERT(nvarchar(50), m.[在线时间])) = 1
AND (@startTime IS NULL OR CONVERT(datetime, m.[离开时间], 120) >= @startTime)
AND (@endTime IS NULL OR CONVERT(datetime, m.[离开时间], 120) <= @endTime)
AND (ISNULL(@订单号_check, 0) = 0 OR m.[订单号] = @订单号)
AND (ISNULL(@产品型号代码_check, 0) = 0 OR m.[机型代码] = @产品型号代码)
AND (ISNULL(@工位_check, 0) = 0 OR EXISTS (
SELECT 1 FROM StationFilter f WHERE f.工位号 = m.[工位号]
))
),
DailyAgg AS (
SELECT
工位,
日期,
CAST(AVG(在线时间) AS decimal(18, 2)) AS 当日平均节拍
FROM SourceData
GROUP BY 工位, 日期
),
TotalAgg AS (
SELECT
工位,
CAST(AVG(在线时间) AS decimal(18, 2)) AS 总平均节拍
FROM SourceData
GROUP BY 工位
),
PivotData AS (
SELECT 工位, 日期, 当日平均节拍
FROM DailyAgg
)
SELECT 工位, 总平均节拍, ' + @cols + N'
FROM (
SELECT p.工位, t.总平均节拍, p.日期, p.当日平均节拍
FROM PivotData p
INNER JOIN TotalAgg t ON p.工位 = t.工位
) src
PIVOT (
MAX(当日平均节拍)
FOR 日期 IN (' + @cols + N')
) p
ORDER BY 工位'
EXEC sp_executesql
@sql,
N'@startTime datetime, @endTime datetime, @工位_check bit, @工位列表 nvarchar(max), @订单号_check bit, @订单号 nvarchar(50), @产品型号代码_check bit, @产品型号代码 nvarchar(50)',
@startTime = @startTime,
@endTime = @endTime,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @,
@_check = @_check,
@ = @
END

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1561220647782" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3503" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M976.068378 432.323827l-103.740035 0c-18.395807 0-38.215939-14.281091-44.030211-31.73939l-14.146916-33.581723c-8.427254-16.359092-4.682381-40.38511 8.332643-53.396694l73.825776-73.825776c13.011584-13.013305 13.011584-34.305927 0-47.319231l-66.236259-66.234538c-13.013305-13.013305-34.304206-13.013305-47.314071 0l-74.362478 74.357318c-13.009864 13.011584-37.097809 16.885472-53.53431 8.609595l-32.826556-13.507002c-17.506464-5.676656-31.8254-25.374654-31.8254-43.775622L590.210563 46.469453c0-18.395807-15.05174-33.450988-33.452708-33.450988l-93.676871 0c-18.397527 0-33.454428 15.055181-33.454428 33.450988l0 105.443032c0 18.395807-14.318936 38.098965-31.821959 43.775622l-32.829996 13.507002c-16.42962 8.275877-40.522725 4.401989-53.53603-8.609595l-74.357318-74.357318c-13.013305-13.013305-34.304206-13.013305-47.31235 0l-66.241419 66.234538c-13.011584 13.013305-13.011584 34.305927 0 47.319231l73.830937 73.825776c13.011584 13.011584 16.761618 37.037602 8.327483 53.396694l-14.146916 33.581723c-5.814272 17.458299-25.627523 31.73939-44.028491 31.73939L43.773901 432.325548C25.378094 432.323827 10.321193 447.380728 10.321193 465.779976l0 93.673431c0 18.400968 15.056901 33.452708 33.452708 33.452708l102.680392 0c18.400968 0 38.026717 14.348179 43.613923 31.878726l14.248407 34.978524c8.250074 16.450262 4.350383 40.550249-8.657761 53.563553l-72.12794 72.12794c-13.011584 13.011584-13.011584 34.299046 0 47.314071l66.236259 66.241419c13.011584 13.009864 34.304206 13.009864 47.31235 0l71.601559-71.601559c13.011584-13.011584 37.053084-16.783981 53.424217-8.382529l35.726811 14.898643c17.492703 5.721382 31.797877 25.457223 31.797877 43.861632l0 100.979116c0 18.397527 15.055181 33.452708 33.457869 33.452708l93.673431 0c18.400968 0 33.456148-15.055181 33.456148-33.452708l0-100.979116c0-18.404408 14.305174-38.14025 31.797877-43.861632l26.014568-10.852735-50.484397-50.486117c-27.85002 9.437011-57.354872 14.368821-87.620051 14.368821-72.683564 0-141.016745-28.309313-192.411128-79.700255-51.390942-51.396103-79.693375-119.722403-79.693375-192.405967 0-72.681844 28.302432-141.015025 79.693375-192.411128 51.394382-51.390942 119.727563-79.693375 192.411128-79.693375 72.681844 0 141.009864 28.304153 192.404247 79.693375 51.390942 51.396103 79.700255 119.729284 79.700255 192.411128 0 29.587421-4.694423 58.454079-13.725467 85.753635l50.68222 50.67878 10.787367-26.494503c5.590646-17.530547 25.216396-31.878726 43.617363-31.878726l102.680392 0c18.397527 0 33.452708-15.05174 33.452708-33.452708l0-93.673431c0-18.399247-15.055181-33.456148-33.452708-33.456148L976.068378 432.325548zM715.060879 622.278511c-10.691036-10.46569-15.681333-18.935949-9.68472-35.835183 31.517484-88.777745 2.674909-179.905282-74.592985-233.895444-48.748717-34.054778-103.17409-43.877113-163.250316-25.978444 22.457197 26.37409 47.701115 48.34963 71.412337 71.876791 46.52794 46.18734 47.207418 103.322027 2.828007 147.015079-43.010133 42.347856-101.077167 40.951055-145.73353-4.102674-23.43599-23.632092-46.025642-48.096761-69.002338-72.184706-2.760919 1.64623-5.526999 3.29074-8.294799 4.93181-13.271334 72.4737 3.173767 137.263271 57.640425 189.218438 57.981024 55.314716 127.354925 70.941003 203.437602 45.341003 19.436527-6.536756 29.735358-3.087757 42.908641 10.285069 56.718398 57.590539 159.455557 155.187744 217.259401 211.701438 27.942911 27.33396 62.286682 28.221583 89.830507 4.32114 34.204435-29.678592 35.933235-68.924929 3.153125-101.732563C875.840989 776.081494 772.806236 678.812848 715.060879 622.278511L715.060879 622.278511zM881.810079 910.578686c-17.55463 0-31.787555-14.232926-31.787555-31.787555s14.231205-31.787555 31.787555-31.787555 31.782395 14.234646 31.782395 31.787555S899.364709 910.578686 881.810079 910.578686L881.810079 910.578686zM881.810079 910.578686" p-id="3504"></path></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -7,6 +7,7 @@ export default {
cancel: 'Cancel',
home: 'Home',
logout: 'Logout',
online: 'Online',
language: 'Language',
chinese: 'Chinese',
english: 'English'
@@ -30,6 +31,30 @@ export default {
editFailed: 'Edit failed',
accountPasswordError: 'Incorrect username or password'
},
dashboard: {
workbench: 'MES Workbench',
heroDescription: '{date}, the current workbench includes {moduleCount} top-level modules, {entryCount} quick entries.',
focus: 'Focus',
module: 'Module',
pendingTitle: 'Pending Alerts',
sortHint: 'Sorted by count in descending order',
handleHint: 'Open the related business page to process it',
emptyTitle: 'No pending alerts at the moment',
emptyText: 'New business alerts will appear here automatically when available.',
quickAccess: 'Quick Access',
moduleEntryCount: '{count} quick entries',
metricTopModules: 'Top-Level Modules',
metricTopModulesHint: 'Primary navigation groups',
metricQuickEntries: 'Quick Entries',
metricQuickEntriesHint: 'Direct dashboard shortcuts',
metricPendingMessages: 'Pending Messages',
metricPendingMessagesHint: 'Items requiring timely action',
metricNoticeSources: 'Alert Sources',
metricNoticeSourcesHint: 'Active alert categories',
greetingMorning: 'Good morning',
greetingAfternoon: 'Good afternoon',
greetingEvening: 'Good evening'
},
menuManagement: {
filterPlaceholder: 'Enter keyword to filter',
menuName: 'Menu Name',
@@ -310,13 +335,57 @@ export default {
selectOrder: 'Please select an order!',
confirmCompleteOrder: 'Confirm early completion for order {orderNo}?'
},
cylinderMaintenance: {
keywordLabel: 'Keyword:',
keywordPlaceholder: 'Please enter keyword',
productionDateLabel: 'Production Date',
selectProductionDate: 'Please select production date',
productionStatusLabel: 'Production Status',
selectProductionStatus: 'Please select production status',
onlineTimeLabel: 'Online Time',
startDate: 'Start Date',
endDate: 'End Date',
dateRangeSeparator: '~',
search: 'Search',
importData: 'Import',
importTitle: 'Cylinder Group Import',
importHint: 'The first Excel row must match the `缸体分组号` table fields before import.',
confirmImport: 'Confirm Import',
dragExcelFile: 'Drop the Excel file here or',
browse: 'Browse',
sequence: 'No.',
blankNo: 'Blank No.',
materialNo: 'Material No.',
groupNo: 'Group No.',
productionMonth: 'Production Month',
crankcaseNo: 'Crankcase No.',
bindingTime: 'Binding Time',
qrCode: 'QR Code',
serialNo: 'Serial No.',
statusAll: 'All',
statusCompleted: 'Completed',
statusPending: 'Not Produced',
loadingData: 'Loading data',
noData: 'No data',
noImportData: 'No import data',
queryFailed: 'Query failed',
importPreviewFailed: 'Import preview failed',
importSuccess: 'Import succeeded',
importFailed: 'Import failed. Check the file or field mapping.',
uploadOneFileOnly: 'Only one file can be uploaded',
uploadExcelOnly: 'Only .xlsx, .xls, and .csv files are supported'
},
recipeManagement: {
productModelLabel: 'Product Model:',
productModel: 'Product Model',
productModelLabel: 'Cylinder Model:',
productModel: 'Cylinder Model',
search: 'Search',
loadingData: 'Loading data',
productModelCode: 'Pro. Model Code',
largeModel: 'Large Model',
productModelCode: 'Cylinder Code',
largeModel: 'Model Name',
crankcaseCode: 'Crankcase Code',
crankcaseModel: 'Crankcase Model',
crankcaseModelName: 'Crankcase Model Name',
combinePartNumber: 'Combined Part No.',
add: 'Add',
edit: 'Edit',
partType: 'Part Type',
@@ -630,6 +699,7 @@ export default {
'下线统计': 'Offline Count',
'工位节拍': 'Station Beat',
'工位JPH': 'Station JPH',
'缸体分组号': 'Cylinder Group No.',
'生产计划订单完工': 'Order Complete',
'SPC分析': 'SPC Analysis',
'基本趋势图': 'Trend Chart',

View File

@@ -7,6 +7,7 @@ export default {
cancel: '取消',
home: '主页',
logout: '退出',
online: '在线',
language: '语言',
chinese: '中文',
english: 'English'
@@ -30,6 +31,30 @@ export default {
editFailed: '编辑失败',
accountPasswordError: '用户名或密码不正确'
},
dashboard: {
workbench: 'MES 工作台',
heroDescription: '{date},当前工作台已汇总 {moduleCount} 个一级模块、{entryCount} 个快捷入口。',
focus: '重点关注',
module: '模块',
pendingTitle: '待办提醒',
sortHint: '按数量从高到低展示',
handleHint: '进入对应业务页面处理',
emptyTitle: '当前没有待办提醒',
emptyText: '通知列表为空时,这里会自动显示新的业务提醒。',
quickAccess: '快速进入',
moduleEntryCount: '{count} 个快捷入口',
metricTopModules: '一级模块',
metricTopModulesHint: '系统主导航分组',
metricQuickEntries: '快捷入口',
metricQuickEntriesHint: '首页可直达功能',
metricPendingMessages: '待办消息',
metricPendingMessagesHint: '需要及时处理事项',
metricNoticeSources: '提醒来源',
metricNoticeSourcesHint: '当前激活通知类型',
greetingMorning: '早上好',
greetingAfternoon: '下午好',
greetingEvening: '晚上好'
},
menuManagement: {
filterPlaceholder: '输入关键字进行过滤',
menuName: '菜单名称',
@@ -310,13 +335,57 @@ export default {
selectOrder: '请选择订单!',
confirmCompleteOrder: '确定要提前完工订单:{orderNo}吗?'
},
cylinderMaintenance: {
keywordLabel: '关键字:',
keywordPlaceholder: '请输入关键字',
productionDateLabel: '生产日期',
selectProductionDate: '请选择生产日期',
productionStatusLabel: '生产状态',
selectProductionStatus: '请选择生产状态',
onlineTimeLabel: '上线时间',
startDate: '开始日期',
endDate: '结束日期',
dateRangeSeparator: '',
search: '查询',
importData: '导入',
importTitle: '缸体分组号导入',
importHint: 'Excel首行需与缸体分组号表字段一致确认无误后再导入。',
confirmImport: '确认导入',
dragExcelFile: '将Excel文件拖入或',
browse: '浏览',
sequence: '序号',
blankNo: '毛坯号',
materialNo: '物料号',
groupNo: '分组号',
productionMonth: '生产月份',
crankcaseNo: '曲轴箱号',
bindingTime: '绑定时间',
qrCode: '二维码',
serialNo: '流水号',
statusAll: '全部',
statusCompleted: '已完成',
statusPending: '未生产',
loadingData: '数据加载中',
noData: '暂无数据',
noImportData: '暂无可导入数据',
queryFailed: '查询失败',
importPreviewFailed: '导入预览失败',
importSuccess: '导入成功',
importFailed: '导入失败,请检查导入文件或字段映射',
uploadOneFileOnly: '仅支持上传一个文件',
uploadExcelOnly: '仅支持上传 .xlsx、.xls、.csv 格式文件'
},
recipeManagement: {
productModelLabel: '产品型号:',
productModel: '产品型号',
productModelLabel: '缸体型号:',
productModel: '缸体型号',
search: '查询',
loadingData: '数据加载中',
productModelCode: '产品型号代码',
largeModel: '机型',
productModelCode: '缸体代码',
largeModel: '机型名称',
crankcaseCode: '曲轴箱代码',
crankcaseModel: '曲轴箱型号',
crankcaseModelName: '曲轴箱型号名称',
combinePartNumber: '合箱零件号',
add: '增加',
edit: '编辑',
partType: '零件类型',
@@ -630,6 +699,7 @@ export default {
'下线统计': '下线统计',
'工位节拍': '工位节拍',
'工位JPH': '工位JPH',
'缸体分组号': '缸体分组号',
'生产计划订单完工': '生产计划订单完工',
'SPC分析': 'SPC分析',
'基本趋势图': '基本趋势图',

View File

@@ -5,13 +5,13 @@
min-width: 1360px; // div3固定宽度后新增
min-height: 100%;
transition: margin-left .28s;
margin-left: 220px;
margin-left: 236px;
position: relative;
}
// 侧边栏
.sidebar-container {
transition: width 0.28s;
width: 220px !important;
width: 236px !important;
height: 100%;
position: fixed;
font-size: 0px;
@@ -20,17 +20,36 @@
left: 0;
z-index: 1001;
overflow: hidden;
background: linear-gradient(180deg, #f8fbff 0%, #eef5ff 100%);
border-right: 1px solid rgba(108, 146, 210, 0.18);
box-shadow: 18px 0 40px rgba(15, 43, 92, 0.08);
&::before {
content: '';
position: absolute;
top: -18%;
right: -22%;
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(89, 168, 255, 0.22) 0%, rgba(89, 168, 255, 0) 72%);
pointer-events: none;
}
//reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.el-scrollbar__bar.is-vertical{
right: 0px;
right: 4px;
width: 6px;
}
.scrollbar-wrapper {
overflow-x: hidden!important;
.el-scrollbar__view {
height: 100%;
display: flex;
flex-direction: column;
}
}
.is-horizontal {
@@ -42,33 +61,145 @@
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
margin-right: 12px;
font-size: 18px;
color: inherit;
transition: color .2s ease;
}
.el-menu {
border: none;
height: 100%;
background: white!important;
background: transparent !important;
width: 100% !important;
}
.el-scrollbar__thumb {
background-color: rgba(102, 136, 191, 0.36);
border-radius: 999px;
}
.sidebar-menu {
padding: 0 10px 14px;
}
.submenu-title-noDropdown,
.el-submenu > .el-submenu__title,
.el-menu-item {
height: 46px;
line-height: 46px;
margin-bottom: 8px;
padding-left: 18px !important;
border-radius: 14px;
color: #31456a !important;
font-size: 14px;
font-weight: 500;
transition: background-color .22s ease, color .22s ease, box-shadow .22s ease, transform .22s ease;
}
.submenu-title-noDropdown,
.el-submenu > .el-submenu__title {
position: relative;
}
.submenu-title-noDropdown:hover,
.el-submenu > .el-submenu__title:hover,
.el-menu-item:hover {
background: rgba(21, 118, 232, 0.10) !important;
color: #0a67d1 !important;
transform: translateX(2px);
}
.submenu-title-noDropdown:hover .svg-icon,
.el-submenu > .el-submenu__title:hover .svg-icon,
.el-menu-item:hover .svg-icon {
color: #0a67d1 !important;
}
.el-submenu.is-active > .el-submenu__title,
.submenu-title-noDropdown.is-active,
.el-menu-item.is-active {
background: linear-gradient(135deg, #1576e8 0%, #44a0ff 100%) !important;
color: #ffffff !important;
box-shadow: 0 14px 24px rgba(21, 118, 232, 0.26);
}
.el-submenu.is-active > .el-submenu__title .svg-icon,
.submenu-title-noDropdown.is-active .svg-icon,
.el-menu-item.is-active .svg-icon {
color: #ffffff !important;
}
.el-submenu .el-menu {
height: auto !important;
margin: 2px 0 12px;
padding: 8px;
background: rgba(255, 255, 255, 0.68) !important;
border-radius: 16px;
box-shadow: inset 0 0 0 1px rgba(145, 178, 233, 0.18);
backdrop-filter: blur(8px);
}
.el-submenu .el-menu-item {
min-width: 100% !important;
height: 40px;
line-height: 40px;
margin-bottom: 6px;
padding-left: 42px !important;
border-radius: 12px;
font-size: 13px;
color: #48617f !important;
background: transparent !important;
}
.el-submenu .el-menu-item:last-child {
margin-bottom: 0;
}
.el-submenu .el-menu-item:hover {
background: rgba(21, 118, 232, 0.10) !important;
color: #0a67d1 !important;
}
.el-submenu .el-menu-item.is-active {
background: rgba(21, 118, 232, 0.14) !important;
color: #0a67d1 !important;
box-shadow: none;
}
.el-submenu .el-menu-item.is-active::before {
content: '';
position: absolute;
left: 18px;
top: 50%;
width: 8px;
height: 8px;
border-radius: 50%;
background: #1576e8;
transform: translateY(-50%);
}
.el-submenu__icon-arrow {
margin-top: -5px;
right: 18px;
color: #90a3bd;
font-weight: 700;
}
}
.hideSidebar {
.sidebar-container {
width: 36px !important;
width: 72px !important;
}
.main-container {
margin-left: 36px;
margin-left: 72px;
}
.submenu-title-noDropdown {
padding-left: 10px !important;
padding-left: 0 !important;
position: relative;
.el-tooltip {
padding: 0 10px !important;
padding: 0 !important;
}
}
.sidebar-container .sidebar-menu {
padding-left: 8px;
padding-right: 8px;
}
.sidebar-container .el-menu-item,
.sidebar-container .el-submenu > .el-submenu__title {
padding: 0 !important;
text-align: center;
}
.sidebar-container .svg-icon {
margin-right: 0;
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding-left: 12px !important;
padding-left: 0 !important;
.el-submenu__icon-arrow {
display: none;
}
@@ -90,14 +221,13 @@
}
.sidebar-container .nest-menu .el-submenu>.el-submenu__title,
.sidebar-container .el-submenu .el-menu-item {
min-width: 200px !important;
background-color: white !important;
min-width: 100% !important;
&:hover {
background-color: variables.$menuHover !important;
background-color: rgba(21, 118, 232, 0.10) !important;
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: 180px !important;
min-width: 220px !important;
}
//适配移动端
@@ -107,12 +237,12 @@
}
.sidebar-container {
transition: transform .28s;
width: 180px !important;
width: 236px !important;
}
&.hideSidebar {
.sidebar-container {
transition-duration: 0.3s;
transform: translate3d(-180px, 0, 0);
transform: translate3d(-236px, 0, 0);
}
}
}

View File

@@ -80,8 +80,8 @@
<el-row class="personnel-shell contentRow">
<el-col :span="6">
<el-card class="box-card departmentCard" style="min-height: 600px; max-height: 650px">
<div slot="header" class="clearfix" style="display: flex; align-items: center;justify-content: space-between;">
<el-card class="box-card departmentCard" style="min-height: 600px; max-height: 700px">
<div slot="header" style="display: flex; align-items: center;justify-content: space-between;width: 100%;padding: 0 10px;">
<span>{{ $t('personnelManagement.departmentMaintenance') }}</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="expandALL()">{{ $t('personnelManagement.expand') }}</el-button>
</div>
@@ -242,7 +242,7 @@
</template>
</el-table-column>
</el-table>
<div class="block" style="margin-top: 10px;">
<div class="block">
<el-pagination
:current-page="PageCurrent"
:page-size="PageSize"
@@ -535,10 +535,11 @@
<el-table-column align="center" :label="$t('personnelManagement.operation')" width="100">
<template slot-scope="scope">
<el-button
icon="el-icon-circle-close-outline"
icon="el-icon-delete"
size="mini"
type="danger"
type="text"
@click="deletePeople(scope.row)"
style="color: red"
>{{ $t('personnelManagement.delete') }}
</el-button>
</template>
@@ -1282,7 +1283,7 @@ export default {
<style scoped>
.personnel-shell {
margin-bottom: 14px;
margin-bottom: 0;
}
.actionCard,

View File

@@ -0,0 +1,655 @@
<template>
<div class="app-container mes-admin-page">
<el-card class="toolbar-card" shadow="never">
<div class="toolbar">
<div class="toolbar-left">
<span class="toolbar-label">设备/工位</span>
<el-select
v-model="machineCode"
clearable
filterable
placeholder="不选则统计全部设备/工位"
size="small"
style="width: 260px"
@change="handleMachineChange">
<el-option
v-for="item in machineOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<el-button
icon="el-icon-search"
plain
size="small"
style="margin-left: 10px"
type="primary"
@click="searchData">查询
</el-button>
</div>
<div class="toolbar-right" v-if="selectedMachineName">
<span class="machine-name">{{ selectedMachineName }}</span>
</div>
</div>
</el-card>
<div v-loading="loading" class="content-wrapper">
<el-row :gutter="16" class="summary-row">
<el-col :span="6">
<div class="summary-card total">
<div class="summary-title">刀具总数</div>
<div class="summary-value">{{ summaryInfo.totalCount }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card normal">
<div class="summary-title">正常刀具</div>
<div class="summary-value">{{ summaryInfo.normalCount }}</div>
<div class="summary-unit">{{ normalRateText }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card warning">
<div class="summary-title">预警刀具</div>
<div class="summary-value">{{ summaryInfo.warningCount }}</div>
<div class="summary-unit">{{ warningRateText }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card scrap">
<div class="summary-title">报废刀具</div>
<div class="summary-value">{{ summaryInfo.scrapCount }}</div>
<div class="summary-unit">{{ scrapRateText }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="16" class="detail-row">
<el-col :span="7" style="padding: 0">
<el-card class="panel-card overview-card" shadow="never">
<div slot="header" class="panel-header">
<div style="height:32px;align-items: center;display: flex;justify-content: space-between;width:100%;color:#243447">
<span>状态概览</span>
<span class="panel-subtitle">按设备统计刀具状态</span>
</div>
</div>
<div class="status-ring-wrapper">
<div class="status-ring" :style="statusRingStyle">
<div class="status-ring-inner">
<div class="status-ring-total">{{ summaryInfo.totalCount }}</div>
<div class="status-ring-label">刀具总数</div>
</div>
</div>
</div>
<div class="bar-group">
<div class="bar-item">
<div class="bar-label">
<span>正常占比</span>
<span>{{ normalRateText }}</span>
</div>
<el-progress
:percentage="normalRate"
:stroke-width="14"
color="#10b981"
:show-text="false"/>
</div>
<div class="bar-item">
<div class="bar-label">
<span>预警占比</span>
<span>{{ warningRateText }}</span>
</div>
<el-progress
:percentage="warningRate"
:stroke-width="14"
color="#f59e0b"
:show-text="false"/>
</div>
<div class="bar-item">
<div class="bar-label">
<span>报废占比</span>
<span>{{ scrapRateText }}</span>
</div>
<el-progress
:percentage="scrapRate"
:stroke-width="14"
color="#ef4444"
:show-text="false"/>
</div>
</div>
</el-card>
</el-col>
<el-col :span="17" style="padding: 0">
<div slot="header" class="panel-header">
<span>刀具明细</span>
<span class="panel-subtitle">该设备下每把刀的寿命与状态</span>
</div>
<el-table
:data="tableData"
:header-cell-style="{ background: '#f3f6fb', color: '#3a4a5d' }"
border
height="520"
size="mini"
style="width: 100%">
<el-table-column align="center" label="序号" type="index" width="60"/>
<el-table-column align="center" label="设备/工位" min-width="120">
<template slot-scope="scope">
{{ scope.row.设备编号 || '-' }}
</template>
</el-table-column>
<el-table-column align="center" label="刀具代码" min-width="120">
<template slot-scope="scope">
{{ scope.row.刀具代码 || '-' }}
</template>
</el-table-column>
<!-- <el-table-column align="center" label="刀具名称" min-width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.刀具名称 || '-' }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column align="center" label="刀具组号" min-width="110">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.刀具组号 || '-' }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column align="center" label="额定寿命" min-width="100">
<template slot-scope="scope">
{{ formatNumber(scope.row.额定寿命) }}
</template>
</el-table-column>
<el-table-column align="center" label="已用寿命" min-width="100">
<template slot-scope="scope">
{{ formatNumber(scope.row.已用寿命) }}
</template>
</el-table-column>
<el-table-column align="center" label="预警值1" min-width="100">
<template slot-scope="scope">
{{ formatNumber(scope.row.预警值1) }}
</template>
</el-table-column>
<el-table-column align="center" label="剩余寿命" min-width="100">
<template slot-scope="scope">
{{ formatNumber(getRemainingLife(scope.row)) }}
</template>
</el-table-column>
<el-table-column align="center" label="寿命进度" min-width="150">
<template slot-scope="scope">
<div class="table-progress">
<el-progress
:text-inside="true"
:percentage="calcRate(scope.row.已用寿命, scope.row.额定寿命)"
:color="getRowProgressColor(scope.row)"
:stroke-width="12"/>
</div>
</template>
</el-table-column>
<el-table-column align="center" label="状态" min-width="90">
<template slot-scope="scope">
<el-tag :type="getRowStatus(scope.row).type" size="mini">
{{ getRowStatus(scope.row).text }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<div v-if="!loading && !tableData.length" class="empty-block">
当前范围没有刀具寿命数据
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
loading: false,
machineCode: '',
selectedMachineName: '',
machineOptions: [],
tableData: [],
summaryInfo: {
totalCount: 0,
normalCount: 0,
warningCount: 0,
scrapCount: 0
}
}
},
computed: {
normalRate() {
return this.calcCountRate(this.summaryInfo.normalCount, this.summaryInfo.totalCount)
},
warningRate() {
return this.calcCountRate(this.summaryInfo.warningCount, this.summaryInfo.totalCount)
},
scrapRate() {
return this.calcCountRate(this.summaryInfo.scrapCount, this.summaryInfo.totalCount)
},
normalRateText() {
return this.normalRate.toFixed(1) + '%'
},
warningRateText() {
return this.warningRate.toFixed(1) + '%'
},
scrapRateText() {
return this.scrapRate.toFixed(1) + '%'
},
statusRingStyle() {
const normalEnd = this.normalRate
const warningEnd = this.normalRate + this.warningRate
return {
background: 'conic-gradient(#10b981 0% ' + normalEnd + '%, #f59e0b ' + normalEnd + '% ' + warningEnd + '%, #ef4444 ' + warningEnd + '% 100%)'
}
}
},
created() {
this.getMachineOptions()
},
methods: {
getMachineOptions() {
this.machineOptions = []
var param = []
var Data = this.CreateData('11', '设备管理_刀具寿命_机床列表_查询', param)
this.ExecDatabase(Data).then(response => {
const list = response.data || []
for (let i = 0; i < list.length; i++) {
const machineCode = list[i].设备编号 || ''
const machineName = list[i].设备名称 || machineCode
if (machineCode) {
this.machineOptions.push({
label: machineName,
value: machineCode,
machineName: machineName
})
}
}
this.selectedMachineName = '全部设备/工位'
this.searchData()
})
},
handleMachineChange(value) {
if (!value) {
this.selectedMachineName = '全部设备/工位'
return
}
const current = this.machineOptions.find(item => item.value === value)
this.selectedMachineName = current ? current.machineName : ''
},
searchData() {
this.loading = true
this.handleMachineChange(this.machineCode)
var param = []
param[0] = ['设备编号', this.machineCode || '']
var Data = this.CreateData('11', '设备管理_刀具寿命_明细_查询', param)
this.ExecDatabase(Data).then(response => {
const list = response.data || []
this.tableData = list
this.buildSummary(list)
this.loading = false
}).catch(() => {
this.loading = false
this.tableData = []
this.resetSummary()
this.$message.error('刀具寿命数据查询失败')
})
},
buildSummary(list) {
let normalCount = 0
let warningCount = 0
let scrapCount = 0
for (let i = 0; i < list.length; i++) {
const status = this.getRowStatus(list[i]).text
if (status === '正常') {
normalCount++
} else if (status === '预警') {
warningCount++
} else if (status === '报废') {
scrapCount++
}
}
this.summaryInfo = {
totalCount: list.length,
normalCount: normalCount,
warningCount: warningCount,
scrapCount: scrapCount
}
},
resetSummary() {
this.summaryInfo = {
totalCount: 0,
normalCount: 0,
warningCount: 0,
scrapCount: 0
}
},
toNumber(value) {
const num = Number(value)
return isNaN(num) ? 0 : num
},
formatNumber(value) {
const num = this.toNumber(value)
if (num % 1 === 0) {
return num
}
return num.toFixed(2)
},
calcRate(used, total) {
const totalNum = this.toNumber(total)
const usedNum = this.toNumber(used)
if (totalNum <= 0) {
return 0
}
let rate = usedNum / totalNum * 100
if (rate < 0) {
rate = 0
}
if (rate > 100) {
rate = 100
}
return Number(rate.toFixed(1))
},
calcCountRate(count, total) {
if (!total) {
return 0
}
return Number((count / total * 100).toFixed(1))
},
getRemainingLife(row) {
const remaining = this.toNumber(row.额定寿命) - this.toNumber(row.已用寿命)
return remaining > 0 ? remaining : 0
},
getRowStatus(row) {
const rated = this.toNumber(row.额定寿命)
const used = this.toNumber(row.已用寿命)
const warning = this.toNumber(row.预警值1)
if (rated > 0 && used >= rated) {
return {
text: '报废',
type: 'danger'
}
}
if (warning > 0 && used >= warning) {
return {
text: '预警',
type: 'warning'
}
}
return {
text: '正常',
type: 'success'
}
},
getRowProgressColor(row) {
const status = this.getRowStatus(row)
if (status.type === 'danger') {
return '#ef4444'
}
if (status.type === 'warning') {
return '#f59e0b'
}
return '#10b981'
}
}
}
</script>
<style scoped>
.tool-life-page {
background: #f5f7fb;
min-height: calc(100vh - 84px);
}
.toolbar-card,
.panel-card {
border: none;
border-radius: 16px;
}
.toolbar {
align-items: center;
display: flex;
justify-content: space-between;
min-height: 42px;
}
.toolbar-left {
align-items: center;
display: flex;
flex-wrap: wrap;
}
.toolbar-label {
color: #4c5a67;
font-size: 14px;
margin-right: 8px;
}
.toolbar-right {
color: #6b7785;
font-size: 13px;
}
.machine-name {
background: linear-gradient(135deg, #eef5ff, #f7fbff);
border: 1px solid #dbeafe;
border-radius: 999px;
color: #1d4ed8;
display: inline-block;
padding: 7px 14px;
}
.content-wrapper {
margin-top: 16px;
}
.summary-row {
margin-bottom: 16px;
}
.summary-card {
border-radius: 18px;
color: #fff;
min-height: 138px;
overflow: hidden;
padding: 22px 20px;
position: relative;
}
.summary-card::after {
background: rgba(255, 255, 255, 0.14);
border-radius: 50%;
content: '';
height: 120px;
position: absolute;
right: -20px;
top: -20px;
width: 120px;
}
.summary-card.total {
background: linear-gradient(135deg, #2563eb, #60a5fa);
}
.summary-card.normal {
background: linear-gradient(135deg, #059669, #34d399);
}
.summary-card.warning {
background: linear-gradient(135deg, #d97706, #fbbf24);
}
.summary-card.scrap {
background: linear-gradient(135deg, #dc2626, #f87171);
}
.summary-title {
font-size: 14px;
opacity: 0.92;
}
.summary-value {
font-size: 36px;
font-weight: 700;
line-height: 1.2;
margin-top: 16px;
}
.summary-unit {
font-size: 13px;
margin-top: 8px;
opacity: 0.88;
}
.detail-row {
margin-bottom: 16px;
}
.panel-header {
align-items: center;
display: flex;
justify-content: space-between;
}
.panel-subtitle {
color: #8a97a6;
font-size: 12px;
}
.overview-card {
min-height: 520px;
}
.status-ring-wrapper {
display: flex;
justify-content: center;
margin-top: 12px;
}
.status-ring {
align-items: center;
border-radius: 50%;
display: flex;
height: 220px;
justify-content: center;
width: 220px;
}
.status-ring-inner {
align-items: center;
background: #fff;
border-radius: 50%;
box-shadow: inset 0 0 30px rgba(37, 99, 235, 0.08);
display: flex;
flex-direction: column;
height: 166px;
justify-content: center;
width: 166px;
}
.status-ring-total {
color: #1f2937;
font-size: 34px;
font-weight: 700;
}
.status-ring-label {
color: #7b8794;
font-size: 13px;
margin-top: 8px;
}
.status-list {
background: #f8fafc;
border-radius: 14px;
margin-top: 22px;
padding: 10px 14px;
}
.status-item {
align-items: center;
display: flex;
min-height: 66px;
}
.status-dot {
border-radius: 50%;
height: 12px;
margin-right: 12px;
width: 12px;
}
.normal-dot {
background: #10b981;
}
.warning-dot {
background: #f59e0b;
}
.scrap-dot {
background: #ef4444;
}
.status-text {
flex: 1;
}
.status-name {
color: #243142;
font-size: 14px;
font-weight: 600;
}
.status-desc {
color: #8090a3;
font-size: 12px;
margin-top: 4px;
}
.status-count {
color: #111827;
font-size: 22px;
font-weight: 700;
}
.bar-group {
margin-top: 22px;
}
.bar-item {
margin-bottom: 18px;
}
.bar-label {
color: #495566;
display: flex;
font-size: 13px;
justify-content: space-between;
margin-bottom: 8px;
}
.table-progress {
padding: 0 10px;
}
.empty-block {
background: #fff;
border: 1px dashed #d6deea;
border-radius: 16px;
color: #8a97a6;
margin-top: 16px;
padding: 28px 16px;
text-align: center;
}
::v-deep .el-card__body {
padding: 18px 20px;
}
</style>

View File

@@ -141,7 +141,7 @@
size="mini">
<!-- <el-table-column :selectable="selectable" type="selection" width="45" align="center"/>-->
<el-table-column type="index" width="60" :label="$t('assemblyQualitydataQuery.sequence')" align="center"/>
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.orderNumber')">
<el-table-column align="center" width="100px" :label="$t('assemblyQualitydataQuery.orderNumber')">
<template slot-scope="scope">
{{ scope.row.订单号 }}
</template>
@@ -157,7 +157,7 @@
</template>
</el-table-column>
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.productModel')">
<el-table-column align="center" width="120px" :label="$t('assemblyQualitydataQuery.productModel')">
<template slot-scope="scope">
{{ scope.row.发动机型号 }}
</template>
@@ -188,7 +188,7 @@
<!-- {{ scope.row.工位名称 }}-->
<!-- </template>-->
<!--</el-table-column>-->
<el-table-column align="center" width="150px" :label="$t('assemblyQualitydataQuery.measuringPosition')">
<el-table-column align="center" width="100px" :label="$t('assemblyQualitydataQuery.measuringPosition')">
<template slot-scope="scope">
{{ scope.row.测量位置 }}
</template>
@@ -198,7 +198,7 @@
{{ scope.row.测量项目 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.measuringValue')" width="120px">
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.measuringValue')" width="100px">
<template slot-scope="scope">
{{ scope.row.测量值 }}
</template>
@@ -213,16 +213,16 @@
<!-- {{ scope.row.理论值 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.upperLimit')" width="100px">
<template slot-scope="scope">
{{ scope.row.上限值 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('assemblyQualitydataQuery.lowerLimit')" width="100px">
<template slot-scope="scope">
{{ scope.row.下限值 }}
</template>
</el-table-column>
<!-- <el-table-column align="center" :label="$t('assemblyQualitydataQuery.upperLimit')" width="100px">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.上限值 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column align="center" :label="$t('assemblyQualitydataQuery.lowerLimit')" width="100px">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.下限值 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column align="center" width="150px" :label="$t('assemblyQualitydataQuery.productionDate')">
<template slot-scope="scope">
{{ scope.row.生产日期 }}

View File

@@ -127,7 +127,7 @@
{{ scope.row.工位号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('inquiryTransitInformation.orderNumber')">
<el-table-column align="center" :label="$t('inquiryTransitInformation.orderNumber')" width="120">
<template slot-scope="scope">
{{ scope.row.订单号 }}
</template>
@@ -137,7 +137,7 @@
{{ scope.row.发动机号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('inquiryTransitInformation.productModel')">
<el-table-column align="center" :label="$t('inquiryTransitInformation.productModel')" width="120">
<template slot-scope="scope">
{{ scope.row.机型 }}
</template>
@@ -162,17 +162,17 @@
<!-- {{ scope.row.单元号 }}-->
<!-- </template>-->
<!--</el-table-column>-->
<el-table-column align="center" :label="$t('inquiryTransitInformation.arrivalTime')">
<el-table-column align="center" :label="$t('inquiryTransitInformation.arrivalTime')" width="150">
<template slot-scope="scope">
{{ scope.row.到达时间 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('inquiryTransitInformation.departureTime')">
<el-table-column align="center" :label="$t('inquiryTransitInformation.departureTime')" width="150">
<template slot-scope="scope">
{{ scope.row.离开时间 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('inquiryTransitInformation.onlineTimeSeconds')">
<el-table-column align="center" :label="$t('inquiryTransitInformation.onlineTimeSeconds')" width="100">
<template slot-scope="scope">
{{ scope.row.在线时间 }}
</template>

View File

@@ -8,8 +8,8 @@
<!-- <el-button type="distribution" size="small" icon="el-icon-circle-plus" @click="creatOrder('form')">增加</el-button>-->
</div>
<el-table
v-loading="loading"
ref="multipleTable"
v-loading="loading"
:data="tableData"
:header-cell-style="{background:'#eef1f6',color:'#606266'}"
:element-loading-text="$t('recipeManagement.loadingData')"
@@ -18,9 +18,10 @@
class="main"
tooltip-effect="dark"
height="600px"
style="width: 60%;"
style="width: 100%;"
border
size="mini">
size="mini"
>
<el-table-column align="center" :label="$t('recipeManagement.productModelCode')" width="130px">
<template slot-scope="scope">
{{ scope.row.发动机机型标志 }}
@@ -41,21 +42,27 @@
{{ scope.row.标识字符 }}
</template>
</el-table-column>
<!-- <el-table-column align="center" label="特征">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.壳体编号型号条码 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column align="center" label="规格(大机型)">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.箱体名称 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column align="center" label="螺栓数量">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.螺栓数量 }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column align="center" :label="$t('recipeManagement.crankcaseCode')">
<template slot-scope="scope">
{{ scope.row.箱体标志 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('recipeManagement.crankcaseModel')">
<template slot-scope="scope">
{{ scope.row.工厂编号2 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('recipeManagement.crankcaseModelName')">
<template slot-scope="scope">
{{ scope.row.流水号后缀 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('recipeManagement.combinePartNumber')">
<template slot-scope="scope">
{{ scope.row.壳体编号型号 }}
</template>
</el-table-column>
<!-- <el-table-column align="center" label="是否禁用">-->
<!-- <template slot-scope="scope">-->
<!-- <span v-if="Number(scope.row.是否禁用) === 1"> </span>-->
@@ -96,7 +103,8 @@
<el-input
v-model="form.equipmentType"
size="small"
:placeholder="$t('recipeManagement.partType')"/>
:placeholder="$t('recipeManagement.partType')"
/>
</el-form-item>
<el-form-item :label="$t('recipeManagement.partTypeSign')" prop="equipmentSign" style="display: inline-block">
<el-input-number
@@ -106,14 +114,16 @@
:step="1"
style="width: 200px"
size="small"
:placeholder="$t('recipeManagement.partTypeSign')"/>
:placeholder="$t('recipeManagement.partTypeSign')"
/>
</el-form-item>
<el-form-item :label="$t('recipeManagement.partTypeName')" prop="itemNo" style="display: inline-block">
<el-input
v-model="form.itemNo"
clearable
size="small"
:placeholder="$t('recipeManagement.partTypeName')"/>
:placeholder="$t('recipeManagement.partTypeName')"
/>
</el-form-item>
<!-- <el-form-item label="螺栓数量" prop="deviceNum" style="display: inline-block">-->
<!-- <el-input-number-->

View File

@@ -0,0 +1,134 @@
<template>
<div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
{{ $t('cylinderMaintenance.dragExcelFile') }}
<el-button :loading="loading" style="margin-left: 16px" plain type="success" size="mini" @click="handleUpload">{{ $t('cylinderMaintenance.browse') }}</el-button>
</div>
</div>
</template>
<script>
import * as XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error(this.$t('cylinderMaintenance.uploadOneFileOnly'))
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$message.error(this.$t('cylinderMaintenance.uploadExcelOnly'))
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
.drop{
border: 2px dashed #bbb;
width: 600px;
height: 160px;
line-height: 160px;
margin: 0 auto;
font-size: 24px;
border-radius: 5px;
text-align: center;
color: #bbb;
position: relative;
}
</style>

View File

@@ -0,0 +1,402 @@
<template>
<div class="app-container mes-admin-page">
<el-card class="topCard">
<div class="topDiv toolbar">
<span style="margin-left: 10px">{{ $t('cylinderMaintenance.keywordLabel') }}</span>
<el-input
v-model="keyword"
clearable
size="small"
style="width: 240px"
:placeholder="$t('cylinderMaintenance.keywordPlaceholder')"
@keyup.enter.native="handleSearch"
/>
<span style="margin-left: 10px">{{ $t('cylinderMaintenance.productionDateLabel') }}</span>
<el-date-picker
v-model="dateTime2"
align="center"
format="yyyy-MM-dd"
:placeholder="$t('cylinderMaintenance.selectProductionDate')"
size="small"
style="width: 150px;"
type="date"
value-format="yyyy-MM-dd"
/>
<span style="margin-left: 10px">{{ $t('cylinderMaintenance.productionStatusLabel') }}</span>
<el-select
v-model="productStatus"
:placeholder="$t('cylinderMaintenance.selectProductionStatus')"
size="small"
style="width: 150px"
>
<el-option
v-for="item in productStatusList"
:key="item.value"
:label="item.label"
:value="item.value"
size="mini"
/>
</el-select>
<span v-if="productStatus === 1" style="margin-left: 10px">{{ $t('cylinderMaintenance.onlineTimeLabel') }}</span>
<el-date-picker
v-if="productStatus === 1"
v-model="dateTime1"
align="center"
:end-placeholder="$t('cylinderMaintenance.endDate')"
format="yyyy-MM-dd"
:range-separator="$t('cylinderMaintenance.dateRangeSeparator')"
size="small"
:start-placeholder="$t('cylinderMaintenance.startDate')"
style="width: 240px;"
type="daterange"
value-format="yyyy-MM-dd"
/>
<el-button
type="primary"
plain
size="small"
icon="el-icon-search"
style="margin-left: 10px"
@click="handleSearch"
>{{ $t('cylinderMaintenance.search') }}</el-button>
<el-button
type="warning"
size="small"
icon="el-icon-upload2"
@click="openImportDialog"
>{{ $t('cylinderMaintenance.importData') }}</el-button>
</div>
<div class="tablePanel">
<el-table
v-loading="loading"
:data="tableDataAll"
:header-cell-style="{ background: '#eef1f6', color: '#606266' }"
:element-loading-text="$t('cylinderMaintenance.loadingData')"
:empty-text="$t('cylinderMaintenance.noData')"
border
height="640px"
size="mini"
style="width: 100%"
>
<el-table-column
type="index"
width="60"
align="center"
:label="$t('cylinderMaintenance.sequence')"
/>
<el-table-column align="center" :label="$t('cylinderMaintenance.blankNo')">
<template slot-scope="scope">
{{ scope.row.毛坯号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.materialNo')" width="100px">
<template slot-scope="scope">
{{ scope.row.物料号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.groupNo')" width="80px">
<template slot-scope="scope">
{{ scope.row.分组号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.productionMonth')" width="100px">
<template slot-scope="scope">
{{ scope.row.生产月份 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.crankcaseNo')">
<template slot-scope="scope">
{{ scope.row.曲轴箱号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.bindingTime')">
<template slot-scope="scope">
{{ scope.row.绑定时间 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.qrCode')">
<template slot-scope="scope">
{{ scope.row.二维码 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.serialNo')" width="100px">
<template slot-scope="scope">
{{ scope.row.流水号 }}
</template>
</el-table-column>
</el-table>
</div>
<div class="block" style="margin-top: 10px">
<el-pagination
:current-page="pageCurrent"
:page-sizes="[30, 50, 100]"
:page-size="pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<el-dialog
:visible.sync="dialogFormVisible3"
:title="$t('cylinderMaintenance.importTitle')"
center
style="min-height: 200px"
width="1000px"
>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload"/>
<el-table
v-loading="loading"
:data="tableDataListAll"
:header-cell-style="{ background: '#f5f5f5', color: '#606266' }"
:element-loading-text="$t('cylinderMaintenance.loadingData')"
:empty-text="$t('cylinderMaintenance.noImportData')"
border
height="320px"
size="mini"
style="width: 100%"
>
<el-table-column align="center" :label="$t('cylinderMaintenance.blankNo')">
<template slot-scope="scope">
{{ scope.row.毛坯号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.materialNo')">
<template slot-scope="scope">
{{ scope.row.物料号 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('cylinderMaintenance.groupNo')">
<template slot-scope="scope">
{{ scope.row.分组号 }}
</template>
</el-table-column>
</el-table>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="materialAddAllCancel">{{ $t('common.cancel') }}</el-button>
<el-date-picker
v-model="dateTime"
:clearable="false"
align="center"
format="yyyy-MM-dd"
size="small"
style="width: 150px;"
type="date"
value-format="yyyy-MM-dd"
/>
<el-button
type="primary"
icon="el-icon-upload"
size="small"
@click="materialAddAllSubmit"
>{{ $t('cylinderMaintenance.confirmImport') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import UploadExcelComponent from './UploadExcel.vue'
import { getNowShotTime } from '../../../utils/tool'
export default {
name: 'CylinderMaintenance',
components: { UploadExcelComponent },
data() {
return {
productStatus: 0,
dateTime: '',
dateTime2: '',
dateTime1: [],
keyword: '',
loading: false,
tableDataAll: [],
tableDataListAll: [],
dialogFormVisible3: false,
total: 0,
pageSize: 30,
pageCurrent: 1
}
},
computed: {
productStatusList() {
return [
{
label: this.$t('cylinderMaintenance.statusAll'),
value: 0
},
{
label: this.$t('cylinderMaintenance.statusCompleted'),
value: 1
},
{
label: this.$t('cylinderMaintenance.statusPending'),
value: 2
}
]
}
},
created() {
this.dateTime1 = [getNowShotTime(), getNowShotTime()]
},
mounted() {
this.searchOrder()
},
methods: {
handleSearch() {
this.pageCurrent = 1
this.searchOrder()
},
searchOrder() {
this.loading = true
var param = []
let dateTime1_check = 0
let dateTime1_start = ''
let dateTime1_end = ''
if (!this.dateTime1) {
dateTime1_check = 0
} else {
dateTime1_check = 1
dateTime1_start = this.dateTime1[0]
dateTime1_end = this.dateTime1[1]
}
param[0] = ['生产日期', this.dateTime2]
param[1] = ['关键字', this.keyword]
param[2] = ['生产时间_check', dateTime1_check]
param[3] = ['生产开始时间', dateTime1_start]
param[4] = ['生产结束时间', dateTime1_end]
param[5] = ['生产状态', this.productStatus]
param[6] = ['PageCurrent', this.pageCurrent]
param[7] = ['PageSize', this.pageSize]
param[8] = ['PageCount', '1111', 'int', '1']
param[9] = ['ItemCount', '1111', 'int', '1']
var Data = this.CreateData(
'11',
'缸体分组号_分页查询',
param
)
this.ExecDatabase(Data).then((response) => {
if (response.data.result.length !== 0) {
this.tableDataAll = response.data.result
this.total = parseInt(response.data.output[0].ItemCount)
} else {
this.tableDataAll = []
this.total = 0
}
}).catch(() => {
this.tableDataAll = []
this.total = 0
this.$message.error(this.$t('cylinderMaintenance.queryFailed'))
}).finally(() => {
this.loading = false
})
},
openImportDialog() {
this.clearImportData()
this.dialogFormVisible3 = true
},
beforeUpload(file) {
return true
},
handleSuccess({ results, header }) {
const tableName = `缸体分组号_导入临时表`
var Data = this.CreateData('7', tableName, results, header)
this.ExecDatabase(Data).then(response => {
this.fetchTempTableData()
})
},
fetchTempTableData() {
this.tableDataListAll = []
var param = []
var Data = this.CreateData('11', '缸体分组号_导入临时表_查询', param)
this.ExecDatabase(Data).then(response => {
this.tableDataListAll = response.data
}).catch(() => {
this.tableDataListAll = []
this.$message.error(this.$t('cylinderMaintenance.importPreviewFailed'))
})
},
materialAddAllSubmit() {
if (!this.tableDataListAll.length) {
this.$message.error(this.$t('cylinderMaintenance.noImportData'))
return
}
if (!this.dateTime) {
this.$message.error(this.$t('cylinderMaintenance.selectProductionDate'))
return
}
this.loading = true
const param = []
param[0] = ['生产月份', this.dateTime]
const Data = this.CreateData('12', '缸体分组号_确认导入_新增', param)
this.ExecDatabase(Data).then((response) => {
if (response.data.length > 0 && response.data[0].result === '1') {
this.$message.success(this.$t('cylinderMaintenance.importSuccess'))
this.dialogFormVisible3 = false
this.clearImportData()
this.searchOrder()
} else {
this.$message.error(this.$t('cylinderMaintenance.importFailed'))
}
}).catch(() => {
this.$message.error(this.$t('cylinderMaintenance.importFailed'))
}).finally(() => {
this.loading = false
})
},
materialAddAllCancel() {
this.dialogFormVisible3 = false
this.clearImportData()
},
clearImportData() {
this.tableDataListAll = []
},
handleSizeChange(val) {
this.pageCurrent = 1
this.pageSize = val
},
handleCurrentChange(val) {
this.pageCurrent = val
}
}
}
</script>
<style scoped>
.topCard {
margin: 5px;
}
.topDiv {
margin-bottom: 10px;
}
.toolbar {
align-items: center;
}
.tablePanel {
padding-top: 4px;
border-radius: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.52) 0%, rgba(243, 248, 252, 0.92) 100%);
}
.importHint {
margin: 10px 0;
color: #909399;
font-size: 13px;
}
.el-input {
width: 200px;
}
::v-deep .toolbar .el-button {
margin-bottom: 2px;
}
</style>

View File

@@ -101,7 +101,12 @@
size="mini"
>
<el-table-column align="center" type="index" :label="$t('releaseOfProductionPlan.sequence')" />
<el-table-column align="center" :label="$t('releaseOfProductionPlan.crankcaseAssemblyNo')" width="250">
<el-table-column align="center" label="缸体码">
<template slot-scope="scope">
{{ scope.row.箱体码 }}
</template>
</el-table-column>
<el-table-column align="center" :label="$t('releaseOfProductionPlan.crankcaseAssemblyNo')">
<template slot-scope="scope">
{{ scope.row.发动机号 }}
</template>

View File

@@ -1,48 +1,140 @@
<template>
<el-card style="min-height:calc(100vh - 84px);background: white;background-size:100% 100%">
<div class="dashboard-page">
<el-card class="dashboard-card">
<div class="dashboard-container">
<div class="sudoku_row">
<svg-icon icon-class="人员" style="width: 35px;height: 35px"/>
<div class="dashboard-text" style="color:black">/{{ name }}</div>
<el-divider content-position="right" style="margin:24px"/>
<section class="hero-panel">
<div class="hero-copy">
<div class="hero-badge">
<svg-icon icon-class="人员" class="hero-badge-icon"/>
<span>{{ $t('dashboard.workbench') }}</span>
</div>
<h1 class="hero-title">{{ greeting }}{{ name }}</h1>
<p class="hero-description">
{{ $t('dashboard.heroDescription', {
date: todayLabel,
moduleCount: fathers.length,
entryCount: sons.length,
noticeCount: noticeTotal
}) }}
</p>
</div>
<div class="hero-summary">
<div v-for="metric in heroMetrics" :key="metric.label" class="summary-pill">
<span class="summary-value">{{ metric.value }}</span>
<span class="summary-label">{{ metric.label }}</span>
</div>
</div>
</section>
<div v-for="(father, index1) in fathers" :key="index1" style="width:100%;background: white;margin-bottom: 15px">
<div style="width: 100%;height: 40px;line-height: 40px;background: rgb(127,172,237);color:white">
<svg-icon :icon-class="father.icon" style="color: white;margin: 5px 5px 0px 10px;border: 0 solid black;width:20px;height:20px"/>
{{ translateMenuTitle(father.title) }}
<section class="stats-grid">
<div
v-for="(metric, index) in heroMetrics"
:key="metric.label"
class="stat-card"
:style="getGradientStyle(index)"
>
<div class="stat-card__label">{{ metric.label }}</div>
<div class="stat-card__value">{{ metric.value }}</div>
<div class="stat-card__hint">{{ metric.hint }}</div>
</div>
</section>
<section class="notice-panel">
<div class="section-heading">
<div>
<div class="section-eyebrow">{{ $t('dashboard.focus') }}</div>
<h2 class="section-title">{{ $t('dashboard.pendingTitle') }}</h2>
</div>
<div class="section-meta">{{ $t('dashboard.sortHint') }}</div>
</div>
<!-- 使用计算属性后的 sonsMap不再有 v-if -->
<div v-if="sudokus.length" class="notice-grid">
<router-link
v-for="(son, index2) in getSonsByPid(father.id)"
:key="index2"
:to="son.path"
v-for="(item, index) in sudokus"
:key="item.id || index"
:to="item.router"
class="notice-card"
>
<el-card shadow="hover" class="sudoku_item1" style="height:130px;width:175px;margin: 5px;display: inline-block;background: rgba(255,255,255,0.4);border: 2px solid rgb(208,222,234)">
<svg-icon :icon-class="son.icon" class="bgc" style="color: rgb(37,89,164);display:block;margin: auto;border: 0px solid black;width:40px;height:40px"/>
<h4 style="margin-top: 10px;color:rgb(37,89,164);text-align:center">{{ translateMenuTitle(son.title) }}</h4>
</el-card>
<div class="notice-card__icon" :style="getSoftStyle(index)">
<svg-icon :icon-class="item.icon || '看板_board'"/>
</div>
<div class="notice-card__body">
<div class="notice-card__title">{{ translateMenuTitle(item.name) }}</div>
<div class="notice-card__subtitle">{{ $t('dashboard.handleHint') }}</div>
</div>
<div class="notice-card__count">{{ item.value }}</div>
</router-link>
</div>
<div v-else class="empty-panel">
<svg-icon icon-class="看板_board" class="empty-panel__icon"/>
<div class="empty-panel__title">{{ $t('dashboard.emptyTitle') }}</div>
<div class="empty-panel__text">{{ $t('dashboard.emptyText') }}</div>
</div>
</section>
<section
v-for="(section, sectionIndex) in moduleSections"
:key="section.id"
class="module-panel"
>
<div class="section-heading">
<div class="module-title-wrap">
<div class="module-icon" :style="getSoftStyle(sectionIndex)">
<svg-icon :icon-class="section.icon || '模块化'"/>
</div>
<div>
<div class="section-eyebrow">{{ $t('dashboard.module') }}</div>
<h2 class="section-title">{{ translateMenuTitle(section.title) }}</h2>
</div>
</div>
<div class="section-meta">{{ $t('dashboard.moduleEntryCount', { count: section.children.length }) }}</div>
</div>
<div class="module-grid">
<router-link
v-for="(son, itemIndex) in section.children"
:key="son.id"
:to="son.path"
class="module-entry"
>
<div class="module-entry__glow" :style="getSoftStyle(sectionIndex + itemIndex)"/>
<div class="module-entry__icon" :style="getSoftStyle(sectionIndex + itemIndex)">
<svg-icon :icon-class="son.icon || '模块化'"/>
</div>
<div class="module-entry__title">{{ translateMenuTitle(son.title) }}</div>
<div class="module-entry__meta">
<span>{{ $t('dashboard.quickAccess') }}</span>
<svg-icon icon-class="右箭头" class="module-entry__arrow"/>
</div>
</router-link>
</div>
</section>
</div>
</el-card>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import request from '@/utils/request'
import { translateMenuTitle } from '@/utils/i18n'
const DASHBOARD_TONES = [
['#1576e8', '#44a0ff'],
['#0f9f85', '#43c4a8'],
['#f08a24', '#ffb255'],
['#6e5df6', '#958bff']
]
export default {
name: 'Dashboard',
data() {
return {
colors: ['rgb(162,126,253)', 'rgb(254,178,0)', 'rgb(66,222,144)', 'rgb(68,176,254)', 'rgb(255,87,116)'],
sudokus: [],
curSelect: null,
number: '',
timer: '',
fathers: [],
sons: []
sons: [],
sudokus: []
}
},
computed: {
@@ -50,7 +142,6 @@ export default {
'name',
'token'
]),
// 预先按 pid 分组,避免每次渲染都过滤
sonsByPid() {
const map = {}
for (const son of this.sons) {
@@ -61,196 +152,595 @@ export default {
map[pid].push(son)
}
return map
},
moduleSections() {
return this.fathers.map(father => ({
...father,
children: this.sonsByPid[father.id] || []
})).filter(section => section.children.length > 0)
},
noticeTotal() {
return this.sudokus.reduce((total, item) => total + (Number(item.value) || 0), 0)
},
heroMetrics() {
return [
{
label: this.$t('dashboard.metricTopModules'),
value: this.fathers.length,
hint: this.$t('dashboard.metricTopModulesHint')
},
{
label: this.$t('dashboard.metricQuickEntries'),
value: this.sons.length,
hint: this.$t('dashboard.metricQuickEntriesHint')
}
// {
// label: this.$t('dashboard.metricPendingMessages'),
// value: this.noticeTotal,
// hint: this.$t('dashboard.metricPendingMessagesHint')
// },
// {
// label: this.$t('dashboard.metricNoticeSources'),
// value: this.sudokus.length,
// hint: this.$t('dashboard.metricNoticeSourcesHint')
// }
]
},
greeting() {
const hour = new Date().getHours()
if (hour < 12) return this.$t('dashboard.greetingMorning')
if (hour < 18) return this.$t('dashboard.greetingAfternoon')
return this.$t('dashboard.greetingEvening')
},
todayLabel() {
return new Intl.DateTimeFormat(this.$i18n.locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
weekday: 'long'
}).format(new Date())
}
},
mounted() {
this.getFileList()
this.timer = setInterval(this.getFileList, 60 * 60 * 1000) // 60 分钟一刷新
this.initFirstModule()
this.initSecondModule()
this.loadDashboardData()
this.timer = setInterval(this.getFileList, 60 * 60 * 1000)
},
beforeDestroy() {
clearInterval(this.timer)
},
methods: {
getSonsByPid(pid) {
return this.sonsByPid[pid] || []
},
translateMenuTitle,
async loadDashboardData() {
await Promise.all([
this.initFirstModule(),
this.initSecondModule(),
this.getFileList()
])
},
getGradientStyle(index) {
const tone = DASHBOARD_TONES[index % DASHBOARD_TONES.length]
return {
background: `linear-gradient(135deg, ${tone[0]} 0%, ${tone[1]} 100%)`
}
},
getSoftStyle(index) {
const tone = DASHBOARD_TONES[index % DASHBOARD_TONES.length]
return {
background: `linear-gradient(135deg, ${tone[0]}1f 0%, ${tone[1]}24 100%)`,
color: tone[0]
}
},
async initFirstModule() {
await request({
const data = await request({
url: '',
method: 'post',
data: {
type: '1',
name: `菜单系统模块_首页信息_查询数据1`,
name: '菜单系统模块_首页信息_查询数据1',
param: '角色编号=' + this.token
}
}).then(data => {
this.fathers = []
data.data.map(v => {
this.fathers.push({
})
this.fathers = (data.data || []).map(v => ({
id: v.id,
title: v.title,
icon: v.icon
})
})
})
},
Color(index) {
const colorAngle = Math.floor(Math.random() * 360 + index)
return 'hsla(' + colorAngle + ',70%,30%,1)'
}))
},
async initSecondModule() {
await request({
const data = await request({
url: '',
method: 'post',
data: {
type: '1',
name: `菜单系统模块_首页信息_查询数据2`,
name: '菜单系统模块_首页信息_查询数据2',
param: '角色编号=' + this.token
}
}).then(data => {
this.sons = []
data.data.map(v => {
this.sons.push({
})
this.sons = (data.data || []).map(v => ({
id: v.id,
pid: v.pid,
path: v.path,
icon: v.icon,
title: v.title
})
})
})
}))
},
async getFileList() {
await request({
const data = await request({
url: '',
method: 'post',
data: {
type: '1',
name: `菜单系统模块_通知信息_查询数据`,
name: '菜单系统模块_通知信息_查询数据',
param: '角色编号=' + this.token
}
}).then(data => {
this.sudokus = []
for (let i = 0; i < data.data.length; i++) {
this.getMessageNum(data.data[i].消息通知来源, data.data[i].通知条件, data, i)
}
})
const noticeRows = data.data || []
const sudokus = await Promise.all(noticeRows.map(item => this.getMessageNum(item)))
this.sudokus = sudokus
.filter(Boolean)
.sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0))
},
async getMessageNum(tableName, condition, rawData, i) {
await request({
async getMessageNum(item) {
const res = await request({
url: '',
method: 'post',
data: {
type: '3',
name: `select count(*) from ${tableName} where ${condition}`
name: `select count(*) from ${item.消息通知来源} where ${item.通知条件}`
}
}).then(res => {
this.sudokus.push({
id: rawData.data[i].id,
name: rawData.data[i].title,
icon: rawData.data[i].icon,
router: rawData.data[i].path,
value: res.data[0].Column1
})
})
return {
id: item.id,
name: item.title,
icon: item.icon,
router: item.path,
value: Number(res.data[0].Column1) || 0
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.dashboard-container .el-divider__text {
color: gray;
font-size: 22px;
background-color: rgba(255,255,255,0.8);
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.box-card {
width: 100%
}
.item {
margin-top: 10px;
margin-right: 40px;
}
.sudoku_row{
display: flex;
align-items: center;
width:100%;
flex-wrap: wrap;
}
.sudoku_item{
margin-top: 24ps;
margin-bottom: 20px;
margin-right: 20px;
border: 0 solid red;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width:240px;
height: 80px;
/*padding-top: 20px;*/
/*padding-bottom: 10px;*/
}
.sudoku_item:hover {
border: 2px solid white;
}
.sudoku_item img{
margin-bottom: 3px;
display: block;
}
.sudoku_item1{
margin-top: 24ps;
margin-bottom: 20px;
margin-left: 20px;
border: 0 solid red;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width:15%;
height: 80px;
padding-top: 20px;
/*padding-bottom: 10px;*/
}
.sudoku_item1:hover {
border: 2px solid rgb(135,187,224);
}
.sudoku_item1 img{
margin-bottom: 3px;
display: block;
}
.opacity{
opacity: 0.4;
background: #e5e5e5;
}
.dashboard {
&-container {
margin: 30px;
}
&-text {
font-size: 30px;
line-height: 46px;
}
}
</style>
<style>
.el-badge__content {
<style rel="stylesheet/scss" lang="scss">
.dashboard-page .el-badge__content {
background-color: rgb(255, 151, 91);
}
/*#dashbadge {*/
/*background-color: rgb(255,151,91);*/
/*}*/
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-page {
min-height: calc(100vh - 84px);
padding: 18px;
background:
radial-gradient(circle at top right, rgba(21, 118, 232, 0.12), transparent 24%),
linear-gradient(180deg, #f4f8ff 0%, #eef4fb 100%);
}
.dashboard-card {
min-height: calc(100vh - 120px);
border: none;
border-radius: 28px;
overflow: hidden;
background: rgba(255, 255, 255, 0.78);
box-shadow: 0 24px 60px rgba(26, 61, 111, 0.12);
}
.dashboard-container {
padding: 28px;
}
.hero-panel {
display: flex;
justify-content: space-between;
align-items: stretch;
gap: 24px;
padding: 28px;
border-radius: 28px;
background:
radial-gradient(circle at top right, rgba(255, 255, 255, 0.18), transparent 32%),
linear-gradient(135deg, #0e67d0 0%, #2f8fff 52%, #62b2ff 100%);
box-shadow: 0 24px 48px rgba(21, 118, 232, 0.22);
color: #ffffff;
}
.hero-copy {
flex: 1;
min-width: 0;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 36px;
padding: 0 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
font-size: 13px;
letter-spacing: 0.04em;
}
.hero-badge-icon {
width: 16px;
height: 16px;
}
.hero-title {
margin: 18px 0 10px;
font-size: 34px;
line-height: 1.18;
font-weight: 700;
}
.hero-description {
max-width: 720px;
margin: 0;
font-size: 15px;
line-height: 1.75;
color: rgba(255, 255, 255, 0.9);
}
.hero-summary {
display: grid;
grid-template-columns: repeat(2, minmax(120px, 1fr));
gap: 12px;
width: 320px;
}
.summary-pill {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 108px;
padding: 18px 20px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
}
.summary-value {
font-size: 26px;
line-height: 1;
font-weight: 700;
}
.summary-label {
margin-top: 10px;
font-size: 13px;
color: rgba(255, 255, 255, 0.86);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin-top: 22px;
}
.stat-card {
position: relative;
overflow: hidden;
min-height: 132px;
padding: 22px;
border-radius: 24px;
color: #ffffff;
}
.stat-card::after {
content: '';
position: absolute;
top: -12px;
right: -12px;
width: 86px;
height: 86px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
}
.stat-card__label {
position: relative;
z-index: 1;
font-size: 14px;
opacity: 0.9;
}
.stat-card__value {
position: relative;
z-index: 1;
margin-top: 16px;
font-size: 34px;
line-height: 1;
font-weight: 700;
}
.stat-card__hint {
position: relative;
z-index: 1;
margin-top: 12px;
font-size: 13px;
opacity: 0.84;
}
.notice-panel,
.module-panel {
margin-top: 24px;
padding: 24px;
border-radius: 26px;
background: rgba(255, 255, 255, 0.8);
box-shadow: inset 0 0 0 1px rgba(149, 178, 214, 0.18);
}
.section-heading {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 18px;
}
.section-eyebrow {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #7a91b1;
}
.section-title {
margin: 8px 0 0;
font-size: 24px;
line-height: 1.2;
color: #16345f;
}
.section-meta {
font-size: 13px;
color: #7d8ea8;
white-space: nowrap;
}
.notice-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
}
.notice-card {
display: flex;
align-items: center;
gap: 16px;
min-height: 112px;
padding: 20px;
border-radius: 22px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
box-shadow: 0 14px 30px rgba(28, 66, 117, 0.08);
transition: transform .22s ease, box-shadow .22s ease;
}
.notice-card:hover,
.module-entry:hover {
transform: translateY(-4px);
box-shadow: 0 18px 36px rgba(28, 66, 117, 0.14);
}
.notice-card__icon,
.module-icon,
.module-entry__icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 18px;
}
.notice-card__icon {
width: 52px;
height: 52px;
font-size: 24px;
}
.notice-card__body {
flex: 1;
min-width: 0;
}
.notice-card__title,
.module-entry__title {
color: #1a355f;
font-weight: 600;
}
.notice-card__title {
font-size: 15px;
line-height: 1.5;
}
.notice-card__subtitle {
margin-top: 6px;
font-size: 12px;
color: #8aa0bb;
}
.notice-card__count {
min-width: 48px;
height: 48px;
padding: 0 12px;
border-radius: 16px;
background: #eef5ff;
color: #1576e8;
font-size: 22px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
.empty-panel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 220px;
border: 1px dashed #c8d7ea;
border-radius: 24px;
background: linear-gradient(180deg, #fbfdff 0%, #f3f8ff 100%);
text-align: center;
}
.empty-panel__icon {
width: 42px;
height: 42px;
color: #8ea6c3;
}
.empty-panel__title {
margin-top: 16px;
font-size: 18px;
color: #26456f;
font-weight: 600;
}
.empty-panel__text {
margin-top: 8px;
font-size: 13px;
color: #8a9cb4;
}
.module-title-wrap {
display: flex;
align-items: center;
gap: 14px;
}
.module-icon {
width: 54px;
height: 54px;
font-size: 24px;
}
.module-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 16px;
}
.module-entry {
position: relative;
overflow: hidden;
min-height: 170px;
padding: 20px;
border-radius: 24px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
box-shadow: 0 14px 30px rgba(28, 66, 117, 0.08);
transition: transform .22s ease, box-shadow .22s ease;
}
.module-entry__glow {
position: absolute;
top: -22px;
right: -10px;
width: 82px;
height: 82px;
border-radius: 50%;
opacity: 0.9;
}
.module-entry__icon {
position: relative;
z-index: 1;
width: 56px;
height: 56px;
font-size: 26px;
}
.module-entry__title {
position: relative;
z-index: 1;
margin-top: 38px;
font-size: 15px;
line-height: 1.55;
min-height: 46px;
}
.module-entry__meta {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
font-size: 12px;
color: #7d93ae;
}
.module-entry__arrow {
width: 14px;
height: 14px;
}
@media (max-width: 1600px) {
.notice-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.module-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (max-width: 1280px) {
.hero-panel {
flex-direction: column;
}
.hero-summary {
width: 100%;
}
.stats-grid,
.notice-grid,
.module-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.dashboard-page {
padding: 12px;
}
.dashboard-container {
padding: 16px;
}
.hero-panel,
.notice-panel,
.module-panel {
padding: 18px;
border-radius: 22px;
}
.hero-title {
font-size: 28px;
}
.hero-summary,
.stats-grid,
.notice-grid,
.module-grid {
grid-template-columns: 1fr;
}
.section-heading {
align-items: flex-start;
flex-direction: column;
}
.notice-card {
align-items: flex-start;
flex-wrap: wrap;
}
}
</style>

View File

@@ -1,14 +1,31 @@
<template>
<div>
<el-menu class="navbar" mode="horizontal" style="background: #1576e8!important;">
<div class="navbar">
<div class="navbar__backdrop"/>
<div class="navbar__content">
<div class="navbar__left">
<div class="navbar__toggle">
<hamburger :toggle-click="toggleSideBar" :is-active="sidebar.opened" class="hamburger-container"/>
</div>
<div class="navbar__brand">
<!-- <div class="navbar__brand-title">{{ $t('login.systemName') }}</div>-->
<!-- <div class="navbar__brand-meta">-->
<!-- <span class="navbar__brand-chip">{{ currentDateLabel }}</span>-->
<!-- </div>-->
</div>
<div class="navbar__breadcrumb">
<breadcrumb />
</div>
</div>
<div class="navbar__right">
<el-dropdown class="language-container" trigger="click" @command="changeLanguage">
<el-button class="language-button" size="mini" type="text">
{{ currentLocaleLabel }}
<div class="nav-action language-button">
<span class="nav-action__icon el-icon-connection"/>
<span class="nav-action__text">{{ currentLocaleLabel }}</span>
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
</div>
<el-dropdown-menu slot="dropdown" class="navbar-dropdown">
<el-dropdown-item
v-for="item in localeOptions"
:key="item.value"
@@ -19,30 +36,38 @@
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<div class="avatar-wrapper__meta">
<span class="avatar-wrapper__label">{{ name }}</span>
</div>
<div class="avatar-wrapper__image-box">
<img :src="require('@/assets/img/leftPic.png')" class="user-avatar">
</div>
<i class="el-icon-caret-bottom"/>
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<el-dropdown-menu slot="dropdown" class="user-dropdown navbar-dropdown">
<router-link class="inlineBlock" to="/">
<el-dropdown-item>
{{ $t('common.home') }}
</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">{{ $t('common.logout') }}</span>
<span class="dropdown-action">{{ $t('common.logout') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<!-- <div style="position: absolute;right: 20%;top:8px">-->
<!-- <el-badge v-if="showError" :value="errorCount" type="danger">-->
<!-- <el-button size="small" type="warning" @click="showOrder">昨日未生产订单</el-button>-->
<!-- </el-badge>-->
<!-- </div>-->
</el-menu>
<el-dialog :visible.sync="dialogFormVisible4" title="未生产订单" center style="min-height: 200px" width="400px">
<div style="font-size: 1.5rem;color: #ff0000">
</div>
<el-dialog :visible.sync="dialogFormVisible4" title="未生产订单" center class="navbar-dialog" width="400px">
<div class="navbar-dialog__content">
存在订单号 {{ errorData[0].订单号 }} {{ errorCount }}个逾期订单请前往生产计划下发模块进行处理
</div>
<div slot="footer" class="dialog-footer">
@@ -131,6 +156,13 @@ export default {
currentLocaleLabel() {
const current = this.localeOptions.find(item => item.value === this.currentLocale)
return current ? current.label : this.$t('common.language')
},
currentDateLabel() {
return new Intl.DateTimeFormat(this.currentLocale, {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(new Date())
}
},
created() {
@@ -477,69 +509,296 @@ export default {
<style rel="stylesheet/scss" lang="scss" scoped>
.navbar {
height: 50px;
line-height: 50px;
border-radius: 0px !important;
.hamburger-container {
line-height: 58px;
height: 50px;
float: left;
padding: 0 10px;
}
.screenfull {
position: absolute;
right: 90px;
top: 16px;
color: red;
}
.language-container {
height: 50px;
display: inline-block;
position: absolute;
right: 95px;
top: 0;
.language-button {
height: 50px;
min-width: 76px;
padding: 0 8px;
color: #fff;
font-size: 13px;
line-height: 50px;
cursor: pointer;
}
.language-button:hover,
.language-button:focus {
color: #f2f6fc;
}
}
.avatar-container {
height: 50px;
display: inline-block;
position: absolute;
right: 35px;
.avatar-wrapper {
cursor: pointer;
margin-top: 5px;
position: relative;
width: 40px;
height: 40px;
background-color: #1576e8;
.user-avatar {
width: 30px;
height: 30px;
margin: 3px 0 0 3px;
padding: 5px 18px 5px;
overflow: hidden;
background: linear-gradient(135deg, #0d66cf 0%, #2084f3 54%, #54abff 100%);
box-shadow: 0 18px 36px rgba(18, 86, 171, 0.2);
}
.el-icon-caret-bottom {
color: #fff;
.navbar__backdrop {
position: absolute;
right: -10px;
top: 25px;
font-size: 12px;
inset: 0;
background:
radial-gradient(circle at left top, rgba(255, 255, 255, 0.18), transparent 28%),
radial-gradient(circle at right bottom, rgba(255, 255, 255, 0.12), transparent 24%);
pointer-events: none;
}
.navbar__content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
min-height: 56px;
}
.navbar__left,
.navbar__right {
display: flex;
align-items: center;
}
.navbar__left {
flex: 1;
min-width: 0;
}
.navbar__right {
gap: 12px;
flex-shrink: 0;
}
.navbar__toggle {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.14);
backdrop-filter: blur(10px);
transition: background-color .2s ease, transform .2s ease;
}
.navbar__toggle:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
}
.hamburger-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.navbar__brand {
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
max-width: 360px;
}
.navbar__brand-title {
overflow: hidden;
color: #ffffff;
font-size: 18px;
font-weight: 700;
line-height: 1.1;
text-overflow: ellipsis;
white-space: nowrap;
}
.navbar__brand-meta {
margin-top: 6px;
}
.navbar__brand-chip {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
letter-spacing: 0.04em;
}
.navbar__breadcrumb {
flex: 1;
min-width: 0;
margin-left: 8px;
padding: 0 14px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(10px);
}
.language-container,
.avatar-container {
display: flex;
align-items: center;
}
.nav-action,
.avatar-wrapper {
display: inline-flex;
align-items: center;
min-height: 48px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.14);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
color: #ffffff;
transition: background-color .2s ease, transform .2s ease;
cursor: pointer;
}
.nav-action:hover,
.avatar-wrapper:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
}
.language-button {
padding: 0 14px;
font-size: 13px;
}
.nav-action__icon {
font-size: 16px;
}
.nav-action__text {
margin-left: 8px;
}
.avatar-wrapper {
padding: 0 12px 0 14px;
gap: 10px;
}
.avatar-wrapper__meta {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: 1.2;
}
.avatar-wrapper__label {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
}
.avatar-wrapper__sub {
margin-top: 4px;
font-size: 11px;
color: rgba(255, 255, 255, 0.8);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.avatar-wrapper__image-box {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.18);
}
.user-avatar {
width: 26px;
height: 26px;
object-fit: contain;
}
.el-icon-caret-bottom {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.dropdown-action {
display: block;
}
.navbar-dialog__content {
font-size: 22px;
line-height: 1.6;
color: #f56c6c;
}
::v-deep .app-breadcrumb.el-breadcrumb {
display: flex;
align-items: center;
min-height: 48px;
margin-left: 0;
line-height: 1.4;
}
::v-deep .app-breadcrumb .el-breadcrumb__item {
display: inline-flex;
align-items: center;
}
::v-deep .app-breadcrumb .el-breadcrumb__inner,
::v-deep .app-breadcrumb .el-breadcrumb__inner a,
::v-deep .app-breadcrumb .no-redirect,
::v-deep .app-breadcrumb .el-breadcrumb__separator {
color: rgba(255, 255, 255, 0.92) !important;
}
::v-deep .app-breadcrumb .el-breadcrumb__inner {
font-size: 13px;
font-weight: 500;
}
::v-deep .navbar-dropdown.el-dropdown-menu {
border-radius: 16px;
border: none;
box-shadow: 0 16px 34px rgba(22, 56, 102, 0.16);
padding: 8px;
}
::v-deep .navbar-dropdown .el-dropdown-menu__item {
border-radius: 10px;
line-height: 38px;
font-size: 13px;
color: #214269;
}
::v-deep .navbar-dropdown .el-dropdown-menu__item:not(.is-disabled):hover {
background: #edf5ff;
color: #1576e8;
}
::v-deep .el-notification.right {
cursor: pointer !important;
}
@media (max-width: 1200px) {
.navbar__brand {
display: none;
}
}
@media (max-width: 900px) {
.navbar {
padding: 10px 12px;
}
.navbar__breadcrumb {
display: none;
}
.avatar-wrapper__meta {
display: none;
}
}
@media (max-width: 640px) {
.language-button {
min-width: 48px;
padding: 0 12px;
}
.nav-action__text {
display: none;
}
.navbar__right {
gap: 8px;
}
}
</style>

View File

@@ -1,24 +1,24 @@
<template>
<div>
<div class="sidebar-shell">
<el-scrollbar wrap-class="scrollbar-wrapper">
<div v-show="isShow" class="img-wrapper">
<img :src="require('@/assets/img/LOGO1.png')" height="49px">
<!-- <span style="font-size: 18px; font-weight: bold; color: white;margin-top: 7px">迈思信息<br>-->
<!-- <span style="font-size: 13px; color: white;margin-top: 0px!important;">MESWORK</span>-->
<!-- </span>-->
<div class="sidebar-shell">
<div v-show="showBrand" class="img-wrapper">
<img :src="require('@/assets/img/LOGO1.png')" class="brand-logo" alt="WODEER MES">
</div>
<el-menu
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
:style="menuStyle"
class="sidebar-menu"
mode="vertical"
background-color="white"
text-color="black"
active-text-color="#1576e8"
style="height:calc(100% - 124px)"
background-color="transparent"
text-color="#31456a"
active-text-color="#ffffff"
>
<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"/>
</el-menu>
</div>
</el-scrollbar>
</div>
</template>
@@ -29,12 +29,6 @@ import SidebarItem from './SidebarItem'
export default {
components: { SidebarItem },
data() {
return {
isShow: '',
leftPic: ''
}
},
computed: {
...mapGetters([
'sidebar',
@@ -44,37 +38,46 @@ export default {
return this.$router.options.routes
},
isCollapse() {
this.showLogo()
return !this.sidebar.opened
}
},
// created() {
// this.axios.get('../MESWORK.png', {
// responseType: 'arraybuffer'
// }).then(res => {
// this.leftPic = 'data:image/jpeg;base64,' + btoa(new Uint8Array(res.data).reduce((data, byte) => data + String.fromCharCode(byte), ''))
// })
// },
methods: {
showLogo() {
if (this.sidebar.opened === true) {
this.isShow = true
} else {
this.isShow = false
showBrand() {
return this.sidebar.opened
},
menuStyle() {
return {
height: this.sidebar.opened ? 'calc(100% - 128px)' : 'calc(100% - 12px)'
}
}
}
}
</script>
<style lang="scss" scoped>
.img-wrapper{
background-color: #ffffff !important;
text-align: center;
//border-bottom: 1px solid #1576e8;
img{
//width: 137px;
//height: 38px;
//margin: 6px 10px 5px 45px;
.sidebar-shell {
min-height: 100%;
display: flex;
flex-direction: column;
}
.img-wrapper {
display: flex;
align-items: center;
justify-content: center;
margin: 12px 10px 18px;
padding: 12px 10px;
min-height: 76px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.72);
box-shadow: inset 0 0 0 1px rgba(145, 178, 233, 0.18);
}
.brand-logo {
display: block;
max-width: 100%;
height: 49px;
object-fit: contain;
}
.sidebar-menu {
flex: 1;
}
</style>

View File

@@ -1,7 +1,27 @@
<template>
<div>
<div class="login-container">
<!-- <i class="login-img"/>-->
<div class="login-backdrop">
<div class="login-orb login-orb--one"/>
<div class="login-orb login-orb--two"/>
<div class="login-orb login-orb--three"/>
</div>
<div class="login-shell">
<section class="login-hero">
<!-- <div class="login-hero__badge">{{ currentLocaleLabel }}</div>-->
<h1 class="login-hero__title">{{ $t('login.systemName') }}</h1>
<div class="login-hero__grid">
<div class="hero-card hero-card--primary">
<span class="hero-card__value">{{ currentDateLabel }}</span>
<span class="hero-card__label">{{ currentLocaleLabel }}</span>
</div>
<!-- <div class="hero-card hero-card&#45;&#45;soft">-->
<!-- <span class="hero-card__value">{{ currentLocaleLabel }}</span>-->
<!-- <span class="hero-card__label">{{ $t('login.loginButton') }}</span>-->
<!-- </div>-->
</div>
</section>
<el-form
ref="loginForm"
:model="loginForm"
@@ -9,22 +29,15 @@
auto-complete="on"
label-position="left"
label-width="0px"
class="card-box login-form">
<h1 class="title" style="justify-content: center;align-items: center;">
<!-- <div style="width: 100px;">
<img :src="require('@/assets/img/LOGO.png')" alt="" style="height: 50px;">
</div> -->
<div style="margin: 0 10px">
<!-- <div>550KV GIS隔离开关</div> -->
<div>{{ $t('login.systemName') }}</div>
</div>
</h1>
class="card-box login-form"
>
<div class="login-form__header">
<el-dropdown class="login-language" trigger="click" @command="changeLanguage">
<el-button class="login-language-button" size="mini" type="text">
{{ currentLocaleLabel }}
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-menu slot="dropdown" class="login-dropdown">
<el-dropdown-item
v-for="item in localeOptions"
:key="item.value"
@@ -35,6 +48,11 @@
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<h1 class="title">
<div class="title__main">{{ $t('login.systemName') }}</div>
<div class="title__meta">{{ currentDateLabel }}</div>
</h1>
</div>
<div class="line"/>
<div class="content">
<el-form-item prop="username">
@@ -48,12 +66,13 @@
<svg-icon icon-class="password"/>
</span>
<el-input
:type="pwdType"
v-model="loginForm.password"
:type="pwdType"
name="password"
auto-complete="on"
:placeholder="$t('login.passwordPlaceholder')"
@keyup.enter.native="handleLogin"/>
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span>
</el-form-item>
<div style="display: flex;align-items: center;">
@@ -66,6 +85,7 @@
</div>
</el-form>
</div>
</div>
<el-dialog :visible.sync="dialogFormVisible" :title="$t('login.changePassword')" center style="min-height: 200px" width="400px">
<el-form ref="form" :model="form" :rules="rules" label-position="left" label-width="150px">
@@ -128,6 +148,13 @@ export default {
const current = this.localeOptions.find(item => item.value === this.currentLocale)
return current ? current.label : this.$t('common.language')
},
currentDateLabel() {
return new Intl.DateTimeFormat(this.currentLocale, {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(new Date())
},
rules() {
return {
againPassword: [
@@ -255,175 +282,347 @@ export default {
</script>
<style rel="stylesheet/scss" lang="scss">
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
position: fixed;
height: 100%;
width:100%;
background-color: #ebebeb;
background-image:url("../../../static/login.png");background-repeat:no-repeat; background-size:100% 100%;-moz-background-size:100% 100%;
inset: 0;
overflow: hidden;
background:
linear-gradient(115deg, rgba(7, 30, 72, 0.72), rgba(17, 91, 174, 0.44)),
url("../../../static/login.png") center center / cover no-repeat;
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px #293444 inset !important;
-webkit-text-fill-color: #fff !important;
-webkit-box-shadow: 0 0 0 1000px #ffffff inset !important;
-webkit-text-fill-color: #24405f !important;
}
/*.login-img{*/
/* position: absolute;*/
/* width: 100%;*/
/* height: 100%;*/
/* background: url("../../../static/login.png") no-repeat;*/
/*}*/
input {
background: transparent;
border: 1px;
border: none;
-webkit-appearance: none;
border-radius: 0;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
padding: 0 8px 0 14px;
color: #24405f;
height: 56px;
line-height: 56px;
}
.el-input {
display: inline-block;
height: 47px;
width: 85%;
height: 56px;
width: calc(100% - 56px);
}
.el-input__inner {
border: none !important;
background: transparent !important;
font-size: 17px !important;
color: #24405f !important;
padding: 0 !important;
height: 56px;
line-height: 56px;
}
.el-form-item {
margin-bottom: 18px;
border: 1px solid rgba(125, 162, 214, 0.36);
background: rgba(245, 249, 255, 0.92);
border-radius: 18px;
color: #454545;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.el-form-item:focus-within {
border-color: rgba(21, 118, 232, 0.72);
box-shadow: 0 14px 26px rgba(21, 118, 232, 0.14);
transform: translateY(-1px);
}
}
.login-backdrop {
position: absolute;
inset: 0;
background:
linear-gradient(90deg, rgba(8, 33, 79, 0.78) 0%, rgba(8, 33, 79, 0.32) 40%, rgba(255, 255, 255, 0.08) 100%);
}
.login-orb {
position: absolute;
border-radius: 50%;
filter: blur(6px);
}
.login-orb--one {
top: 10%;
left: 8%;
width: 240px;
height: 240px;
background: radial-gradient(circle, rgba(93, 176, 255, 0.28) 0%, rgba(93, 176, 255, 0) 72%);
}
.login-orb--two {
right: 18%;
bottom: 12%;
width: 280px;
height: 280px;
background: radial-gradient(circle, rgba(159, 210, 171, 0.22) 0%, rgba(159, 210, 171, 0) 72%);
}
.login-orb--three {
top: 28%;
right: 10%;
width: 140px;
height: 140px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 74%);
}
.login-shell {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
gap: 36px;
max-width: 1320px;
min-height: 100%;
margin: 0 auto;
padding: 40px 48px;
}
.login-hero {
flex: 1;
max-width: 540px;
color: #ffffff;
}
.login-hero__badge {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
font-size: 13px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.login-hero__title {
margin: 22px 0 0;
font-size: 44px;
line-height: 1.2;
font-weight: 700;
text-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}
.login-hero__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
margin-top: 34px;
}
.hero-card {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 132px;
padding: 22px;
border-radius: 24px;
backdrop-filter: blur(12px);
box-shadow: 0 20px 40px rgba(8, 26, 58, 0.18);
}
.hero-card--primary {
background: rgba(255, 255, 255, 0.16);
}
.hero-card--soft {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(126, 198, 255, 0.12));
}
.hero-card__value {
font-size: 26px;
font-weight: 700;
line-height: 1.2;
}
.hero-card__label {
margin-top: 12px;
font-size: 13px;
color: rgba(255, 255, 255, 0.84);
letter-spacing: 0.04em;
}
.login-form {
position: relative;
width: 100%;
max-width: 500px;
padding: 28px 28px 30px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.88);
box-shadow: 0 28px 60px rgba(9, 31, 74, 0.22);
backdrop-filter: blur(18px);
}
.login-form__header {
position: relative;
padding-right: 110px;
}
.login-language {
position: absolute;
top: 0;
right: 0;
.login-language-button {
min-width: 86px;
min-height: 38px;
padding: 0 12px;
border-radius: 12px;
background: rgba(21, 118, 232, 0.08);
color: #1576e8;
font-size: 13px;
box-shadow: inset 0 0 0 1px rgba(21, 118, 232, 0.12);
}
.login-language-button:hover,
.login-language-button:focus {
color: #0f5eb8;
background: rgba(21, 118, 232, 0.12);
}
}
.title {
margin: 0;
padding-right: 10px;
color: #123d70;
text-align: left;
}
.title__main {
font-size: 31px;
line-height: 1.28;
font-weight: 700;
letter-spacing: 0.02em;
}
.title__meta {
margin-top: 10px;
font-size: 13px;
color: #7890ab;
letter-spacing: 0.05em;
}
.line {
width: 100%;
margin: 24px auto 24px;
height: 1px;
background: linear-gradient(90deg, rgba(21, 118, 232, 0.44), rgba(21, 118, 232, 0));
}
.content {
width: 100%;
}
.svg-container {
padding: 6px 5px 6px 15px;
padding: 0 0 0 18px;
color: #1576e8;
font-size: 20px !important;
vertical-align: middle;
width: 30px;
width: 48px;
line-height: 56px;
display: inline-block;
&_login {
font-size: 20px;
}
}
.title {
font-size: 22px;
line-height: 30px;
color: #1576e8;
margin: 0 auto 20px auto;
text-align: center;
font-weight: bold;
font-family: 微软雅黑RegularBold;
display: flex;
}
.content{
width: 330px;
margin: 30px auto 0 auto;
}
.line{
width: 100%;
margin: 0 auto 25px auto;
background-color: #1576e8;
height: 1px;
}
.login-form {
height: 450px;
border-radius: 20px;
background-color: white;
position: absolute;
left: 0;
right: 0;
width: 400px;
padding: 35px 15px 15px 15px;
margin: 120px auto;
}
.login-language {
position: absolute;
right: 20px;
top: 14px;
.login-language-button {
min-width: 78px;
padding: 6px 8px;
color: #1576e8;
font-size: 13px;
cursor: pointer;
}
.login-language-button:hover,
.login-language-button:focus {
color: #0f5eb8;
}
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.el-input__inner{
border: none!important;
font-size: 18px!important;
color: #29335b!important;
}
.el-form-item {
border: 1px solid #1576e8;
background: #ffffff;
border-radius: 5px;
color: #454545;
}
/*登录button*/
.signIn {
width: 100%;
min-height: 56px;
margin-top: 16px;
border: none;
margin-top: 20px;
border-radius: 5px;
border-radius: 18px;
font-size: 18px;
color:white;
background: -webkit-linear-gradient(left, #1576e8 , #9fd2ab); /* Safari 5.1 - 6.0 */
background: -o-linear-gradient(right, #1576e8, #9fd2ab); /* Opera 11.1 - 12.0 */
background: -moz-linear-gradient(right, #1576e8, #9fd2ab); /* Firefox 3.6 - 15 */
background: linear-gradient(to right, #1576e8 , #9fd2ab); /* 标准的语法(必须放在最后) */
color: #ffffff;
box-shadow: 0 18px 32px rgba(21, 118, 232, 0.26);
background: linear-gradient(135deg, #1576e8 0%, #2ea2ff 55%, #77d1b8 100%);
}
.signIn:hover,
.signIn:focus {
background: linear-gradient(135deg, #1169cf 0%, #2194ee 55%, #67c2aa 100%);
}
.remeberPassword {
color: #1576e8;
color: #4f6987;
}
.change-password-button {
margin-left: auto;
color: #1576e8;
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
right: 16px;
top: 16px;
font-size: 17px;
color: #1576e8;
cursor: pointer;
user-select: none;
}
@media (min-width: 1024px) {
/*根据屏幕大小确定显示的位置*/
.login-dropdown.el-dropdown-menu {
border: none;
border-radius: 14px;
box-shadow: 0 18px 36px rgba(25, 63, 115, 0.16);
padding: 8px;
}
.login-dropdown .el-dropdown-menu__item {
border-radius: 10px;
color: #23466f;
}
.login-dropdown .el-dropdown-menu__item:not(.is-disabled):hover {
background: #edf5ff;
color: #1576e8;
}
@media (max-width: 1080px) {
.login-shell {
justify-content: center;
padding: 28px 20px;
}
.login-hero {
display: none;
}
}
@media (max-width: 640px) {
.login-form {
position: absolute;
left: 40%;
top: 0;
width: 600px;
max-width: none;
padding: 22px 18px 24px;
border-radius: 24px;
}
.title{
font-size: 32px!important;
line-height: 32px;
.login-form__header {
padding-right: 0;
}
.el-form-item{
width: 400px;
height: 48px;
}
.content{
width: 400px;
}
}
@media (min-width: 1281px) {
.login-form{
position: absolute;
left: 30%;
top: 6%;
width: 600px;
.login-language {
position: static;
display: flex;
justify-content: flex-end;
margin-bottom: 14px;
}
.title__main {
font-size: 24px;
}
}
</style>

View File

@@ -5,7 +5,7 @@ window.g = {
mqttIP: '192.168.0.80',
mqttPortNumber: '8083',
printTemplate: 'BQFT_BarCode_001',
baseURL: 'http://127.0.0.1:10026',
baseURL: 'http://192.168.5.100:10026',
requestConfig_baseURL: 'http://127.0.0.1:9981/mes'
// requestConfig_baseURL: 'http://10.89.33.67:6867/mes'
}