Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
6e92ac705c |
|
@ -4,8 +4,8 @@
|
|||
VITE_BUILD_MOCK = false
|
||||
|
||||
# 接口地址
|
||||
VITE_API_BASE_URL = 'http://pms.dtyx.net:9158/'
|
||||
VITE_API_WS_URL = 'ws://localhost:8000'
|
||||
VITE_API_BASE_URL = 'https://api.continew.top'
|
||||
VITE_API_WS_URL = 'wss://api.continew.top'
|
||||
|
||||
# 地址前缀
|
||||
VITE_BASE = '/'
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/Industrial-image-management-system---web" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -58,8 +58,7 @@
|
|||
"vue-router": "^4.3.3",
|
||||
"vue3-tree-org": "^4.2.2",
|
||||
"xe-utils": "^3.5.7",
|
||||
"xgplayer": "^2.31.6",
|
||||
"xlsx": "^0.18.5"
|
||||
"xgplayer": "^2.31.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.16.3",
|
||||
|
|
|
@ -143,9 +143,6 @@ importers:
|
|||
xgplayer:
|
||||
specifier: ^2.31.6
|
||||
version: 2.32.6
|
||||
xlsx:
|
||||
specifier: ^0.18.5
|
||||
version: 0.18.5
|
||||
devDependencies:
|
||||
'@antfu/eslint-config':
|
||||
specifier: ^2.16.3
|
||||
|
@ -1484,10 +1481,6 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adler-32@1.3.1:
|
||||
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
aieditor@1.0.13:
|
||||
resolution: {integrity: sha512-A1NIydCJgno3VvEKWPyHZlS7IF5FwBO1X4QO3GEKNcs8wMmmVGbcoVDPHON3uo9bTKaxuuIiONyfLCGHLBpW2Q==}
|
||||
|
||||
|
@ -1695,10 +1688,6 @@ packages:
|
|||
capital-case@1.0.4:
|
||||
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
||||
|
||||
cfb@1.2.2:
|
||||
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
chalk@1.1.3:
|
||||
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -1770,10 +1759,6 @@ packages:
|
|||
codemirror@6.0.1:
|
||||
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
||||
|
||||
codepage@1.15.0:
|
||||
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
collection-visit@1.0.0:
|
||||
resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -1864,11 +1849,6 @@ packages:
|
|||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
|
@ -2675,10 +2655,6 @@ packages:
|
|||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
frac@1.1.2:
|
||||
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
fragment-cache@0.2.1:
|
||||
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -4130,10 +4106,6 @@ packages:
|
|||
resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ssf@0.11.2:
|
||||
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
stable@0.1.8:
|
||||
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
|
||||
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
|
||||
|
@ -4715,18 +4687,10 @@ packages:
|
|||
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
wmf@1.0.2:
|
||||
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
word-wrap@1.2.5:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
word@0.3.0:
|
||||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -4749,11 +4713,6 @@ packages:
|
|||
resolution: {integrity: sha512-ESwYYcG8SQciPaN43tZkN3r0dS/jQ5RtyxyGbxn2+qcKgZQ861M899xq8Cab/z6qVVX+/4eIsxDbm3lfYGYzvA==}
|
||||
hasBin: true
|
||||
|
||||
xlsx@0.18.5:
|
||||
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
xml-name-validator@4.0.0:
|
||||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -6207,8 +6166,6 @@ snapshots:
|
|||
|
||||
acorn@8.11.3: {}
|
||||
|
||||
adler-32@1.3.1: {}
|
||||
|
||||
aieditor@1.0.13(@tiptap/extension-code-block@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)):
|
||||
dependencies:
|
||||
'@tiptap/core': 2.5.8(@tiptap/pm@2.5.8)
|
||||
|
@ -6464,11 +6421,6 @@ snapshots:
|
|||
tslib: 2.6.2
|
||||
upper-case-first: 2.0.2
|
||||
|
||||
cfb@1.2.2:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
crc-32: 1.2.2
|
||||
|
||||
chalk@1.1.3:
|
||||
dependencies:
|
||||
ansi-styles: 2.2.1
|
||||
|
@ -6569,8 +6521,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- '@lezer/common'
|
||||
|
||||
codepage@1.15.0: {}
|
||||
|
||||
collection-visit@1.0.0:
|
||||
dependencies:
|
||||
map-visit: 1.0.0
|
||||
|
@ -6658,8 +6608,6 @@ snapshots:
|
|||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cron-parser@4.9.0:
|
||||
|
@ -7600,8 +7548,6 @@ snapshots:
|
|||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
frac@1.1.2: {}
|
||||
|
||||
fragment-cache@0.2.1:
|
||||
dependencies:
|
||||
map-cache: 0.2.2
|
||||
|
@ -9103,10 +9049,6 @@ snapshots:
|
|||
dependencies:
|
||||
extend-shallow: 3.0.2
|
||||
|
||||
ssf@0.11.2:
|
||||
dependencies:
|
||||
frac: 1.1.2
|
||||
|
||||
stable@0.1.8: {}
|
||||
|
||||
static-extend@0.1.2:
|
||||
|
@ -9775,12 +9717,8 @@ snapshots:
|
|||
dependencies:
|
||||
string-width: 5.1.2
|
||||
|
||||
wmf@1.0.2: {}
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
word@0.3.0: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
@ -9817,16 +9755,6 @@ snapshots:
|
|||
fs-extra: 5.0.0
|
||||
xgplayer-subtitles: 1.0.19
|
||||
|
||||
xlsx@0.18.5:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
cfb: 1.2.2
|
||||
codepage: 1.15.0
|
||||
crc-32: 1.2.2
|
||||
ssf: 0.11.2
|
||||
wmf: 1.0.2
|
||||
word: 0.3.0
|
||||
|
||||
xml-name-validator@4.0.0: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
|
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
Before Width: | Height: | Size: 1.3 MiB |
|
@ -1,20 +0,0 @@
|
|||
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 风机塔筒 -->
|
||||
<rect x="47" y="40" width="6" height="45" fill="#4a5568" rx="3"/>
|
||||
|
||||
<!-- 风机机舱 -->
|
||||
<ellipse cx="50" cy="40" rx="8" ry="5" fill="#2d3748"/>
|
||||
|
||||
<!-- 风机叶片 -->
|
||||
<g transform="translate(50, 40)">
|
||||
<!-- 叶片1 -->
|
||||
<path d="M0,0 L-25,-15 Q-35,-20 -40,-30" stroke="#4a5568" stroke-width="3" fill="none"/>
|
||||
<!-- 叶片2 -->
|
||||
<path d="M0,0 L15,-30 Q20,-40 30,-45" stroke="#4a5568" stroke-width="3" fill="none"/>
|
||||
<!-- 叶片3 -->
|
||||
<path d="M0,0 L30,10 Q40,15 45,25" stroke="#4a5568" stroke-width="3" fill="none"/>
|
||||
</g>
|
||||
|
||||
<!-- 风机底座 -->
|
||||
<ellipse cx="50" cy="85" rx="12" ry="3" fill="#2d3748"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 761 B |
|
@ -17,13 +17,14 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore, useUserStore } from '@/stores'
|
||||
// 1
|
||||
|
||||
defineOptions({ name: 'App' })
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
appStore.initTheme()
|
||||
appStore.initSiteConfig()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loading-icon {
|
||||
animation: arco-loading-circle 1s infinite cubic-bezier(0,0,1,1);
|
||||
|
|
|
@ -6,7 +6,6 @@ import type { AttachInfoData, BusinessTypeResult } from './type'
|
|||
* 批量新增附件信息
|
||||
* @param businessType 业务类型
|
||||
* @param files 文件列表
|
||||
* @returns
|
||||
*/
|
||||
export function batchAddAttachment(businessType: string, formData: FormData) {
|
||||
return request<AttachInfoData[]>({
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import http from '@/utils/http'
|
||||
import type { AttendanceRecordReq, AttendanceRecordResp } from './type'
|
||||
|
||||
const BASE_URL = '/attendance-record'
|
||||
|
||||
/** 新增考勤记录 */
|
||||
export function addAttendanceRecord(data: AttendanceRecordReq) {
|
||||
return http.post<AttendanceRecordResp>(BASE_URL, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
/** 新增考勤记录请求体 */
|
||||
export interface AttendanceRecordReq {
|
||||
recordImage?: string
|
||||
recordPosition?: string
|
||||
recordPositionLabel?: string
|
||||
}
|
||||
|
||||
/** 新增考勤记录响应体 */
|
||||
export interface AttendanceRecordResp {
|
||||
code: number
|
||||
data: object
|
||||
msg: string
|
||||
status: number
|
||||
success: boolean
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import http from '@/utils/http'
|
||||
import type { CertificationInfo, CertificationListParams, CertificationListResponse, SimpleUserInfo,CertificationPageResponse, CertificationReq } from './type'
|
||||
import type { CertificationInfo, CertificationListParams, CertificationListResponse, SimpleUserInfo } from './type'
|
||||
|
||||
const { request } = http
|
||||
|
||||
|
@ -7,7 +7,7 @@ const { request } = http
|
|||
export type { CertificationInfo, CertificationListParams, CertificationListResponse, SimpleUserInfo }
|
||||
|
||||
// 新增人员资质
|
||||
export function createCertification(data: CertificationReq) {
|
||||
export function createCertification(data: CertificationInfo) {
|
||||
return request({
|
||||
url: '/certification',
|
||||
method: 'post',
|
||||
|
@ -74,12 +74,4 @@ export function getUserList() {
|
|||
url: '/user/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
// 查询人员资质信息分页列表(新接口)
|
||||
export function getCertificationPage(params: CertificationListParams) {
|
||||
return request<CertificationPageResponse>({
|
||||
url: '/certification/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
|
@ -11,25 +11,19 @@ export interface CertificationInfo {
|
|||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
export interface CertificationReq {
|
||||
certificationCode: string
|
||||
certificationImage: string
|
||||
certificationName: string
|
||||
certificationType: string
|
||||
userId: string
|
||||
validityDateBegin: string
|
||||
validityDateEnd: string
|
||||
}
|
||||
|
||||
/** 人员资质列表查询参数 */
|
||||
export interface CertificationListParams {
|
||||
certificationName?: string
|
||||
certificationType?: string
|
||||
userName?: string
|
||||
current?: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
/** 人员资质列表响应 */
|
||||
export interface CertificationListResponse {
|
||||
data: CertificationInfo[]
|
||||
records: CertificationInfo[]
|
||||
total: number
|
||||
current: number
|
||||
size: number
|
||||
|
@ -38,22 +32,7 @@ export interface CertificationListResponse {
|
|||
/** 用户信息简化版 */
|
||||
export interface SimpleUserInfo {
|
||||
userId: string
|
||||
name: string
|
||||
userName: string
|
||||
account: string
|
||||
}
|
||||
export interface CertificationInfo {
|
||||
certificationId?: string
|
||||
certificationCode: string
|
||||
certificationName: string
|
||||
userId: string
|
||||
userName?: string // 新增,后端会返回
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface CertificationPageResponse {
|
||||
rows: CertificationInfo[]
|
||||
total: number
|
||||
code?: number
|
||||
msg?: string
|
||||
[key: string]: any
|
||||
}
|
||||
name: string
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import http from '@/utils/http'
|
||||
import type { PerformanceDimension, PerformanceRule, DimensionQuery, RuleQuery } from './type'
|
||||
|
||||
/** 维度相关 */
|
||||
export function getDimensionList(params?: DimensionQuery) {
|
||||
return http.get<PerformanceDimension[]>('/performance-dimension/list', params)
|
||||
}
|
||||
export function getDimensionDetail(id: string) {
|
||||
return http.get<PerformanceDimension>(`/performance-dimension/${id}`)
|
||||
}
|
||||
export function addDimension(data: Partial<PerformanceDimension>) {
|
||||
return http.post('/performance-dimension', data)
|
||||
}
|
||||
export function updateDimension(id: string, data: Partial<PerformanceDimension>) {
|
||||
return http.put(`/performance-dimension/${id}`, data)
|
||||
}
|
||||
export function deleteDimension(id: string) {
|
||||
return http.del(`/performance-dimension/${id}`)
|
||||
}
|
||||
|
||||
/** 细则相关 */
|
||||
export function getRuleList(params?: RuleQuery) {
|
||||
return http.get<PerformanceRule[]>('/performance-rule/list', params)
|
||||
}
|
||||
export function getRuleDetail(id: string) {
|
||||
return http.get<PerformanceRule>(`/performance-rule/${id}`)
|
||||
}
|
||||
export function addRule(data: Partial<PerformanceRule>) {
|
||||
return http.post('/performance-rule', data)
|
||||
}
|
||||
export function updateRule(id: string, data: Partial<PerformanceRule>) {
|
||||
return http.put(`/performance-rule/${id}`, data)
|
||||
}
|
||||
export function deleteRule(id: string) {
|
||||
return http.del(`/performance-rule/${id}`)
|
||||
}
|
||||
// 我的绩效
|
||||
export function getMyEvaluation() {
|
||||
return http.get('/performance-evaluation/my')
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/** 绩效维度 */
|
||||
export interface PerformanceDimension {
|
||||
dimensionId: string
|
||||
dimensionName: string
|
||||
description?: string
|
||||
deptName: string
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 绩效细则 */
|
||||
export interface PerformanceRule {
|
||||
ruleId: string
|
||||
ruleName: string
|
||||
description?: string
|
||||
dimensionName: string
|
||||
bonus?: string
|
||||
score?: number
|
||||
weight?: number
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 查询参数 */
|
||||
export interface DimensionQuery {
|
||||
dimensionName?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
export interface RuleQuery {
|
||||
dimensionName?: string
|
||||
ruleName?: string
|
||||
status?: 0 | 1
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import type * as T from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/performance'
|
||||
|
||||
/* ===== 维度 ===== */
|
||||
export const getDimensionList = () => http.get<T.DimensionResp[]>(`${BASE_URL}/dimension`)
|
||||
|
||||
export const addDimension = (data: T.DimensionAddReq) =>
|
||||
http.post<boolean>(`${BASE_URL}/dimension`, data)
|
||||
|
||||
export const updateDimension = (id: string, data: T.DimensionUpdateReq) =>
|
||||
http.put<boolean>(`${BASE_URL}/dimension/${id}`, data)
|
||||
|
||||
export const deleteDimension = (id: string) =>
|
||||
http.del<boolean>(`${BASE_URL}/dimension/${id}`)
|
||||
|
||||
/** 维度详情(RuleDrawer.vue 需要) */
|
||||
export const getDimensionDetail = (id: string) =>
|
||||
http.get<T.DimensionResp>(`${BASE_URL}/dimension/${id}`)
|
||||
|
||||
/* ===== 细则 ===== */
|
||||
export const getRuleList = (dimensionId: string) =>
|
||||
http.get<T.RuleResp[]>(`${BASE_URL}/rule`, { dimensionId })
|
||||
|
||||
export const addRule = (data: T.RuleAddReq) =>
|
||||
http.post<boolean>(`${BASE_URL}/rule`, data)
|
||||
|
||||
export const updateRule = (id: string, data: T.RuleUpdateReq) =>
|
||||
http.put<boolean>(`${BASE_URL}/rule/${id}`, data)
|
||||
|
||||
export const deleteRule = (id: string) =>
|
||||
http.del<boolean>(`${BASE_URL}/rule/${id}`)
|
||||
|
||||
/** 细则详情(RuleDrawer.vue 需要) */
|
||||
export const getRuleDetail = (id: string) =>
|
||||
http.get<T.RuleResp>(`${BASE_URL}/rule/${id}`)
|
||||
|
||||
/* ===== 周期 ===== */
|
||||
export const getPeriodList = () => http.get<T.PeriodResp[]>(`${BASE_URL}/period`)
|
||||
export const addPeriod = (data: T.PeriodResp) => http.post<boolean>(`${BASE_URL}/period`, data)
|
||||
export const deletePeriod = (id: string) => http.del<boolean>(`${BASE_URL}/period/${id}`)
|
||||
export const updataPeriod = (id: string, data: T.PeriodResp) => http.post<boolean>(`${BASE_URL}/period/${id}`, data)
|
||||
|
||||
/* ===== 评估 ===== */
|
||||
export const startEvaluate = (data: T.EvaluateReq) =>
|
||||
http.post<boolean>(`${BASE_URL}/evaluate`, data)
|
||||
|
||||
export const getEvaluatePage = (query?: T.EvaluateQuery) =>
|
||||
http.get<PageRes<T.EvaluateResp[]>>(`${BASE_URL}/evaluate/page`, query)
|
||||
// 同样的修改和删除评估接口
|
||||
|
||||
/** 员工查看自己的绩效 */
|
||||
export const getMyEvaluate = (query?: T.EvaluateQuery) =>
|
||||
http.get<PageRes<T.EvaluateResp[]>>(`${BASE_URL}/evaluate/my`, query)
|
||||
|
||||
/* ===== 反馈 ===== */
|
||||
export const submitFeedback = (data: T.FeedbackReq) =>
|
||||
http.post<boolean>(`${BASE_URL}/feedback`, data)
|
|
@ -1,87 +0,0 @@
|
|||
// ========== 维度 ==========
|
||||
export interface DimensionResp {
|
||||
id: string
|
||||
name: string
|
||||
desc: string
|
||||
weight: number
|
||||
status: 0 | 1
|
||||
ruleCount: number
|
||||
deptId: string
|
||||
}
|
||||
|
||||
export interface DimensionAddReq {
|
||||
name: string
|
||||
desc: string
|
||||
weight: number
|
||||
deptId: string
|
||||
}
|
||||
|
||||
export interface DimensionUpdateReq extends DimensionAddReq {
|
||||
status: 0 | 1
|
||||
}
|
||||
|
||||
// ========== 细则 ==========
|
||||
export interface RuleResp {
|
||||
id: string
|
||||
dimensionId: string
|
||||
name: string
|
||||
ruleDesc: string
|
||||
score: number
|
||||
weight: number
|
||||
isExtra: boolean
|
||||
}
|
||||
|
||||
export interface RuleAddReq {
|
||||
dimensionId: string
|
||||
name: string
|
||||
ruleDesc: string
|
||||
score: number
|
||||
weight: number
|
||||
isExtra: boolean
|
||||
}
|
||||
|
||||
export interface RuleUpdateReq extends RuleAddReq {}
|
||||
|
||||
// ========== 绩效周期 ==========
|
||||
export interface PeriodResp {
|
||||
id: string
|
||||
name: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
|
||||
// ========== 评估 ==========
|
||||
export interface EvaluateReq {
|
||||
userId: string
|
||||
dimensionId: string
|
||||
ruleId: string
|
||||
periodId: string
|
||||
}
|
||||
|
||||
export interface EvaluateResp {
|
||||
id: string // 细则id
|
||||
userId: string
|
||||
userName: string
|
||||
dimensionName: string
|
||||
ruleName: string
|
||||
periodName: string
|
||||
score: number
|
||||
aiComment: string
|
||||
status: 0 | 1 // 0待反馈 1已反馈
|
||||
}
|
||||
|
||||
// ========== 查询 ==========
|
||||
export interface EvaluateQuery {
|
||||
keyword?: string
|
||||
periodId?: string
|
||||
dimensionId?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
|
||||
// ========== 反馈 ==========
|
||||
export interface FeedbackReq {
|
||||
evaluateId: string
|
||||
level: 0 | 1 | 2
|
||||
content: string
|
||||
userId: string
|
||||
}
|
|
@ -23,9 +23,9 @@ export function deleteTaskGroup(id: number) {
|
|||
return http.del(`${BASE_URL}/group/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 查询任务列表(标准导出) */
|
||||
export const listTask = (params: any) => {
|
||||
return http.get('/project-task/list', params)
|
||||
/** @desc 查询任务列表 */
|
||||
export function listTask(query: T.TaskPageQuery) {
|
||||
return http.get<PageRes<T.TaskResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 获取任务详情 */
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import type { SalaryRecord, SalaryQuery, SalaryCreateRequest } from '@/views/salary-management/types'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/salary'
|
||||
|
||||
// 获取工资单列表
|
||||
export const getSalaryList = (query?: SalaryQuery) => {
|
||||
return http.get<PageRes<SalaryRecord[]>>(`${BASE_URL}/list`, query)
|
||||
}
|
||||
|
||||
// 获取工资单详情
|
||||
export const getSalaryDetail = (id: string) => {
|
||||
return http.get<SalaryRecord>(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
// 创建工资单
|
||||
export const createSalary = (data: SalaryCreateRequest) => {
|
||||
return http.post<SalaryRecord>(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
// 更新工资单
|
||||
export const updateSalary = (id: string, data: Partial<SalaryRecord>) => {
|
||||
return http.put<SalaryRecord>(`${BASE_URL}/${id}`, data)
|
||||
}
|
||||
|
||||
// 删除工资单
|
||||
export const deleteSalary = (id: string) => {
|
||||
return http.del<boolean>(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
// 提交审批
|
||||
export const submitApproval = (id: string) => {
|
||||
return http.post<boolean>(`${BASE_URL}/${id}/submit`)
|
||||
}
|
||||
|
||||
// 审批工资单
|
||||
export const approveSalary = (id: string, data: { status: string; comment?: string }) => {
|
||||
return http.put<boolean>(`${BASE_URL}/${id}/approve`, data)
|
||||
}
|
||||
|
||||
// 导出工资单
|
||||
export const exportSalary = (id: string, format: 'excel' | 'pdf' = 'excel') => {
|
||||
return http.download(`${BASE_URL}/${id}/export`, { format })
|
||||
}
|
||||
|
||||
// 批量导出
|
||||
export const exportSalaryBatch = (ids: string[], format: 'excel' | 'pdf' = 'excel') => {
|
||||
return http.download(`${BASE_URL}/export/batch`, { ids, format })
|
||||
}
|
||||
|
||||
// 获取实习生配置
|
||||
export const getInternConfig = () => {
|
||||
return http.get<any>(`${BASE_URL}/config/intern`)
|
||||
}
|
||||
|
||||
// 获取员工列表
|
||||
export const getEmployeeList = (type?: string) => {
|
||||
return http.get<any[]>(`${BASE_URL}/employees`, { type })
|
||||
}
|
||||
|
||||
// 获取项目列表
|
||||
export const getProjectList = () => {
|
||||
return http.get<any[]>(`${BASE_URL}/projects`)
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
<template>
|
||||
<div class="turbine-grid-container">
|
||||
<div class="turbine-grid">
|
||||
<div v-for="turbine in turbines" :key="turbine.id" class="turbine-card"
|
||||
:class="getStatusClass(turbine.status)">
|
||||
<div class="turbine-status-badge" :class="`status-${turbine.status}`">
|
||||
{{ getStatusText(turbine.status) }}
|
||||
</div>
|
||||
|
||||
<div class="turbine-icon">
|
||||
<img src="/static/images/wind-turbine-icon.svg" alt="风机图标" class="turbine-image" />
|
||||
</div>
|
||||
|
||||
<div class="turbine-info">
|
||||
<div class="turbine-number">
|
||||
<a-input v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
|
||||
@change="handleTurbineNoChange(turbine)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="turbine-actions">
|
||||
<a-button type="text" size="mini" @click="openMapModal(turbine)" title="地图选点">
|
||||
<template #icon><icon-location /></template>
|
||||
</a-button>
|
||||
<a-button type="text" size="mini" @click="editTurbine(turbine)" title="编辑">
|
||||
<template #icon><icon-edit /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加新机组按钮 -->
|
||||
<div v-if="showAddButton" class="turbine-card add-turbine-card" @click="addTurbine">
|
||||
<div class="add-icon">
|
||||
<icon-plus />
|
||||
</div>
|
||||
<div class="add-text">添加机组</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface Turbine {
|
||||
id: number
|
||||
turbineNo: string
|
||||
status: 0 | 1 | 2 // 0: 待施工, 1: 施工中, 2: 已完成
|
||||
lat?: number
|
||||
lng?: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
turbines: Turbine[]
|
||||
showAddButton?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:turbines', turbines: Turbine[]): void
|
||||
(e: 'turbine-change', turbine: Turbine): void
|
||||
(e: 'add-turbine'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showAddButton: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const getStatusText = (status: number) => {
|
||||
const statusMap = {
|
||||
0: '待施工',
|
||||
1: '施工中',
|
||||
2: '已完成'
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
}
|
||||
|
||||
const getStatusClass = (status: number) => {
|
||||
return `status-${status}`
|
||||
}
|
||||
|
||||
const handleTurbineNoChange = (turbine: Turbine) => {
|
||||
emit('turbine-change', turbine)
|
||||
emit('update:turbines', props.turbines)
|
||||
}
|
||||
|
||||
const openMapModal = (turbine: Turbine) => {
|
||||
Message.info(`地图选点功能待开发,当前机组编号:${turbine.turbineNo}`)
|
||||
}
|
||||
|
||||
const editTurbine = (turbine: Turbine) => {
|
||||
// 可以打开编辑弹窗
|
||||
Message.info(`编辑机组:${turbine.turbineNo}`)
|
||||
}
|
||||
|
||||
const addTurbine = () => {
|
||||
emit('add-turbine')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.turbine-grid-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.turbine-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.turbine-card {
|
||||
position: relative;
|
||||
background: white;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.turbine-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.turbine-card.status-0 {
|
||||
border-left: 4px solid #ff7d00;
|
||||
}
|
||||
|
||||
.turbine-card.status-1 {
|
||||
border-left: 4px solid #165dff;
|
||||
}
|
||||
|
||||
.turbine-card.status-2 {
|
||||
border-left: 4px solid #00b42a;
|
||||
}
|
||||
|
||||
.turbine-status-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.turbine-status-badge.status-0 {
|
||||
background-color: #ff7d00;
|
||||
}
|
||||
|
||||
.turbine-status-badge.status-1 {
|
||||
background-color: #165dff;
|
||||
}
|
||||
|
||||
.turbine-status-badge.status-2 {
|
||||
background-color: #00b42a;
|
||||
}
|
||||
|
||||
.turbine-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.turbine-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.turbine-info {
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.turbine-input {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.turbine-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.add-turbine-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px dashed #d9d9d9;
|
||||
background-color: #fafafa;
|
||||
color: #666;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.add-turbine-card:hover {
|
||||
border-color: #165dff;
|
||||
color: #165dff;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.turbine-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.turbine-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.turbine-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,7 +4,7 @@
|
|||
:style="appStore.menuDark ? appStore.themeCSSVar : undefined"
|
||||
>
|
||||
<a-layout-sider
|
||||
class="menu" collapsible breakpoint="xl" hide-trigger :width="250"
|
||||
class="menu" collapsible breakpoint="xl" hide-trigger :width="300"
|
||||
:collapsed="appStore.menuCollapse" @collapse="handleCollapse"
|
||||
>
|
||||
<Logo :collapsed="appStore.menuCollapse"></Logo>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
<template>
|
||||
<section class="system-logo" :class="{ collapsed: props.collapsed }" @click="toHome">
|
||||
<row>
|
||||
<img v-if="logo" class="logo" :src="logo" alt="logo" />
|
||||
<img v-else class="logo" src="/logo.svg" alt="logo" />
|
||||
</row>
|
||||
<row>
|
||||
<span class="system-name gi_line_1">{{ title }}</span>
|
||||
</row>
|
||||
<img v-if="logo" class="logo" :src="logo" alt="logo" />
|
||||
<img v-else class="logo" src="/logo.svg" alt="logo" />
|
||||
<span class="system-name gi_line_1">{{ title }}</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
@ -18,10 +14,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
})
|
||||
const appStore = useAppStore()
|
||||
// const title = computed(() => appStore.getTitle())
|
||||
const title = "数智平台"
|
||||
const logo = "/logo.png"
|
||||
//computed(() => appStore.getLogo())
|
||||
|
||||
const title = "武汉迪特聚能有限公司管理平台"
|
||||
const logo = computed(() => appStore.getLogo())
|
||||
|
||||
interface Props {
|
||||
collapsed?: boolean
|
||||
|
@ -35,46 +29,50 @@ const toHome = () => {
|
|||
|
||||
<style scoped lang="scss">
|
||||
.system-logo {
|
||||
height: 64px; // 增加高度,给上下排列留空间
|
||||
padding: 8px 12px;
|
||||
height: 56px;
|
||||
padding: 0 12px;
|
||||
color: var(--color-text-1);
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.collapsed {
|
||||
height: 56px;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
align-items: center;
|
||||
.system-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 0;
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.system-name {
|
||||
padding-left: 6px;
|
||||
white-space: nowrap;
|
||||
transition: color 0.3s;
|
||||
line-height: 1.5;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
color: $color-theme !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
max-width: 140px; // 根据你 logo 实际宽度调整
|
||||
object-fit: contain;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.system-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -57,7 +57,6 @@ declare global {
|
|||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
|
|
|
@ -61,7 +61,6 @@ declare module 'vue' {
|
|||
SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default']
|
||||
SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default']
|
||||
TextCopy: typeof import('./../components/TextCopy/index.vue')['default']
|
||||
TurbineGrid: typeof import('./../components/TurbineGrid/index.vue')['default']
|
||||
UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
|
||||
Verify: typeof import('./../components/Verify/index.vue')['default']
|
||||
VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default']
|
||||
|
|
|
@ -0,0 +1,431 @@
|
|||
<template>
|
||||
<div class="airport-management">
|
||||
<div class="page-header">
|
||||
<a-card class="general-card" title="机场管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
新建机场
|
||||
</a-button>
|
||||
</template>
|
||||
<a-row :gutter="16" style="margin-bottom: 16px">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model="searchForm.name"
|
||||
placeholder="请输入机场名称"
|
||||
@search="search"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-option value="1">运营中</a-option>
|
||||
<a-option value="0">停运</a-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-button type="primary" @click="search">
|
||||
<template #icon>
|
||||
<icon-search />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">
|
||||
<template #icon>
|
||||
<icon-refresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<div class="page-content">
|
||||
<a-card class="general-card">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === '1' ? 'green' : 'red'">
|
||||
{{ record.status === '1' ? '运营中' : '停运' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #actions="{ record }">
|
||||
<a-button type="text" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
查看详情
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
content="确定要删除这个机场吗?"
|
||||
@ok="handleDelete(record)"
|
||||
>
|
||||
<a-button type="text" size="small" status="danger">
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑机场弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
width="800px"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="机场名称" field="name">
|
||||
<a-input v-model="form.name" placeholder="请输入机场名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="机场代码" field="code">
|
||||
<a-input v-model="form.code" placeholder="请输入机场代码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="所在省份" field="province">
|
||||
<a-input v-model="form.province" placeholder="请输入所在省份" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="所在城市" field="city">
|
||||
<a-input v-model="form.city" placeholder="请输入所在城市" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="经度" field="longitude">
|
||||
<a-input-number
|
||||
v-model="form.longitude"
|
||||
placeholder="请输入经度"
|
||||
:precision="6"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="纬度" field="latitude">
|
||||
<a-input-number
|
||||
v-model="form.latitude"
|
||||
placeholder="请输入纬度"
|
||||
:precision="6"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="海拔高度(米)" field="altitude">
|
||||
<a-input-number
|
||||
v-model="form.altitude"
|
||||
placeholder="请输入海拔高度"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" field="status">
|
||||
<a-select v-model="form.status" placeholder="请选择状态">
|
||||
<a-option value="1">运营中</a-option>
|
||||
<a-option value="0">停运</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注信息"
|
||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { IconPlus, IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
name: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '机场名称',
|
||||
dataIndex: 'name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '机场代码',
|
||||
dataIndex: 'code',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '所在地区',
|
||||
dataIndex: 'location',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '经纬度',
|
||||
dataIndex: 'coordinates',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '海拔高度(米)',
|
||||
dataIndex: 'altitude',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'actions',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '北京首都国际机场',
|
||||
code: 'PEK',
|
||||
province: '北京市',
|
||||
city: '北京市',
|
||||
location: '北京市朝阳区',
|
||||
longitude: 116.584556,
|
||||
latitude: 40.080111,
|
||||
coordinates: '116.584556, 40.080111',
|
||||
altitude: 35,
|
||||
status: '1',
|
||||
remark: '中国最大的国际机场之一',
|
||||
createTime: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '上海浦东国际机场',
|
||||
code: 'PVG',
|
||||
province: '上海市',
|
||||
city: '上海市',
|
||||
location: '上海市浦东新区',
|
||||
longitude: 121.805214,
|
||||
latitude: 31.143378,
|
||||
coordinates: '121.805214, 31.143378',
|
||||
altitude: 4,
|
||||
status: '1',
|
||||
remark: '华东地区重要的国际航空枢纽',
|
||||
createTime: '2024-01-02 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '广州白云国际机场',
|
||||
code: 'CAN',
|
||||
province: '广东省',
|
||||
city: '广州市',
|
||||
location: '广州市白云区',
|
||||
longitude: 113.298889,
|
||||
latitude: 23.392436,
|
||||
coordinates: '113.298889, 23.392436',
|
||||
altitude: 15,
|
||||
status: '1',
|
||||
remark: '华南地区最大的民用机场',
|
||||
createTime: '2024-01-03 09:15:00'
|
||||
}
|
||||
])
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 3,
|
||||
showTotal: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 弹窗相关
|
||||
const modalVisible = ref(false)
|
||||
const modalTitle = ref('新增机场')
|
||||
const formRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
code: '',
|
||||
province: '',
|
||||
city: '',
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
altitude: null,
|
||||
status: '1',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入机场名称' }],
|
||||
code: [{ required: true, message: '请输入机场代码' }],
|
||||
province: [{ required: true, message: '请输入所在省份' }],
|
||||
city: [{ required: true, message: '请输入所在城市' }],
|
||||
longitude: [{ required: true, message: '请输入经度' }],
|
||||
latitude: [{ required: true, message: '请输入纬度' }],
|
||||
status: [{ required: true, message: '请选择状态' }]
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const search = () => {
|
||||
loading.value = true
|
||||
// 这里应该调用实际的API
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
Message.success('查询成功')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const reset = () => {
|
||||
searchForm.name = ''
|
||||
searchForm.status = ''
|
||||
search()
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
modalTitle.value = '新增机场'
|
||||
modalVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: any) => {
|
||||
modalTitle.value = '编辑机场'
|
||||
modalVisible.value = true
|
||||
Object.assign(form, record)
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (record: any) => {
|
||||
Message.info(`查看机场详情: ${record.name}`)
|
||||
// 这里可以跳转到详情页面或打开详情弹窗
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (record: any) => {
|
||||
Message.success(`删除机场: ${record.name}`)
|
||||
// 这里应该调用删除API
|
||||
}
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (valid) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
// 这里应该调用保存API
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
Message.success(form.id ? '更新成功' : '新增成功')
|
||||
modalVisible.value = false
|
||||
search()
|
||||
} catch (error) {
|
||||
Message.error('操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
modalVisible.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key === 'status') {
|
||||
form[key] = '1'
|
||||
} else {
|
||||
form[key] = null
|
||||
}
|
||||
})
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 分页改变
|
||||
const handlePageChange = (page: number) => {
|
||||
pagination.current = page
|
||||
search()
|
||||
}
|
||||
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.current = 1
|
||||
search()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.airport-management {
|
||||
.page-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.general-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}</style>
|
|
@ -12,14 +12,6 @@
|
|||
<a-form-item label="缺陷名称" field="defectName">
|
||||
<a-input v-model="form.defectName" placeholder="请输入缺陷名称" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 缺陷位置 -->
|
||||
<a-form-item
|
||||
field="defectPosition"
|
||||
label="缺陷位置"
|
||||
>
|
||||
<a-input v-model="form.defectPosition" placeholder="请输入缺陷位置" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 缺陷类型 -->
|
||||
<a-form-item
|
||||
|
@ -35,47 +27,19 @@
|
|||
<a-option v-for="type in defectTypes" :key="type.code" :value="type.code">{{ type.label }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!--轴向尺寸-->
|
||||
<div class="dimension-fields">
|
||||
<a-form-item
|
||||
field="axialDimension"
|
||||
label="轴向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.axialDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入轴向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<!--弦向尺寸 -->
|
||||
<a-form-item
|
||||
field="chordDimension"
|
||||
label="弦向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.chordDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入弦向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 缺陷位置 -->
|
||||
<a-form-item
|
||||
field="defectPosition"
|
||||
label="缺陷位置"
|
||||
>
|
||||
<a-input v-model="form.defectPosition" placeholder="请输入缺陷位置" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 缺陷等级 -->
|
||||
<a-form-item
|
||||
field="defectLevel"
|
||||
label="严重程度"
|
||||
label="缺陷等级"
|
||||
:rules="[{ required: true, message: '请选择缺陷等级' }]"
|
||||
>
|
||||
<a-select
|
||||
|
@ -140,8 +104,6 @@ interface DefectFormData {
|
|||
defectPosition: string
|
||||
description: string
|
||||
repairIdea: string
|
||||
axialDimension: number | 0
|
||||
chordDimension: number | 0
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -173,9 +135,7 @@ const form = reactive<DefectFormData>({
|
|||
defectLevelLabel: '',
|
||||
defectPosition: '',
|
||||
description: '',
|
||||
repairIdea: '建议进行进一步检查',
|
||||
axialDimension: 0, // 初始为null
|
||||
chordDimension: 0 // 初始为null
|
||||
repairIdea: '建议进行进一步检查'
|
||||
})
|
||||
|
||||
// 获取缺陷类型列表
|
||||
|
@ -366,7 +326,6 @@ const handleSubmit = async () => {
|
|||
}
|
||||
|
||||
// 触发提交事件
|
||||
console.log("form:",form);
|
||||
emit('submit', form, props.annotation)
|
||||
|
||||
} catch (error) {
|
||||
|
@ -475,25 +434,6 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.dimension-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.arco-input-number) {
|
||||
width: 100%;
|
||||
|
||||
.arco-input-number-step {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.arco-input-number-input {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:visible="previewModalVisible"
|
||||
title="图像详情"
|
||||
width="80%"
|
||||
:footer="footerButtons"
|
||||
:mask-closable="true"
|
||||
@update:visible="emit('update:previewModalVisible', $event)"
|
||||
:confirm-loading="loading"
|
||||
>
|
||||
<div v-if="previewImage" class="modal-image-viewer">
|
||||
<img :src="getImageUrl(previewImage.imagePath)" :alt="editingData.imageName" class="preview-image" />
|
||||
|
||||
<div class="image-details">
|
||||
<a-form layout="vertical" :model="editingData">
|
||||
<a-form-item label="图片名称" field="imageName">
|
||||
<a-input v-model="editingData.imageName" />
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="大小" field="size">
|
||||
<a-input :model-value="formatFileSize(editingData.size || 0)" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="尺寸" field="imageResolution">
|
||||
<a-input v-model="editingData.imageResolution" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="采集员ID" field="collectId">
|
||||
<a-input v-model="editingData.collectId" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="采集员姓名" field="collectorName">
|
||||
<a-input v-model="editingData.collectorName" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="来源" field="imageSource">
|
||||
<a-input v-model="editingData.imageSource" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="图片类型" field="imageType">
|
||||
<a-select v-model="editingData.imageType">
|
||||
<a-option value="DEFECT">缺陷影像</a-option>
|
||||
<a-option value="TYPICAL">典型影像</a-option>
|
||||
<a-option value="OTHER">其他影像</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="图片类型描述" field="imageTypeLabel">
|
||||
<a-textarea
|
||||
v-model="editingData.imageTypeLabel"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button @click="emit('update:previewModalVisible', false)">取消</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="handleSave">保存</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import axios from 'axios'
|
||||
|
||||
const props = defineProps<{
|
||||
previewModalVisible: boolean
|
||||
previewImage: {
|
||||
imagePath: string
|
||||
imageName: string
|
||||
size?: number
|
||||
imageResolution?: string
|
||||
collectId?: string
|
||||
collectorName?: string
|
||||
imageSource?: string
|
||||
imageType?: string
|
||||
imageTypeLabel?: string
|
||||
partId?: string
|
||||
} | null
|
||||
selectedImage: any | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:previewModalVisible': [visible: boolean]
|
||||
'save': [data: any]
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const editingData = ref<{
|
||||
imagePath: string
|
||||
imageName: string
|
||||
size?: number
|
||||
imageResolution?: string
|
||||
collectId?: string
|
||||
collectorName?: string
|
||||
imageSource?: string
|
||||
imageType?: string
|
||||
imageTypeLabel?: string
|
||||
partId?: string
|
||||
imageId?:string
|
||||
}>({
|
||||
imagePath: '',
|
||||
imageName: '',
|
||||
size: 0,
|
||||
imageResolution: '',
|
||||
collectId: '',
|
||||
collectorName: '',
|
||||
imageSource: '',
|
||||
imageType: '',
|
||||
imageTypeLabel: '',
|
||||
partId: '',
|
||||
imageId:''
|
||||
})
|
||||
|
||||
// 监听预览图片变化,初始化编辑数据
|
||||
watch(() => props.previewImage, (newVal) => {
|
||||
if (newVal) {
|
||||
editingData.value = { ...newVal }
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const getImageUrl = (imagePath: string): string => {
|
||||
if (!imagePath) return ''
|
||||
if (imagePath.startsWith('http')) return imagePath
|
||||
const baseUrl = 'http://pms.dtyx.net:9158'
|
||||
return `${baseUrl}${imagePath}`
|
||||
}
|
||||
|
||||
const formatFileSize = (size: number): string => {
|
||||
if (size < 1024) return `${size} B`
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
|
||||
if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`
|
||||
return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!editingData.value.imageName?.trim()) {
|
||||
Message.error('图片名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!editingData.value.partId) {
|
||||
Message.error('缺少部件ID,无法保存')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 构造请求数据
|
||||
const requestData = {
|
||||
collectId: editingData.value.collectId,
|
||||
collectorName: editingData.value.collectorName,
|
||||
imageSource: editingData.value.imageSource,
|
||||
imageType: editingData.value.imageType,
|
||||
imageTypeLabel: editingData.value.imageTypeLabel,
|
||||
imageList: [
|
||||
{
|
||||
imageId: editingData.value.imageId,
|
||||
imageName: editingData.value.imageName,
|
||||
imageResolution: editingData.value.imageResolution,
|
||||
}
|
||||
],
|
||||
}
|
||||
console.log("requestData:",requestData);
|
||||
|
||||
// 调用接口更新数据
|
||||
const response = await axios.post(
|
||||
`http://pms.dtyx.net:9158/image/setting-info/${editingData.value.partId}`,
|
||||
requestData
|
||||
)
|
||||
if (response.data && response.data.code === 200 && response.data.success) {
|
||||
Message.success('图片信息保存成功')
|
||||
emit('save', editingData.value)
|
||||
emit('update:previewModalVisible', false)
|
||||
} else {
|
||||
Message.error(response.data?.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存图片信息失败:', error)
|
||||
Message.error('保存失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.modal-image-viewer {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
|
||||
.preview-image {
|
||||
flex: 1;
|
||||
max-width: 60%;
|
||||
max-height: 500px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.image-details {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
|
||||
:deep(.arco-form) {
|
||||
.arco-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.modal-image-viewer {
|
||||
flex-direction: column;
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -349,7 +349,6 @@ export function useIndustrialImage() {
|
|||
}
|
||||
|
||||
const handleImagePreview = (image: any) => {
|
||||
console.log("image:",image);
|
||||
previewImage.value = image
|
||||
previewModalVisible.value = true
|
||||
}
|
||||
|
|
|
@ -145,14 +145,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 模态框 -->
|
||||
<ImageModals
|
||||
:preview-modal-visible="previewModalVisible"
|
||||
:preview-image="previewImage"
|
||||
:process-image="processImage"
|
||||
:selected-image="selectedImage"
|
||||
@update:preview-modal-visible="previewModalVisible = $event"
|
||||
@save="handleSaveImageSuccess"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -165,7 +158,6 @@ import ProjectTree from './components/ProjectTree.vue'
|
|||
import ImagePreview from './components/ImagePreview.vue'
|
||||
import ImageCanvas from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
|
||||
import IndustrialImageList from '@/components/IndustrialImageList/index.vue'
|
||||
import ImageModals from './components/ImageModals.vue'
|
||||
import RecognitionResults from './components/RecognitionResults.vue'
|
||||
import DefectListPanel from './components/DefectListPanel.vue'
|
||||
import DefectDetailsForm from './components/DefectDetailsForm.vue'
|
||||
|
@ -220,19 +212,6 @@ const loadDefectLevels = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 添加保存成功处理
|
||||
const handleSaveImageSuccess = (updatedData: any) => {
|
||||
// 可以在这里更新本地数据或执行其他操作
|
||||
Message.success('图片信息已更新')
|
||||
// 如果需要更新selectedImage或previewImage
|
||||
if (selectedImage.value && selectedImage.value.id === updatedData.id) {
|
||||
selectedImage.value = { ...selectedImage.value, ...updatedData }
|
||||
}
|
||||
if (previewImage.value && previewImage.value.id === updatedData.id) {
|
||||
previewImage.value = { ...previewImage.value, ...updatedData }
|
||||
}
|
||||
}
|
||||
|
||||
// 使用组合式函数
|
||||
const {
|
||||
// 数据
|
||||
|
@ -615,8 +594,8 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
|
|||
const defectData = {
|
||||
attachId: attachId,
|
||||
attachPath: '',
|
||||
axial: formData.axialDimension,
|
||||
chordwise: formData.chordDimension,
|
||||
// axial: 0,
|
||||
// chordwise: 0,
|
||||
// defectCode: `MANUAL_${Date.now()}`,
|
||||
defectId: '',
|
||||
defectLevel: formData.defectLevel,
|
||||
|
|
|
@ -38,7 +38,7 @@ const IconMap: Record<number, Component> = {
|
|||
const router = useRouter()
|
||||
// 返回首页
|
||||
const back = () => {
|
||||
router.replace({ path: '/asset-management/intellectual-property' })
|
||||
router.replace({ path: '/project-management/bidding/tender-documents' })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -58,7 +58,6 @@ const back = () => {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
@ -67,17 +66,14 @@ const back = () => {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
max-width: 90%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&--a {
|
||||
margin-bottom: 20px;
|
||||
font-size: 32px;
|
||||
|
@ -89,7 +85,6 @@ const back = () => {
|
|||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&--b {
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
|
@ -102,7 +97,6 @@ const back = () => {
|
|||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&--c {
|
||||
padding: 0 30px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -118,13 +112,11 @@ const back = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(60px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
<template>
|
||||
<GiPageLayout :margin="true">
|
||||
<template #header>
|
||||
<h2>新增考勤记录</h2>
|
||||
</template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<a-form-item label="打卡照片" name="recordImage">
|
||||
<a-input v-model="form.recordImage" placeholder="请输入打卡照片URL" />
|
||||
<!-- 你可以替换为图片上传组件 -->
|
||||
</a-form-item>
|
||||
<a-form-item label="打卡地点(经纬度)" name="recordPosition">
|
||||
<a-input v-model="form.recordPosition" placeholder="请输入经纬度" />
|
||||
</a-form-item>
|
||||
<a-form-item label="打卡地点(中文描述)" name="recordPositionLabel">
|
||||
<a-input v-model="form.recordPositionLabel" placeholder="请输入地点描述" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" :loading="loading">提交</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-alert v-if="resultMsg" :type="resultType" :show-icon="true" style="margin-top: 16px;">
|
||||
{{ resultMsg }}
|
||||
</a-alert>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { addAttendanceRecord } from '@/apis/attendance-record/index'
|
||||
import type { AttendanceRecordReq } from '@/apis/attendance-record/type'
|
||||
|
||||
const formRef = ref()
|
||||
const form = ref<AttendanceRecordReq>({
|
||||
recordImage: '',
|
||||
recordPosition: '',
|
||||
recordPositionLabel: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
// 可根据需要添加校验
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const resultMsg = ref('')
|
||||
const resultType = ref<'success' | 'error'>('success')
|
||||
|
||||
const onSubmit = async () => {
|
||||
loading.value = true
|
||||
resultMsg.value = ''
|
||||
try {
|
||||
const res = await addAttendanceRecord(form.value)
|
||||
if (res.success) {
|
||||
resultMsg.value = '考勤记录提交成功'
|
||||
resultType.value = 'success'
|
||||
form.value = { recordImage: '', recordPosition: '', recordPositionLabel: '' }
|
||||
} else {
|
||||
resultMsg.value = res.msg || '提交失败'
|
||||
resultType.value = 'error'
|
||||
}
|
||||
} catch (e: any) {
|
||||
resultMsg.value = e?.message || '提交异常'
|
||||
resultType.value = 'error'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,3 @@
|
|||
<!-- 考勤统计 -->
|
||||
<template>
|
||||
<GiPageLayout>
|
||||
<GiTable
|
||||
|
|
|
@ -15,15 +15,30 @@
|
|||
<a-card class="search-card" :bordered="false">
|
||||
<a-form :model="searchForm" layout="inline">
|
||||
<a-form-item label="证书名称" field="certificationName">
|
||||
<a-input v-model="searchForm.certificationName" placeholder="请输入证书名称" allow-clear style="width: 200px" />
|
||||
<a-input
|
||||
v-model="searchForm.certificationName"
|
||||
placeholder="请输入证书名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="证书类型" field="certificationType">
|
||||
<a-input v-model="searchForm.certificationType" placeholder="请输入证书类型" allow-clear style="width: 200px" />
|
||||
<a-input
|
||||
v-model="searchForm.certificationType"
|
||||
placeholder="请输入证书类型"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="用户姓名" field="userName">
|
||||
<a-input v-model="searchForm.userName" placeholder="请输入用户姓名" allow-clear style="width: 200px" />
|
||||
<a-input
|
||||
v-model="searchForm.userName"
|
||||
placeholder="请输入用户姓名"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
|
@ -47,15 +62,28 @@
|
|||
|
||||
<!-- 人员资质表格 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<a-table :columns="columns" :data="certificationList" :pagination="paginationConfig" :loading="loading"
|
||||
row-key="certificationId" @page-change="handlePageChange">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="certificationList"
|
||||
:pagination="paginationConfig"
|
||||
:loading="loading"
|
||||
row-key="certificationId"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<template #userName="{ record }">
|
||||
<span>{{ getUserName(record.userId) }}</span>
|
||||
</template>
|
||||
|
||||
<template #certificationImage="{ record }">
|
||||
<a-image v-if="record.certificationImage" :src="record.certificationImage" width="60" height="40"
|
||||
fit="cover" show-loader preview />
|
||||
<a-image
|
||||
v-if="record.certificationImage"
|
||||
:src="record.certificationImage"
|
||||
width="60"
|
||||
height="40"
|
||||
fit="cover"
|
||||
show-loader
|
||||
preview
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
|
@ -77,9 +105,19 @@
|
|||
</a-card>
|
||||
|
||||
<!-- 新增/编辑资质信息模态框 -->
|
||||
<a-modal v-model:visible="modalVisible" :title="isEdit ? '编辑资质信息' : '新增资质信息'" width="700px" @ok="handleSubmit"
|
||||
@cancel="handleCancel">
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="isEdit ? '编辑资质信息' : '新增资质信息'"
|
||||
width="700px"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证书编号" field="certificationCode">
|
||||
|
@ -94,47 +132,75 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="证书类型" field="certificationType">
|
||||
<a-select v-model="formData.certificationType" placeholder="请选择证书类型">
|
||||
<a-option v-for="item in CERTIFICATION_TYPE_OPTIONS" :key="item.value" :value="item.value"
|
||||
:label="item.label">
|
||||
{{ item.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证书类型" field="certificationType">
|
||||
<a-input v-model="formData.certificationType" placeholder="请输入证书类型" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-form-item label="持证人" field="userId">
|
||||
<a-select v-model="formData.userId" placeholder="请选择持证人" :loading="loadingUsers" allow-search
|
||||
:filter-option="filterUserOption" @="console.log('d', formData)">
|
||||
<a-option v-for="user in userList" :key="user.userId" :value="user.userId" :label="user.name">
|
||||
{{ user.name }}({{ user.account }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="持证人" field="userId">
|
||||
<a-select
|
||||
v-model="formData.userId"
|
||||
placeholder="请选择持证人"
|
||||
:loading="loadingUsers"
|
||||
allow-search
|
||||
:filter-option="filterUserOption"
|
||||
>
|
||||
<a-option
|
||||
v-for="user in userList"
|
||||
:key="user.userId"
|
||||
:value="user.userId"
|
||||
:label="user.name"
|
||||
>
|
||||
{{ user.name }}({{ user.account }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="有效期开始" field="validityDateBegin">
|
||||
<a-date-picker v-model="formData.validityDateBegin" placeholder="请选择有效期开始日期" style="width: 100%" />
|
||||
<a-date-picker
|
||||
v-model="formData.validityDateBegin"
|
||||
placeholder="请选择有效期开始日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-form-item label="有效期结束" field="validityDateEnd">
|
||||
<a-date-picker v-model="formData.validityDateEnd" placeholder="请选择有效期结束日期" style="width: 100%" />
|
||||
<a-date-picker
|
||||
v-model="formData.validityDateEnd"
|
||||
placeholder="请选择有效期结束日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="证书图片" field="certificationImage">
|
||||
<a-upload :custom-request="handleUpload" :show-file-list="false" accept="image/*"
|
||||
:before-upload="beforeUpload">
|
||||
<a-upload
|
||||
:custom-request="handleUpload"
|
||||
:show-file-list="false"
|
||||
accept="image/*"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<template #upload-button>
|
||||
<div class="upload-wrapper">
|
||||
<a-image v-if="formData.certificationImage" :src="formData.certificationImage" width="200"
|
||||
height="120" fit="cover" show-loader preview />
|
||||
<a-image
|
||||
v-if="formData.certificationImage"
|
||||
:src="formData.certificationImage"
|
||||
width="200"
|
||||
height="120"
|
||||
fit="cover"
|
||||
show-loader
|
||||
preview
|
||||
/>
|
||||
<div v-else class="upload-placeholder">
|
||||
<icon-plus />
|
||||
<div>点击上传证书图片</div>
|
||||
|
@ -167,7 +233,7 @@ const isEdit = ref(false)
|
|||
const formRef = ref()
|
||||
|
||||
// 列表数据
|
||||
const certificationList = ref<CertificationAPI.CertificationListResponse>()
|
||||
const certificationList = ref<CertificationInfo[]>([])
|
||||
const userList = ref<SimpleUserInfo[]>([])
|
||||
|
||||
// 搜索表单
|
||||
|
@ -188,14 +254,14 @@ const paginationConfig = reactive({
|
|||
|
||||
// 表单数据
|
||||
const formData = reactive<CertificationInfo>({
|
||||
certificationId: '',
|
||||
certificationCode: '',
|
||||
certificationImage: 'n',
|
||||
certificationImage: '',
|
||||
certificationName: '',
|
||||
certificationType: '',
|
||||
userId: '',
|
||||
validityDateBegin: '',
|
||||
validityDateEnd: '',
|
||||
// certificationTypeLabel: ''
|
||||
validityDateEnd: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
|
@ -219,15 +285,7 @@ const formRules = {
|
|||
{ required: true, message: '请选择有效期结束日期' }
|
||||
]
|
||||
}
|
||||
const CERTIFICATION_TYPE_OPTIONS = [
|
||||
{ label: '高处作业', value: 'height-operation' },
|
||||
{ label: '低压电工', value: 'low-voltage-operation' },
|
||||
{ label: '高压电工', value: 'high-voltage-operation' },
|
||||
{ label: '海上交通安全', value: 'maritime-traffic-safety' },
|
||||
{ label: '驾驶证', value: 'driving-license' },
|
||||
{ label: '防雷检测', value: 'lightning-protection-detection' },
|
||||
{ label: '无人机驾驶', value: 'drone-driving' }
|
||||
]
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
|
@ -243,15 +301,11 @@ const columns = [
|
|||
{
|
||||
title: '证书类型',
|
||||
dataIndex: 'certificationType',
|
||||
width: 150,
|
||||
render: ({ record }) => {
|
||||
const type = CERTIFICATION_TYPE_OPTIONS.find(item => item.value === record.certificationType)
|
||||
return type ? type.label : '-'
|
||||
}
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '持证人',
|
||||
dataIndex: 'userName',
|
||||
dataIndex: 'userId',
|
||||
slotName: 'userName',
|
||||
width: 120
|
||||
},
|
||||
|
@ -285,10 +339,10 @@ const getUserName = (userId: string) => {
|
|||
const filterUserOption = (inputValue: string, option: any) => {
|
||||
const user = userList.value.find(u => u.userId === option.value)
|
||||
if (!user) return false
|
||||
|
||||
|
||||
const searchText = inputValue.toLowerCase()
|
||||
return user.name.toLowerCase().includes(searchText) ||
|
||||
user.account.toLowerCase().includes(searchText)
|
||||
return user.name.toLowerCase().includes(searchText) ||
|
||||
user.account.toLowerCase().includes(searchText)
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
|
@ -296,16 +350,11 @@ const getUserList = async () => {
|
|||
try {
|
||||
loadingUsers.value = true
|
||||
const response = await CertificationAPI.getUserList()
|
||||
|
||||
|
||||
console.log('用户列表响应:', response)
|
||||
|
||||
|
||||
if (response.data) {
|
||||
userList.value = response.data.map(item => ({
|
||||
userId: item.userId,
|
||||
name: item.name,
|
||||
account: item.account,
|
||||
})) || []
|
||||
console.log('用户列表响应2:', userList.value)
|
||||
userList.value = response.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
|
@ -321,15 +370,20 @@ const getCertificationList = async () => {
|
|||
loading.value = true
|
||||
const params: CertificationListParams = {
|
||||
...searchForm,
|
||||
current: paginationConfig.current,
|
||||
size: paginationConfig.pageSize
|
||||
}
|
||||
|
||||
const response = await CertificationAPI.getCertificationList(params)
|
||||
|
||||
console.log('资质信息列表响应:', response)
|
||||
|
||||
if (response.data) {
|
||||
certificationList.value = response.data || []
|
||||
certificationList.value = response.data.records || []
|
||||
paginationConfig.total = response.data.total || 0
|
||||
console.log('cc', certificationList.value)
|
||||
console.log('cd', response.data[0])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取资质信息列表失败:', error)
|
||||
Message.error('获取资质信息列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
@ -343,13 +397,13 @@ const beforeUpload = (file: File) => {
|
|||
Message.error('只能上传图片文件')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
if (!isLt2M) {
|
||||
Message.error('图片大小不能超过 2MB')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -358,9 +412,9 @@ const handleUpload = async (option: any) => {
|
|||
try {
|
||||
const uploadFormData = new FormData()
|
||||
uploadFormData.append('file', option.fileItem.file)
|
||||
|
||||
|
||||
const response = await uploadFile(uploadFormData)
|
||||
|
||||
|
||||
if (response.data && response.data.url) {
|
||||
formData.certificationImage = response.data.url
|
||||
Message.success('图片上传成功')
|
||||
|
@ -437,7 +491,7 @@ const deleteRecord = (record: CertificationInfo) => {
|
|||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
|
||||
|
||||
if (isEdit.value) {
|
||||
await CertificationAPI.updateCertification(formData.certificationId!, formData)
|
||||
Message.success('更新成功')
|
||||
|
@ -445,7 +499,7 @@ const handleSubmit = async () => {
|
|||
await CertificationAPI.createCertification(formData)
|
||||
Message.success('创建成功')
|
||||
}
|
||||
|
||||
|
||||
modalVisible.value = false
|
||||
resetForm()
|
||||
await getCertificationList()
|
||||
|
@ -544,4 +598,4 @@ onMounted(() => {
|
|||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -1,10 +1,8 @@
|
|||
<!--任务管理-->
|
||||
<template>
|
||||
<GiPageLayout>
|
||||
<a-button type="primary" style="margin-bottom: 16px" @click="openAddModal">发布任务</a-button>
|
||||
<GiTable
|
||||
row-key="taskId"
|
||||
title="任务记录"
|
||||
row-key="id"
|
||||
title="工作量管理"
|
||||
:data="dataList"
|
||||
:columns="tableColumns"
|
||||
:loading="loading"
|
||||
|
@ -13,74 +11,38 @@
|
|||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
@refresh="search"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<template #top>
|
||||
<GiForm
|
||||
v-model="searchForm"
|
||||
search
|
||||
:columns="queryFormColumns"
|
||||
size="medium"
|
||||
@search="search"
|
||||
<GiForm
|
||||
v-model="searchForm"
|
||||
search
|
||||
:columns="queryFormColumns"
|
||||
size="medium"
|
||||
@search="search"
|
||||
@reset="reset"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getTaskStatusColor(record.status)">{{ getTaskStatusText(record.status) }}</a-tag>
|
||||
|
||||
<template #toolbar-left>
|
||||
<a-button type="primary" @click="openAddModal">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增工作量记录</template>
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 工作量显示 -->
|
||||
<template #workload="{ record }">
|
||||
<span class="font-medium text-blue-600">{{ record.workload }}小时</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click.stop="onRowClick(record)">详情</a-link>
|
||||
<a-link status="danger" @click.stop="deleteRecord(record)">删除</a-link>
|
||||
<a-link @click="editRecord(record)">编辑</a-link>
|
||||
<a-link status="danger" @click="deleteRecord(record)">删除</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
<!-- 发布任务弹窗 -->
|
||||
<a-modal v-model:visible="showAddModal" title="发布任务" @ok="handleAddTask" @cancel="showAddModal = false">
|
||||
<a-form :model="addForm" label-width="90px">
|
||||
<a-form-item label="任务ID">
|
||||
<a-input v-model="addForm.taskId" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务描述">
|
||||
<a-input v-model="addForm.remark" placeholder="请输入任务描述" />
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<a-input v-model="addForm.createTime" disabled />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<!-- 任务详情弹窗 -->
|
||||
<a-modal v-model:visible="showDetailModal" title="任务详情" @ok="handleUpdateTask" @cancel="showDetailModal = false" :footer="false">
|
||||
<a-form :model="detailForm" label-width="90px">
|
||||
<a-form-item label="任务ID">
|
||||
<a-input v-model="detailForm.taskId" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务描述">
|
||||
<a-input v-model="detailForm.remark" />
|
||||
</a-form-item>
|
||||
<a-form-item label="分配岗位">
|
||||
<a-select v-model="detailForm.deptName" :options="deptOptions" allow-clear placeholder="请选择岗位" />
|
||||
</a-form-item>
|
||||
<a-form-item label="分配人员">
|
||||
<a-select v-model="detailForm.mainUserId" :options="userOptions" allow-clear placeholder="请选择人员" />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务状态">
|
||||
<a-input :value="getTaskStatusText(detailForm.status)" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<a-input v-model="detailForm.createTime" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="完成时间">
|
||||
<a-input v-model="detailForm.finishTime" disabled />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-space style="float: right">
|
||||
<a-button status="danger" @click="handleDeleteTask">删除</a-button>
|
||||
<a-button type="primary" @click="handleUpdateTask">确定</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
|
@ -88,52 +50,13 @@
|
|||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
// import { listTask } from '@/apis/project/task' // 接口调用处注释
|
||||
|
||||
// 任务状态映射
|
||||
// 0未分配,1已分配,2已完成,3已取消
|
||||
const statusOptions = [
|
||||
{ label: '未分配', value: 0 },
|
||||
{ label: '已分配', value: 1 },
|
||||
{ label: '已完成', value: 2 },
|
||||
{ label: '已取消', value: 3 }
|
||||
]
|
||||
const getTaskStatusColor = (status: number) => {
|
||||
const colorMap: Record<number, string> = {
|
||||
0: 'gray',
|
||||
1: 'blue',
|
||||
2: 'green',
|
||||
3: 'red'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
const getTaskStatusText = (status: number) => {
|
||||
const textMap: Record<number, string> = {
|
||||
0: '未分配',
|
||||
1: '已分配',
|
||||
2: '已完成',
|
||||
3: '已取消'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 示例岗位和人员
|
||||
const deptOptions = [
|
||||
{ label: '开发', value: '开发' },
|
||||
{ label: '测试', value: '测试' },
|
||||
{ label: '运维', value: '运维' }
|
||||
]
|
||||
const userOptions = [
|
||||
{ label: '张三', value: '张三' },
|
||||
{ label: '李四', value: '李四' },
|
||||
{ label: '王五', value: '王五' }
|
||||
]
|
||||
|
||||
// 搜索表单
|
||||
let searchForm = reactive({
|
||||
taskName: '',
|
||||
responsiblePerson: '', // mainUserId
|
||||
status: '',
|
||||
userName: '',
|
||||
projectName: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
page: 1,
|
||||
size: 10
|
||||
})
|
||||
|
@ -141,241 +64,119 @@ let searchForm = reactive({
|
|||
// 查询条件配置
|
||||
const queryFormColumns = [
|
||||
{
|
||||
field: 'taskName',
|
||||
label: '任务标题',
|
||||
field: 'userName',
|
||||
label: '员工姓名',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入任务标题',
|
||||
style: { width: '200px' }
|
||||
placeholder: '请输入员工姓名'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'responsiblePerson',
|
||||
label: '分配用户',
|
||||
field: 'projectName',
|
||||
label: '项目名称',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入分配用户',
|
||||
style: { width: '200px' }
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '任务状态',
|
||||
type: 'select' as const,
|
||||
props: {
|
||||
placeholder: '请选择任务状态',
|
||||
options: statusOptions,
|
||||
style: { width: '200px' }
|
||||
placeholder: '请输入项目名称'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns: TableColumnData[] = [
|
||||
{ title: '任务ID', dataIndex: 'taskId', width: 80 },
|
||||
{ title: '任务标题', dataIndex: 'taskName', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '分配用户', dataIndex: 'mainUserId', width: 120 },
|
||||
{ title: '分配部门', dataIndex: 'deptName', width: 120 },
|
||||
{ title: '任务内容', dataIndex: 'remark', width: 250, ellipsis: true, tooltip: true },
|
||||
{ title: '任务状态', dataIndex: 'status', slotName: 'status', width: 120 },
|
||||
{ title: '员工姓名', dataIndex: 'userName', width: 120 },
|
||||
{ title: '部门', dataIndex: 'deptName', width: 120 },
|
||||
{ title: '项目名称', dataIndex: 'projectName', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '工作内容', dataIndex: 'workContent', width: 250, ellipsis: true, tooltip: true },
|
||||
{ title: '工作量(小时)', dataIndex: 'workload', slotName: 'workload', width: 120 },
|
||||
{ title: '工作日期', dataIndex: 'workDate', width: 120 },
|
||||
{ title: '创建时间', dataIndex: 'createTime', width: 160 },
|
||||
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false)
|
||||
const dataList = ref<any[]>([])
|
||||
const allData = ref<any[]>([])
|
||||
const dataList = ref([
|
||||
{
|
||||
id: 1,
|
||||
userName: '张三',
|
||||
deptName: '技术部',
|
||||
projectName: '企业管理系统',
|
||||
workContent: '前端开发',
|
||||
workload: 8,
|
||||
workDate: '2024-01-15',
|
||||
createTime: '2024-01-15 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userName: '李四',
|
||||
deptName: '技术部',
|
||||
projectName: '移动端应用',
|
||||
workContent: '后端接口开发',
|
||||
workload: 6,
|
||||
workDate: '2024-01-15',
|
||||
createTime: '2024-01-15 11:20:00'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
total: 2,
|
||||
showTotal: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 发布任务弹窗
|
||||
const showAddModal = ref(false)
|
||||
const addForm = reactive({
|
||||
taskId: '',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
})
|
||||
const openAddModal = () => {
|
||||
addForm.taskId = Date.now().toString()
|
||||
addForm.remark = ''
|
||||
addForm.createTime = new Date().toLocaleString()
|
||||
showAddModal.value = true
|
||||
}
|
||||
const handleAddTask = () => {
|
||||
if (!addForm.remark) {
|
||||
Message.warning('请输入任务描述')
|
||||
return
|
||||
}
|
||||
const newTask = {
|
||||
taskId: addForm.taskId,
|
||||
taskName: addForm.remark,
|
||||
mainUserId: '',
|
||||
deptName: '',
|
||||
remark: addForm.remark,
|
||||
status: 0, // 未分配
|
||||
createTime: addForm.createTime,
|
||||
finishTime: ''
|
||||
}
|
||||
allData.value.unshift(newTask)
|
||||
filterAndPaginate()
|
||||
showAddModal.value = false
|
||||
Message.success('任务发布成功')
|
||||
}
|
||||
|
||||
// 任务详情弹窗
|
||||
const showDetailModal = ref(false)
|
||||
const detailForm = reactive({
|
||||
taskId: '',
|
||||
taskName: '',
|
||||
mainUserId: '',
|
||||
deptName: '',
|
||||
remark: '',
|
||||
status: 0,
|
||||
createTime: '',
|
||||
finishTime: ''
|
||||
})
|
||||
let detailIndex = -1
|
||||
const onRowClick = (record: any) => {
|
||||
Object.assign(detailForm, record)
|
||||
detailIndex = allData.value.findIndex(item => item.taskId === record.taskId)
|
||||
showDetailModal.value = true
|
||||
}
|
||||
const handleUpdateTask = () => {
|
||||
if (detailIndex !== -1) {
|
||||
allData.value[detailIndex] = { ...detailForm }
|
||||
filterAndPaginate()
|
||||
showDetailModal.value = false
|
||||
Message.success('任务更新成功')
|
||||
}
|
||||
}
|
||||
const handleDeleteTask = () => {
|
||||
if (detailIndex !== -1) {
|
||||
allData.value.splice(detailIndex, 1)
|
||||
filterAndPaginate()
|
||||
showDetailModal.value = false
|
||||
Message.success('任务已删除')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取任务数据(用示例数据,接口调用处注释)
|
||||
const fetchTaskList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// const res = await listTask(params)
|
||||
// const rows = res?.rows || res?.data?.rows || []
|
||||
// allData.value = rows
|
||||
// 示例数据:
|
||||
allData.value = [
|
||||
{
|
||||
taskId: '1710000000000',
|
||||
taskName: '示例任务A',
|
||||
mainUserId: '张三',
|
||||
deptName: '开发',
|
||||
remark: '开发新功能',
|
||||
status: 1,
|
||||
createTime: '2024-05-01 10:00:00',
|
||||
finishTime: ''
|
||||
},
|
||||
{
|
||||
taskId: '1710000000001',
|
||||
taskName: '示例任务B',
|
||||
mainUserId: '',
|
||||
deptName: '',
|
||||
remark: '待分配任务',
|
||||
status: 0,
|
||||
createTime: '2024-05-02 11:00:00',
|
||||
finishTime: ''
|
||||
},
|
||||
{
|
||||
taskId: '1710000000002',
|
||||
taskName: '示例任务C',
|
||||
mainUserId: '李四',
|
||||
deptName: '测试',
|
||||
remark: '测试任务',
|
||||
status: 2,
|
||||
createTime: '2024-05-03 09:00:00',
|
||||
finishTime: '2024-05-05 18:00:00'
|
||||
}
|
||||
]
|
||||
filterAndPaginate()
|
||||
pagination.total = allData.value.length
|
||||
} catch (e) {
|
||||
Message.error('获取任务数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 前端筛选和分页
|
||||
const filterAndPaginate = () => {
|
||||
let filtered = allData.value
|
||||
if (searchForm.taskName) {
|
||||
filtered = filtered.filter((item: any) =>
|
||||
item.taskName?.toLowerCase().includes(searchForm.taskName.toLowerCase())
|
||||
)
|
||||
}
|
||||
if (searchForm.responsiblePerson) {
|
||||
filtered = filtered.filter((item: any) =>
|
||||
item.mainUserId?.toLowerCase().includes(searchForm.responsiblePerson.toLowerCase())
|
||||
)
|
||||
}
|
||||
if (searchForm.status !== '' && searchForm.status !== undefined) {
|
||||
filtered = filtered.filter((item: any) => String(item.status) === String(searchForm.status))
|
||||
}
|
||||
// 分页
|
||||
const start = (pagination.current - 1) * pagination.pageSize
|
||||
const end = start + pagination.pageSize
|
||||
dataList.value = filtered.slice(start, end)
|
||||
pagination.total = filtered.length
|
||||
}
|
||||
|
||||
// 搜索和重置
|
||||
const search = async () => {
|
||||
pagination.current = 1
|
||||
filterAndPaginate()
|
||||
loading.value = true
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const reset = async () => {
|
||||
const reset = () => {
|
||||
Object.assign(searchForm, {
|
||||
taskName: '',
|
||||
responsiblePerson: '',
|
||||
status: '',
|
||||
userName: '',
|
||||
projectName: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
page: 1,
|
||||
size: 10
|
||||
})
|
||||
pagination.current = 1
|
||||
filterAndPaginate()
|
||||
search()
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const onPageChange = (page: number) => {
|
||||
searchForm.page = page
|
||||
pagination.current = page
|
||||
filterAndPaginate()
|
||||
search()
|
||||
}
|
||||
|
||||
const onPageSizeChange = (size: number) => {
|
||||
searchForm.size = size
|
||||
searchForm.page = 1
|
||||
pagination.pageSize = size
|
||||
pagination.current = 1
|
||||
filterAndPaginate()
|
||||
search()
|
||||
}
|
||||
|
||||
// 操作方法
|
||||
const openAddModal = () => {
|
||||
Message.info('新增工作量记录功能开发中...')
|
||||
}
|
||||
|
||||
const editRecord = (record: any) => {
|
||||
Message.info(`编辑工作量记录: ${record.userName}`)
|
||||
}
|
||||
|
||||
// 删除操作(表格操作列)
|
||||
const deleteRecord = (record: any) => {
|
||||
const idx = allData.value.findIndex(item => item.taskId === record.taskId)
|
||||
if (idx !== -1) {
|
||||
allData.value.splice(idx, 1)
|
||||
filterAndPaginate()
|
||||
Message.success('任务已删除')
|
||||
}
|
||||
Message.info(`删除工作量记录: ${record.userName}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTaskList()
|
||||
search()
|
||||
})
|
||||
</script>
|
|
@ -1,6 +1,13 @@
|
|||
<template>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" :label-col-style="{ display: 'none' }"
|
||||
:wrapper-col-style="{ flex: 1 }" size="large" @submit="handleLogin">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col-style="{ display: 'none' }"
|
||||
:wrapper-col-style="{ flex: 1 }"
|
||||
size="large"
|
||||
@submit="handleLogin"
|
||||
>
|
||||
<a-form-item field="account" hide-label>
|
||||
<a-input v-model="form.account" placeholder="请输入用户名" allow-clear />
|
||||
</a-form-item>
|
||||
|
@ -101,7 +108,7 @@ const handleLogin = async () => {
|
|||
const { rememberMe } = loginConfig.value
|
||||
loginConfig.value.account = rememberMe ? form.account : ''
|
||||
await router.push({
|
||||
path: (redirect as string) || '/asset-management/intellectual-property',
|
||||
path: (redirect as string) || '/project-management/projects/initiation',
|
||||
query: {
|
||||
...othersQuery,
|
||||
},
|
||||
|
|
|
@ -1,335 +0,0 @@
|
|||
<template>
|
||||
<!-- 图片预览模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="previewModalVisible"
|
||||
:width="1000"
|
||||
:footer="false"
|
||||
title="多媒体预览"
|
||||
class="preview-modal"
|
||||
@close="handleModalClose"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<!-- 图片预览区域 -->
|
||||
<div class="image-section">
|
||||
<img
|
||||
:src="currentPreviewItem?.url"
|
||||
:alt="currentPreviewItem?.name"
|
||||
class="preview-image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 音频列表区域 -->
|
||||
<div class="audio-section">
|
||||
<h3 class="audio-title">关联音频 ({{ audioList.length }})</h3>
|
||||
|
||||
<div class="audio-list">
|
||||
<div
|
||||
v-for="(audio, index) in audioList"
|
||||
:key="index"
|
||||
class="audio-item"
|
||||
:class="{ 'active': currentAudioIndex === index }"
|
||||
>
|
||||
<div class="audio-info">
|
||||
<span class="audio-name">{{ audio.audioId || `音频 ${index + 1}` }}</span>
|
||||
<span class="audio-duration">{{ formatDuration(audio.duration) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="audio-controls">
|
||||
<a-button
|
||||
size="mini"
|
||||
@click="toggleAudio(index)"
|
||||
:loading="audioLoading && currentAudioIndex === index"
|
||||
>
|
||||
{{ currentAudioIndex === index && isPlaying ? '暂停' : '播放' }}
|
||||
</a-button>
|
||||
|
||||
<a-progress
|
||||
v-if="currentAudioIndex === index"
|
||||
:percent="playProgress"
|
||||
:show-text="false"
|
||||
size="small"
|
||||
class="progress-bar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的audio元素 -->
|
||||
<audio
|
||||
ref="audioPlayers"
|
||||
:src="audio.url"
|
||||
@timeupdate="handleTimeUpdate"
|
||||
@ended="handleAudioEnded"
|
||||
preload="metadata"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface AudioItem {
|
||||
url: string
|
||||
name?: string
|
||||
duration?: number // 单位秒
|
||||
}
|
||||
|
||||
interface PreviewItem {
|
||||
url: string
|
||||
name: string
|
||||
audios?: AudioItem[]
|
||||
}
|
||||
|
||||
// 模态框显示状态
|
||||
const previewModalVisible = ref(false)
|
||||
const currentPreviewItem = ref<PreviewItem | null>(null)
|
||||
|
||||
// 音频相关状态
|
||||
const audioList = ref<AudioItem[]>([])
|
||||
const currentAudioIndex = ref(-1)
|
||||
const isPlaying = ref(false)
|
||||
const audioLoading = ref(false)
|
||||
const playProgress = ref(0)
|
||||
const audioPlayers = ref<HTMLAudioElement[]>([])
|
||||
|
||||
const getAudioUrl = (filePath: string): string => {
|
||||
if (!filePath) return ''
|
||||
if (filePath.startsWith('http')) return filePath
|
||||
const baseUrl = 'http://pms.dtyx.net:9158'
|
||||
return `${baseUrl}${filePath}`
|
||||
}
|
||||
|
||||
// 打开预览模态框
|
||||
const openPreview = (item: PreviewItem) => {
|
||||
currentPreviewItem.value = item
|
||||
audioList.value = []
|
||||
for (const audio of item.audios) {
|
||||
let temp={
|
||||
audioId:audio.audioId,
|
||||
url:getAudioUrl(audio.filePath)
|
||||
}
|
||||
audioList.value.push(temp)
|
||||
}
|
||||
|
||||
previewModalVisible.value = true
|
||||
resetAudioState()
|
||||
|
||||
// 预加载音频时长
|
||||
nextTick(() => {
|
||||
loadAudioDurations()
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
const handleModalClose = () => {
|
||||
stopAudio()
|
||||
previewModalVisible.value = false
|
||||
}
|
||||
|
||||
// 重置音频状态
|
||||
const resetAudioState = () => {
|
||||
currentAudioIndex.value = -1
|
||||
isPlaying.value = false
|
||||
playProgress.value = 0
|
||||
}
|
||||
|
||||
// 加载音频时长信息
|
||||
const loadAudioDurations = () => {
|
||||
audioPlayers.value.forEach((player, index) => {
|
||||
player.onloadedmetadata = () => {
|
||||
if (!audioList.value[index].duration) {
|
||||
audioList.value[index].duration = player.duration
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换音频播放状态
|
||||
const toggleAudio = (index: number) => {
|
||||
if (currentAudioIndex.value === index) {
|
||||
// 同一音频,切换播放/暂停
|
||||
if (isPlaying.value) {
|
||||
pauseAudio()
|
||||
} else {
|
||||
playAudio(index)
|
||||
}
|
||||
} else {
|
||||
// 切换不同音频
|
||||
stopAudio()
|
||||
playAudio(index)
|
||||
}
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
const playAudio = (index: number) => {
|
||||
audioLoading.value = true
|
||||
currentAudioIndex.value = index
|
||||
|
||||
const player = audioPlayers.value[index]
|
||||
player.play()
|
||||
.then(() => {
|
||||
isPlaying.value = true
|
||||
audioLoading.value = false
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('播放失败:', err)
|
||||
Message.error('音频播放失败')
|
||||
audioLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 暂停音频
|
||||
const pauseAudio = () => {
|
||||
const player = audioPlayers.value[currentAudioIndex.value]
|
||||
player.pause()
|
||||
isPlaying.value = false
|
||||
}
|
||||
|
||||
// 停止音频
|
||||
const stopAudio = () => {
|
||||
if (currentAudioIndex.value !== -1) {
|
||||
const player = audioPlayers.value[currentAudioIndex.value]
|
||||
player.pause()
|
||||
player.currentTime = 0
|
||||
}
|
||||
isPlaying.value = false
|
||||
currentAudioIndex.value = -1
|
||||
playProgress.value = 0
|
||||
}
|
||||
|
||||
// 音频时间更新
|
||||
const handleTimeUpdate = (e: Event) => {
|
||||
const player = e.target as HTMLAudioElement
|
||||
if (player.duration) {
|
||||
playProgress.value = (player.currentTime / player.duration) * 100
|
||||
}
|
||||
}
|
||||
|
||||
// 音频播放结束
|
||||
const handleAudioEnded = () => {
|
||||
isPlaying.value = false
|
||||
playProgress.value = 100
|
||||
}
|
||||
|
||||
// 格式化时长显示
|
||||
const formatDuration = (seconds?: number) => {
|
||||
if (!seconds) return '--:--'
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = Math.floor(seconds % 60)
|
||||
return `${mins}:${secs < 10 ? '0' : ''}${secs}`
|
||||
}
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
openPreview
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.preview-modal {
|
||||
:deep(.arco-modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
display: flex;
|
||||
height: 70vh;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-section {
|
||||
width: 350px;
|
||||
border-left: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.audio-title {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.audio-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.audio-item {
|
||||
padding: 12px 16px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&.active {
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.audio-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.audio-duration {
|
||||
color: #8c8c8c;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.preview-container {
|
||||
flex-direction: column;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
.audio-section {
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
border-left: none;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,290 +1,151 @@
|
|||
<template>
|
||||
<GiPageLayout>
|
||||
<!-- 页面标题 -->
|
||||
<!-- <div class="page-header">
|
||||
<h2 class="page-title">图像音频关联查看</h2>
|
||||
</div>-->
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">数据入库 - 1号机组叶片A型检查</h2>
|
||||
</div>
|
||||
|
||||
<!-- 选项卡区域 -->
|
||||
<div class="tabs-section">
|
||||
<a-tabs v-model:active-key="activeTab" type="line">
|
||||
<a-tab-pane key="image" tab="列表" title="列表">
|
||||
<div class="tab-content">
|
||||
<!-- 选项卡区域 -->
|
||||
<div class="tabs-section">
|
||||
<a-tabs v-model:active-key="activeTab" type="line">
|
||||
<a-tab-pane key="image" tab="图片" title="图片">
|
||||
<div class="tab-content">
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="upload-section">
|
||||
<a-upload
|
||||
:custom-request="customUpload"
|
||||
:show-file-list="false"
|
||||
multiple
|
||||
:accept="getAcceptType()"
|
||||
@change="handleFileChange"
|
||||
drag
|
||||
class="upload-dragger"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon">
|
||||
<icon-upload :size="48" />
|
||||
</div>
|
||||
<div class="upload-text">
|
||||
<p class="primary-text">将文件拖拽到此处,或<span class="link-text">点击上传</span></p>
|
||||
<p class="secondary-text">支持上传任意(不超过100MB)格式文件</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
|
||||
<!-- 筛选条件区域 -->
|
||||
<div class="filter-section">
|
||||
<a-space size="large">
|
||||
<!-- 项目选择 -->
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">项目:</span>
|
||||
<a-select
|
||||
v-model="filterParams.project"
|
||||
placeholder="请选择项目"
|
||||
:options="projectOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="loading.project"
|
||||
style="width: 200px"
|
||||
@change="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- 操作按钮区域 -->
|
||||
<div class="action-buttons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="startUpload"
|
||||
:loading="uploading"
|
||||
:disabled="uploadQueue.length === 0"
|
||||
>
|
||||
开始上传
|
||||
</a-button>
|
||||
<a-button @click="clearFiles">清空文件</a-button>
|
||||
<a-button @click="batchImport">批量导入...</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 机组选择 -->
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">机组:</span>
|
||||
<a-select
|
||||
v-model="filterParams.unit"
|
||||
placeholder="请先选择项目"
|
||||
:options="unitOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:disabled="!filterParams.project"
|
||||
:loading="loading.unit"
|
||||
style="width: 200px"
|
||||
@change="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 部件选择 -->
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">部件:</span>
|
||||
<a-select
|
||||
v-model="filterParams.component"
|
||||
placeholder="请先选择机组"
|
||||
:options="componentOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:disabled="!filterParams.unit"
|
||||
:loading="loading.component"
|
||||
style="width: 200px"
|
||||
@change="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 已上传数据列表 -->
|
||||
<div class="uploaded-files-section">
|
||||
<a-table
|
||||
<!-- 已上传数据列表 -->
|
||||
<div class="uploaded-files-section">
|
||||
<h3 class="section-title">已上传数据</h3>
|
||||
<a-table
|
||||
:columns="fileColumns"
|
||||
:data="imageList"
|
||||
:data="uploadedFiles"
|
||||
:pagination="false"
|
||||
:scroll="{ x: '100%', y: 'calc(100vh - 380px)' }"
|
||||
:loading="loading.image"
|
||||
class="scrollable-table"
|
||||
>
|
||||
<!-- 文件类型 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag :color="getFileTypeColor(record.type)" size="small">
|
||||
{{ record.type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
:scroll="{ x: '100%' }"
|
||||
>
|
||||
<!-- 文件类型 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag :color="getFileTypeColor(record.type)" size="small">
|
||||
{{ record.type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 文件大小 -->
|
||||
<template #size="{ record }">
|
||||
<span>{{ record.imageTypeLabel}}</span>
|
||||
</template>
|
||||
<!-- 文件大小 -->
|
||||
<template #size="{ record }">
|
||||
<span>{{ formatFileSize(record.size) }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.preTreatment)" size="small">
|
||||
{{ record.preTreatment }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)" size="small">
|
||||
{{ record.status }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button size="small" @click="previewFile(record)">预览</a-button>
|
||||
<a-button size="small" status="danger" @click="deleteFile(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 操作 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button size="small" @click="previewFile(record)">预览</a-button>
|
||||
<a-button size="small" status="danger" @click="deleteFile(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="video" tab="视频" title="视频">
|
||||
<div class="tab-content">
|
||||
<a-empty description="视频上传功能开发中..." />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="audio" tab="语音" title="语音">
|
||||
<div class="tab-content">
|
||||
<a-empty description="语音上传功能开发中..." />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="document" tab="文档" title="文档">
|
||||
<div class="tab-content">
|
||||
<a-empty description="文档上传功能开发中..." />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="other" tab="其他" title="其他">
|
||||
<div class="tab-content">
|
||||
<a-empty description="其他文件上传功能开发中..." />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 文件预览模态框 -->
|
||||
<PreviewModal ref="previewModal" />
|
||||
<a-modal
|
||||
v-model:visible="previewModalVisible"
|
||||
title="文件预览"
|
||||
:width="800"
|
||||
:footer="false"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<div v-if="previewFileData && previewFileData.type === 'image'" class="image-preview">
|
||||
<img :src="previewFileData.url" :alt="previewFileData.name" style="max-width: 100%; height: auto;" />
|
||||
</div>
|
||||
<div v-else-if="previewFileData && previewFileData.type === 'video'" class="video-preview">
|
||||
<video :src="previewFileData.url" controls style="max-width: 100%; height: auto;" />
|
||||
</div>
|
||||
<div v-else class="file-info">
|
||||
<p>文件名:{{ previewFileData?.name }}</p>
|
||||
<p>文件类型:{{ previewFileData?.type }}</p>
|
||||
<p>文件大小:{{ formatFileSize(previewFileData?.size) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed,onMounted } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import PreviewModal from './components/PreviewModal.vue'
|
||||
import { IconUpload } from '@arco-design/web-vue/es/icon'
|
||||
|
||||
import {
|
||||
getProjectList,
|
||||
getTurbineList,
|
||||
getPartList,
|
||||
getImageList,
|
||||
deleteImage,
|
||||
autoAnnotateImage,
|
||||
generateReport,
|
||||
batchUploadImages,
|
||||
uploadImageToPartV2
|
||||
} from '@/apis/industrial-image'
|
||||
|
||||
const previewModal = ref()
|
||||
// 活动选项卡
|
||||
const activeTab = ref('image')
|
||||
|
||||
// 筛选参数
|
||||
const filterParams = reactive({
|
||||
project: null,
|
||||
unit: null,
|
||||
component: null
|
||||
})
|
||||
|
||||
// 筛选选项
|
||||
const projectOptions = ref<Array<{label: string, value: string}>>([])
|
||||
const unitOptions = ref<Array<{label: string, value: string}>>([])
|
||||
const componentOptions = ref<Array<{label: string, value: string}>>([])
|
||||
|
||||
// 图像列表
|
||||
const imageList = ref<Array<{
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
imageTypeLabel: string
|
||||
shootingTime: string
|
||||
preTreatment: string
|
||||
url: string
|
||||
}>>([])
|
||||
|
||||
// 加载状态
|
||||
const loading = reactive({
|
||||
project: false,
|
||||
unit: false,
|
||||
component: false,
|
||||
image: false
|
||||
})
|
||||
|
||||
// 在组件挂载时获取项目列表
|
||||
onMounted(() => {
|
||||
fetchProjectList()
|
||||
})
|
||||
|
||||
// 监听项目变化,获取机组列表
|
||||
if (watch) {
|
||||
watch(() => filterParams.project, (newVal) => {
|
||||
// 清空已选的机组和部件
|
||||
filterParams.unit = null
|
||||
filterParams.component = null
|
||||
unitOptions.value = []
|
||||
componentOptions.value = []
|
||||
imageList.value = []
|
||||
|
||||
if (newVal) {
|
||||
fetchTurbineList(newVal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听机组变化,获取部件列表
|
||||
if (watch) {
|
||||
watch(() => filterParams.unit, (newVal) => {
|
||||
// 清空已选的部件
|
||||
filterParams.component = null
|
||||
componentOptions.value = []
|
||||
imageList.value = []
|
||||
|
||||
if (newVal && filterParams.project) {
|
||||
fetchPartList(filterParams.project, newVal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取项目列表
|
||||
const fetchProjectList = async () => {
|
||||
loading.project = true
|
||||
try {
|
||||
const res = await getProjectList({ page: 1, pageSize: 1000 });
|
||||
projectOptions.value = res.data.map(item => ({
|
||||
label: item.projectName,
|
||||
value: item.projectId
|
||||
}))
|
||||
} catch (error) {
|
||||
Message.error('获取项目列表失败')
|
||||
} finally {
|
||||
loading.project = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取机组列表
|
||||
const fetchTurbineList = async (projectId: string) => {
|
||||
loading.unit = true
|
||||
try {
|
||||
const res = await getTurbineList({ projectId })
|
||||
unitOptions.value = res.data.map(item => ({
|
||||
label: item.turbineName,
|
||||
value: item.turbineId
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取机组列表失败:', error)
|
||||
Message.error('获取机组列表失败')
|
||||
} finally {
|
||||
loading.unit = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取部件列表
|
||||
const fetchPartList = async (projectId: string, turbineId: string) => {
|
||||
loading.component = true
|
||||
try {
|
||||
const res = await getPartList({ projectId, turbineId })
|
||||
componentOptions.value = res.data.map(item => ({
|
||||
label: item.partName, // 根据实际接口调整
|
||||
value: item.partId // 根据实际接口调整
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取部件列表失败:', error)
|
||||
Message.error('获取部件列表失败')
|
||||
} finally {
|
||||
loading.component = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理筛选变化,获取图像列表
|
||||
const handleFilterChange = async () => {
|
||||
if (!filterParams.unit) return
|
||||
|
||||
loading.image = true
|
||||
try {
|
||||
let params = {
|
||||
turbineId: filterParams.unit
|
||||
}
|
||||
if(filterParams.component){
|
||||
params = {
|
||||
turbineId: filterParams.unit,
|
||||
partId: filterParams.component
|
||||
}
|
||||
}
|
||||
|
||||
const res = await getImageList(params)
|
||||
imageList.value = res.data.map((item: any) => ({
|
||||
id: item.imageId,
|
||||
name: item.imageName,
|
||||
type: item.imageType?item.imageType:"未指定类型",
|
||||
imageTypeLabel: item.imageTypeLabel,
|
||||
shootingTime: item.shootingTime,
|
||||
preTreatment: item.preTreatment?"已审核":"未审核",
|
||||
imagePath: item.imagePath,
|
||||
audioList:item.audioList
|
||||
}))
|
||||
Message.success(`获取到 ${imageList.value.length} 条图像数据`)
|
||||
} catch (error) {
|
||||
Message.error('获取图像列表失败')
|
||||
} finally {
|
||||
loading.image = false
|
||||
}
|
||||
}
|
||||
|
||||
// const response = await getTurbineList({ projectId: node.id })
|
||||
// 上传状态
|
||||
const uploading = ref(false)
|
||||
const uploadQueue = ref<any[]>([])
|
||||
|
@ -294,32 +155,65 @@ const previewModalVisible = ref(false)
|
|||
const previewFileData = ref<any>(null)
|
||||
|
||||
// 已上传文件列表
|
||||
const uploadedFiles = ref([])
|
||||
const uploadedFiles = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'IMG_20231105_1430.jpg',
|
||||
type: 'image',
|
||||
size: 3355443, // 3.2MB
|
||||
uploadTime: '2023-11-05 14:32',
|
||||
status: '成功',
|
||||
url: '/api/files/IMG_20231105_1430.jpg'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'VID_20231106_0915.mp4',
|
||||
type: 'video',
|
||||
size: 47185920, // 45.6MB
|
||||
uploadTime: '2023-11-06 09:18',
|
||||
status: '成功',
|
||||
url: '/api/files/VID_20231106_0915.mp4'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'IMG_20231107_1645.jpg',
|
||||
type: 'image',
|
||||
size: 2936013, // 2.8MB
|
||||
uploadTime: '2023-11-07 16:48',
|
||||
status: '成功',
|
||||
url: '/api/files/IMG_20231107_1645.jpg'
|
||||
}
|
||||
])
|
||||
|
||||
// 文件表格列配置
|
||||
const fileColumns: TableColumnData[] = [
|
||||
{ title: '文件名', dataIndex: 'name', width: 250 },
|
||||
{ title: '类型', dataIndex: 'type', slotName: 'type', width: 100 },
|
||||
{ title: '类型描述', dataIndex: 'imageTypeLabel', slotName: 'imageTypeLabel', width: 100 },
|
||||
{ title: '上传时间', dataIndex: 'shootingTime', width: 150 },
|
||||
{ title: '状态', dataIndex: 'preTreatment', slotName: 'preTreatment', width: 100 },
|
||||
{ title: '大小', dataIndex: 'size', slotName: 'size', width: 100 },
|
||||
{ title: '上传时间', dataIndex: 'uploadTime', width: 150 },
|
||||
{ title: '状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '操作', slotName: 'action', width: 150, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 过滤后的文件列表
|
||||
const filteredFiles = computed(() => {
|
||||
return uploadedFiles.value.filter(file =>
|
||||
(filterParams.project === null || file.project === filterParams.project) &&
|
||||
(filterParams.unit === null || file.unit === filterParams.unit) &&
|
||||
(filterParams.component === null || file.component === filterParams.component)
|
||||
)
|
||||
})
|
||||
// 根据选项卡获取接受的文件类型
|
||||
const getAcceptType = () => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'image': 'image/*',
|
||||
'video': 'video/*',
|
||||
'audio': 'audio/*',
|
||||
'document': '.pdf,.doc,.docx,.txt,.xls,.xlsx,.ppt,.pptx',
|
||||
'other': '*'
|
||||
}
|
||||
return typeMap[activeTab.value] || '*'
|
||||
}
|
||||
|
||||
// 获取文件类型颜色
|
||||
const getFileTypeColor = (type: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
'TYPICAL': 'blue',
|
||||
'DEFECT': 'orange',
|
||||
'image': 'blue',
|
||||
'video': 'green',
|
||||
'audio': 'orange',
|
||||
'document': 'purple',
|
||||
'other': 'gray'
|
||||
}
|
||||
return colorMap[type] || 'gray'
|
||||
|
@ -327,10 +221,11 @@ const getFileTypeColor = (type: string) => {
|
|||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
console.log(status);
|
||||
const colorMap: Record<string, string> = {
|
||||
'已审核': 'green',
|
||||
'未审核': 'red'
|
||||
'成功': 'green',
|
||||
'失败': 'red',
|
||||
'上传中': 'blue',
|
||||
'等待': 'orange'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
@ -344,30 +239,107 @@ const formatFileSize = (bytes: number) => {
|
|||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + sizes[i]
|
||||
}
|
||||
|
||||
const getImageUrl = (imagePath: string): string => {
|
||||
if (!imagePath) return ''
|
||||
if (imagePath.startsWith('http')) return imagePath
|
||||
const baseUrl = 'http://pms.dtyx.net:9158'
|
||||
return `${baseUrl}${imagePath}`
|
||||
// 自定义上传请求
|
||||
const customUpload = (option: any) => {
|
||||
const { file } = option
|
||||
|
||||
// 检查文件大小
|
||||
if (file.size > 100 * 1024 * 1024) {
|
||||
Message.error('文件大小不能超过100MB')
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到上传队列
|
||||
uploadQueue.value.push({
|
||||
file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: getFileType(file.name),
|
||||
status: '等待'
|
||||
})
|
||||
|
||||
Message.success(`文件 ${file.name} 已添加到上传队列`)
|
||||
}
|
||||
|
||||
// 获取文件类型
|
||||
const getFileType = (fileName: string) => {
|
||||
const extension = fileName.split('.').pop()?.toLowerCase()
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(extension || '')) {
|
||||
return 'image'
|
||||
} else if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'].includes(extension || '')) {
|
||||
return 'video'
|
||||
} else if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(extension || '')) {
|
||||
return 'audio'
|
||||
} else if (['pdf', 'doc', 'docx', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(extension || '')) {
|
||||
return 'document'
|
||||
} else {
|
||||
return 'other'
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件变化
|
||||
const handleFileChange = (fileList: any[]) => {
|
||||
// 文件选择处理逻辑
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
const startUpload = async () => {
|
||||
if (uploadQueue.value.length === 0) {
|
||||
Message.warning('请先选择要上传的文件')
|
||||
return
|
||||
}
|
||||
|
||||
uploading.value = true
|
||||
|
||||
try {
|
||||
// 模拟上传过程
|
||||
for (let i = 0; i < uploadQueue.value.length; i++) {
|
||||
const queueItem = uploadQueue.value[i]
|
||||
queueItem.status = '上传中'
|
||||
|
||||
// 模拟上传延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 添加到已上传列表
|
||||
uploadedFiles.value.push({
|
||||
id: Date.now() + i,
|
||||
name: queueItem.name,
|
||||
type: queueItem.type,
|
||||
size: queueItem.size,
|
||||
uploadTime: new Date().toLocaleString(),
|
||||
status: '成功',
|
||||
url: `/api/files/${queueItem.name}`
|
||||
})
|
||||
}
|
||||
|
||||
uploadQueue.value = []
|
||||
Message.success('所有文件上传完成')
|
||||
} catch (error) {
|
||||
Message.error('上传失败,请重试')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清空文件
|
||||
const clearFiles = () => {
|
||||
uploadQueue.value = []
|
||||
Message.success('已清空文件队列')
|
||||
}
|
||||
|
||||
// 批量导入
|
||||
const batchImport = () => {
|
||||
Message.info('批量导入功能开发中...')
|
||||
}
|
||||
|
||||
// 预览文件
|
||||
const previewFile = (file: any) => {
|
||||
/* previewFileData.value = file
|
||||
previewModalVisible.value = true*/
|
||||
|
||||
const fileObj ={
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
url: getImageUrl(file.imagePath),
|
||||
audios: file.audioList
|
||||
}
|
||||
previewModal.value.openPreview(fileObj)
|
||||
previewFileData.value = file
|
||||
previewModalVisible.value = true
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
const deleteFile = (file: any) => {
|
||||
console.log(index);
|
||||
const index = uploadedFiles.value.findIndex(f => f.id === file.id)
|
||||
if (index > -1) {
|
||||
uploadedFiles.value.splice(index, 1)
|
||||
|
@ -385,7 +357,7 @@ const deleteFile = (file: any) => {
|
|||
|
||||
.page-header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
|
@ -405,22 +377,65 @@ const deleteFile = (file: any) => {
|
|||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
.upload-section {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
|
||||
.filter-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #595959;
|
||||
margin-right: 8px;
|
||||
|
||||
.upload-dragger {
|
||||
:deep(.arco-upload-drag) {
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.upload-icon {
|
||||
color: #bfbfbf;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
.primary-text {
|
||||
font-size: 16px;
|
||||
color: #595959;
|
||||
margin: 0 0 8px 0;
|
||||
|
||||
.link-text {
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.uploaded-files-section {
|
||||
|
@ -434,16 +449,16 @@ const deleteFile = (file: any) => {
|
|||
|
||||
.preview-container {
|
||||
text-align: center;
|
||||
|
||||
|
||||
.image-preview,
|
||||
.video-preview {
|
||||
max-height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.file-info {
|
||||
text-align: left;
|
||||
|
||||
|
||||
p {
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
|
@ -484,4 +499,4 @@ const deleteFile = (file: any) => {
|
|||
:deep(.arco-upload-drag:hover) {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -1,75 +0,0 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
:title="isUpdate ? '修改维度' : '新增维度'"
|
||||
@before-ok="save"
|
||||
@cancel="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" auto-label-width>
|
||||
<a-form-item label="维度名称" field="name" required>
|
||||
<a-input v-model="form.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权重%" field="weight" required>
|
||||
<a-input-number v-model="form.weight" :min="1" :max="100" />
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" field="desc">
|
||||
<a-textarea v-model="form.desc" :rows="3" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" field="status">
|
||||
<a-switch v-model="form.status" :checked-value="0" :unchecked-value="1" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { addDimension, getDimensionDetail, updateDimension } from '@/apis/performance'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
const visible = ref(false)
|
||||
const isUpdate = ref(false)
|
||||
const formRef = ref()
|
||||
const id = ref('')
|
||||
const form = reactive({
|
||||
name: '',
|
||||
weight: 50,
|
||||
desc: '',
|
||||
status: 0 as 0 | 1,
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
Object.assign(form, { name: '', weight: 50, desc: '', status: 0 })
|
||||
id.value = ''
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (isUpdate.value) {
|
||||
await updateDimension(id.value, form)
|
||||
} else {
|
||||
await addDimension(form)
|
||||
}
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
const onAdd = () => {
|
||||
reset()
|
||||
isUpdate.value = false
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const onUpdate = async (dimensionId: string) => {
|
||||
reset()
|
||||
isUpdate.value = true
|
||||
id.value = dimensionId
|
||||
const data = await getDimensionDetail(dimensionId)
|
||||
Object.assign(form, data)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
|
@ -1,100 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading">
|
||||
<a-descriptions :data="detailData" bordered>
|
||||
<a-descriptions-item label="员工">
|
||||
{{ data.userName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="维度">
|
||||
{{ data.dimensionName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="周期">
|
||||
{{ data.periodName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评分">
|
||||
<a-tag color="blue" size="large">{{ data.score }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="AI评价" :span="2">
|
||||
{{ data.aiComment }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 反馈表单 -->
|
||||
<a-divider orientation="left">评价反馈</a-divider>
|
||||
<a-form ref="feedbackRef" :model="feedback" :rules="rules" layout="vertical">
|
||||
<a-form-item label="是否有异议" field="level" required>
|
||||
<a-radio-group v-model="feedback.level">
|
||||
<a-radio :value="0">无异议</a-radio>
|
||||
<a-radio :value="1">部分有异议</a-radio>
|
||||
<a-radio :value="2">完全不同意</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="请说明理由" field="content" required>
|
||||
<a-textarea
|
||||
v-model="feedback.content"
|
||||
:rows="4"
|
||||
placeholder="请具体描述您对评价结果的看法..."
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
提交反馈
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { submitFeedback } from '@/apis/performance'
|
||||
import type { EvaluateResp, FeedbackReq } from '@/apis/performance/type'
|
||||
|
||||
interface Props {
|
||||
data: EvaluateResp
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'feedback'): void
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const feedbackRef = ref()
|
||||
|
||||
const feedback = reactive<FeedbackReq>({
|
||||
evaluateId: props.data.id,
|
||||
level: 0,
|
||||
content: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
level: [{ required: true, message: '请选择异议等级' }],
|
||||
content: [{ required: true, message: '请填写反馈内容' }],
|
||||
}
|
||||
|
||||
const detailData = computed(() => [
|
||||
{ label: '员工', value: props.data.userName },
|
||||
{ label: '维度', value: props.data.dimensionName },
|
||||
{ label: '周期', value: props.data.periodName },
|
||||
{ label: '评分', value: props.data.score },
|
||||
{ label: 'AI评价', value: props.data.aiComment },
|
||||
])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const ok = await feedbackRef.value?.validate()
|
||||
if (ok) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
await submitFeedback(feedback)
|
||||
Message.success('反馈已提交')
|
||||
emit('feedback')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,18 +0,0 @@
|
|||
<template>
|
||||
<a-menu
|
||||
:default-selected-keys="['dimension']"
|
||||
@menu-item-click="handleClick"
|
||||
>
|
||||
<a-menu-item key="dimension">绩效维度</a-menu-item>
|
||||
<a-menu-item key="evaluate">员工评估</a-menu-item>
|
||||
<a-menu-item key="my">我的绩效</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', key: string): void
|
||||
}>()
|
||||
|
||||
const handleClick = (key: string) => emit('select', key)
|
||||
</script>
|
|
@ -1,131 +0,0 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
:title="isUpdate ? '编辑细则' : '新增细则'"
|
||||
:width="480"
|
||||
@before-ok="handleSave"
|
||||
@cancel="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" auto-label-width>
|
||||
<a-form-item label="所属维度" field="dimensionId" required>
|
||||
<a-select v-model="form.dimensionId" placeholder="请选择维度">
|
||||
<a-option
|
||||
v-for="item in dimensionOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
/>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="细则名称" field="name" required>
|
||||
<a-input v-model="form.name" placeholder="如:持有基础资质证书" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="评分规则描述" field="ruleDesc" required>
|
||||
<a-textarea
|
||||
v-model="form.ruleDesc"
|
||||
:rows="4"
|
||||
placeholder="详细说明评分标准"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="满分" field="score" required>
|
||||
<a-input-number v-model="form.score" :min="0" :precision="0" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权重" field="weight" required>
|
||||
<a-input-number v-model="form.weight" :min="0" :max="100" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否加分项" field="isExtra">
|
||||
<a-switch v-model="form.isExtra" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import {
|
||||
addRule,
|
||||
getDimensionList,
|
||||
getRuleDetail,
|
||||
updateRule,
|
||||
} from '@/apis/performance'
|
||||
import type { DimensionResp, RuleAddReq, RuleUpdateReq } from '@/apis/performance/type'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const isUpdate = ref(false)
|
||||
const id = ref('')
|
||||
const formRef = ref()
|
||||
const dimensionOptions = ref<DimensionResp[]>([])
|
||||
|
||||
const form = reactive<RuleAddReq>({
|
||||
dimensionId: '',
|
||||
name: '',
|
||||
ruleDesc: '',
|
||||
score: 100,
|
||||
weight: 10,
|
||||
isExtra: false,
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
Object.assign(form, {
|
||||
dimensionId: '',
|
||||
name: '',
|
||||
ruleDesc: '',
|
||||
score: 100,
|
||||
weight: 10,
|
||||
isExtra: false,
|
||||
})
|
||||
id.value = ''
|
||||
}
|
||||
|
||||
const openAdd = () => {
|
||||
reset()
|
||||
isUpdate.value = false
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const openUpdate = async (ruleId: string) => {
|
||||
reset()
|
||||
isUpdate.value = true
|
||||
id.value = ruleId
|
||||
const detail = await getRuleDetail(ruleId)
|
||||
Object.assign(form, detail)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
const ok = await formRef.value?.validate()
|
||||
if (ok) return false
|
||||
|
||||
try {
|
||||
if (isUpdate.value) {
|
||||
await updateRule(id.value, form as RuleUpdateReq)
|
||||
Message.success('细则已更新')
|
||||
} else {
|
||||
await addRule(form)
|
||||
Message.success('细则已新增')
|
||||
}
|
||||
visible.value = false
|
||||
emit('success')
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
dimensionOptions.value = await getDimensionList()
|
||||
})
|
||||
|
||||
defineExpose({ openAdd, openUpdate })
|
||||
</script>
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="细则管理"
|
||||
:width="800"
|
||||
@before-ok="handleSave"
|
||||
@cancel="reset"
|
||||
>
|
||||
<a-table :data="rules" :columns="columns" row-key="id">
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="RuleDrawerRef?.openUpdate(record.id)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDelete(record.id)">
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-button type="primary" @click="RuleDrawerRef?.openAdd()">
|
||||
<icon-plus /> 新增细则
|
||||
</a-button>
|
||||
</a-modal>
|
||||
|
||||
<RuleDrawer ref="RuleDrawerRef" @success="load" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import RuleDrawer from './RuleDrawer.vue'
|
||||
import { deleteRule, getRuleList } from '@/apis/performance'
|
||||
import type { RuleResp } from '@/apis/performance/type'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
const visible = ref(false)
|
||||
const rules = ref<RuleResp[]>([])
|
||||
const RuleDrawerRef = ref()
|
||||
|
||||
const columns = [
|
||||
{ title: '细则名称', dataIndex: 'name' },
|
||||
{ title: '评分规则描述', dataIndex: 'ruleDesc' },
|
||||
{ title: '满分', dataIndex: 'score' },
|
||||
{ title: '权重', dataIndex: 'weight' },
|
||||
{ title: '是否加分项', dataIndex: 'isExtra', render: ({ record }) => record.isExtra ? '是' : '否' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const open = async (dimensionId: string) => {
|
||||
visible.value = true
|
||||
rules.value = await getRuleList(dimensionId)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
await deleteRule(id)
|
||||
Message.success('删除成功')
|
||||
load()
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
// 重新加载规则列表
|
||||
rules.value = await getRuleList(dimensionId.value)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="绩效维度管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="DimensionDrawerRef?.onAdd()">
|
||||
<icon-plus /> 添加维度
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :data="dimensions" :columns="columns" row-key="id">
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 0 ? 'green' : 'red'">
|
||||
{{ record.status === 0 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="DimensionDrawerRef?.onUpdate(record.id)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDelete(record.id)">
|
||||
删除
|
||||
</a-button>
|
||||
<a-button type="text" @click="openRuleList(record.id)">
|
||||
管理细则
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 维度抽屉 -->
|
||||
<DimensionDrawer ref="DimensionDrawerRef" @success="load" />
|
||||
<!-- 细则列表 -->
|
||||
<RuleList ref="RuleListRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getDimensionList, deleteDimension } from '@/apis/performance'
|
||||
import DimensionDrawer from './components/DimensionDrawer.vue'
|
||||
import RuleList from './components/RuleList.vue'
|
||||
import type { DimensionResp } from '@/apis/performance/type'
|
||||
|
||||
const dimensions = ref<DimensionResp[]>([])
|
||||
const DimensionDrawerRef = ref()
|
||||
const RuleListRef = ref()
|
||||
|
||||
const columns = [
|
||||
{ title: '维度名称', dataIndex: 'name' },
|
||||
{ title: '权重', dataIndex: 'weight', render: ({ record }) => `${record.weight}%` },
|
||||
{ title: '细则数量', dataIndex: 'ruleCount' },
|
||||
{ title: '状态', slotName: 'status' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const load = async () => {
|
||||
const response=await getDimensionList()
|
||||
dimensions.value = await getDimensionList().data||[]
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
await deleteDimension(id)
|
||||
load()
|
||||
}
|
||||
|
||||
const openRuleList = (dimensionId: string) => {
|
||||
RuleListRef.value.open(dimensionId)
|
||||
}
|
||||
</script>
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<a-card title="员工绩效评估">
|
||||
<a-form layout="inline" style="margin-bottom: 16px">
|
||||
<a-form-item label="绩效周期">
|
||||
<a-select v-model="query.periodId" placeholder="请选择周期" allow-clear>
|
||||
<a-option v-for="p in periods" :key="p.id" :value="p.id">{{ p.name }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="维度">
|
||||
<a-select v-model="query.dimensionId" placeholder="请选择维度" allow-clear>
|
||||
<a-option v-for="d in dimensions" :key="d.id" :value="d.id">{{ d.name }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input v-model="query.keyword" placeholder="姓名/工号" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="load">查询</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-table :data="data" :columns="columns" row-key="id">
|
||||
<template #action="{ record }">
|
||||
<a-button type="text" @click="openEvaluate(record)">
|
||||
开始评估
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getDimensionList, getEvaluatePage, getPeriodList, startEvaluate } from '@/apis/performance'
|
||||
import type { EvaluateQuery, EvaluateResp } from '@/apis/performance/type'
|
||||
|
||||
const periods = ref([])
|
||||
const dimensions = ref([])
|
||||
const query = ref<EvaluateQuery>({})
|
||||
const data = ref<EvaluateResp[]>([])
|
||||
|
||||
const columns = [
|
||||
{ title: '员工', dataIndex: 'userName' },
|
||||
{ title: '维度', dataIndex: 'dimensionName' },
|
||||
{ title: '周期', dataIndex: 'periodName' },
|
||||
{ title: '评分', dataIndex: 'score' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const load = async () => {
|
||||
data.value = await getEvaluatePage(query.value)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
periods.value = await getPeriodList()
|
||||
dimensions.value = await getDimensionList()
|
||||
load()
|
||||
})
|
||||
|
||||
const openEvaluate = async (row: EvaluateResp) => {
|
||||
await startEvaluate({
|
||||
userId: row.userId,
|
||||
dimensionId: row.dimensionId,
|
||||
ruleId: row.ruleId,
|
||||
periodId: row.periodId,
|
||||
})
|
||||
Message.success('评估已启动')
|
||||
load()
|
||||
}
|
||||
</script>
|
|
@ -1,19 +0,0 @@
|
|||
<template>
|
||||
<GiPageLayout>
|
||||
<template #left>
|
||||
<PerformanceMenu @select="handleSelectMenu" />
|
||||
</template>
|
||||
<router-view />
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import PerformanceMenu from './components/PerformanceMenu.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleSelectMenu = (key: string) => {
|
||||
router.push(`/performance/${key}`)
|
||||
}
|
||||
</script>
|
|
@ -1,50 +0,0 @@
|
|||
<template>
|
||||
<a-card title="我的绩效">
|
||||
<a-table :data="data" :columns="columns" row-key="evaluationId">
|
||||
<template #action="{ record }">
|
||||
<a-button type="text" @click="openDetail(record)">查看详情</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<a-modal v-model:visible="visible" title="绩效详情">
|
||||
<div v-if="selected">
|
||||
<div>维度:{{ selected.dimensionName }}</div>
|
||||
<div>周期:{{ selected.periodName }}</div>
|
||||
<div>评分:{{ selected.score }}</div>
|
||||
<div>奖金:{{ selected.bonus }}</div>
|
||||
<div>评价:{{ selected.comment }}</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getMyEvaluation } from '@/apis/performance-setting'
|
||||
import type { PerformanceRule } from '@/apis/performance-setting/type'
|
||||
|
||||
const data = ref<any[]>([])
|
||||
const visible = ref(false)
|
||||
const selected = ref<any>()
|
||||
|
||||
const columns = [
|
||||
{ title: '维度', dataIndex: 'dimensionName' },
|
||||
{ title: '周期', dataIndex: 'periodName' },
|
||||
{ title: '评分', dataIndex: 'score' },
|
||||
{ title: '奖金', dataIndex: 'bonus' },
|
||||
{ title: '评价', dataIndex: 'comment' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const load = async () => {
|
||||
const res = await getMyEvaluation()
|
||||
data.value = res.data || []
|
||||
}
|
||||
|
||||
const openDetail = (row: any) => {
|
||||
selected.value = row
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="绩效细则管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="RuleDrawerRef?.openAdd()">
|
||||
<icon-plus /> 新增细则
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :data="rules" :columns="columns" row-key="id">
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="RuleDrawerRef?.openUpdate(record.id)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDelete(record.id)">
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 细则抽屉 -->
|
||||
<RuleDrawer ref="RuleDrawerRef" @success="load" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import RuleDrawer from './components/RuleDrawer.vue'
|
||||
import { deleteRule, getRuleList } from '@/apis/performance'
|
||||
import type { RuleResp } from '@/apis/performance/type'
|
||||
|
||||
const rules = ref<RuleResp[]>([])
|
||||
const RuleDrawerRef = ref()
|
||||
|
||||
const columns = [
|
||||
{ title: '细则名称', dataIndex: 'name' },
|
||||
{ title: '评分规则描述', dataIndex: 'ruleDesc' },
|
||||
{ title: '满分', dataIndex: 'score' },
|
||||
{ title: '权重', dataIndex: 'weight' },
|
||||
{ title: '是否加分项', dataIndex: 'isExtra', render: ({ record }) => record.isExtra ? '是' : '否' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const load = async (dimensionId: string) => {
|
||||
rules.value = await getRuleList(dimensionId)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 假设默认加载某个维度的细则
|
||||
load('default-dimension-id')
|
||||
})
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
await deleteRule(id)
|
||||
Message.success('删除成功')
|
||||
load('default-dimension-id') // 重新加载数据
|
||||
}
|
||||
</script>
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<a-drawer v-model:visible="visible" :title="isEdit ? '编辑维度' : '新增维度'" @before-ok="save">
|
||||
<a-form ref="formRef" :model="form" auto-label-width>
|
||||
<a-form-item label="维度名称" field="dimensionName" required>
|
||||
<a-input v-model="form.dimensionName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="岗位" field="deptName" required>
|
||||
<a-input v-model="form.deptName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" field="description">
|
||||
<a-textarea v-model="form.description" :rows="3" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" field="status">
|
||||
<a-switch v-model="form.status" :checked-value="0" :unchecked-value="1" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { addDimension, updateDimension } from '@/apis/performance-setting'
|
||||
import type { PerformanceDimension } from '@/apis/performance-setting/type'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const visible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref()
|
||||
const form = ref<Partial<PerformanceDimension>>({
|
||||
dimensionName: '',
|
||||
deptName: '',
|
||||
description: '',
|
||||
status: 0,
|
||||
})
|
||||
|
||||
const open = (record?: PerformanceDimension) => {
|
||||
if (record) {
|
||||
Object.assign(form.value, record)
|
||||
isEdit.value = true
|
||||
} else {
|
||||
Object.assign(form.value, { dimensionName: '', deptName: '', description: '', status: 0 })
|
||||
isEdit.value = false
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (isEdit.value && form.value.dimensionId) {
|
||||
await updateDimension(form.value.dimensionId, form.value)
|
||||
} else {
|
||||
await addDimension(form.value)
|
||||
}
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
|
@ -1,86 +0,0 @@
|
|||
<template>
|
||||
<a-drawer v-model:visible="visible" :title="isEdit ? '编辑细则' : '新增细则'" @before-ok="save">
|
||||
<a-form ref="formRef" :model="form" auto-label-width>
|
||||
<a-form-item label="细则名称" field="ruleName" required>
|
||||
<a-input v-model="form.ruleName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" field="description">
|
||||
<a-textarea v-model="form.description" :rows="3" />
|
||||
</a-form-item>
|
||||
<a-form-item label="分数" field="score">
|
||||
<a-input-number v-model="form.score" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权重" field="weight">
|
||||
<a-input-number v-model="form.weight" :min="0" :max="100" />
|
||||
</a-form-item>
|
||||
<a-form-item label="奖金" field="bonus">
|
||||
<a-input v-model="form.bonus" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" field="status">
|
||||
<a-switch v-model="form.status" :checked-value="0" :unchecked-value="1" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { addRule, updateRule } from '@/apis/performance-setting'
|
||||
import type { PerformanceRule, PerformanceDimension } from '@/apis/performance-setting/type'
|
||||
|
||||
const props = defineProps<{ dimension?: PerformanceDimension }>()
|
||||
const emit = defineEmits(['success'])
|
||||
const visible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref()
|
||||
const form = ref<Partial<PerformanceRule>>({
|
||||
ruleName: '',
|
||||
description: '',
|
||||
score: 0,
|
||||
weight: 0,
|
||||
bonus: '',
|
||||
status: 0,
|
||||
dimensionName: '',
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.dimension,
|
||||
(val) => {
|
||||
if (val && !isEdit.value) {
|
||||
form.value.dimensionName = val.dimensionName
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const open = (record?: PerformanceRule) => {
|
||||
if (record) {
|
||||
Object.assign(form.value, record)
|
||||
isEdit.value = true
|
||||
} else {
|
||||
Object.assign(form.value, {
|
||||
ruleName: '',
|
||||
description: '',
|
||||
score: 0,
|
||||
weight: 0,
|
||||
bonus: '',
|
||||
status: 0,
|
||||
dimensionName: props.dimension?.dimensionName || '',
|
||||
})
|
||||
isEdit.value = false
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (isEdit.value && form.value.ruleId) {
|
||||
await updateRule(form.value.ruleId, form.value)
|
||||
} else {
|
||||
await addRule(form.value)
|
||||
}
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
|
@ -1,64 +0,0 @@
|
|||
<template>
|
||||
<a-drawer v-model:visible="visible" :title="`管理细则 - ${dimension?.dimensionName || ''}`">
|
||||
<a-button type="primary" @click="openRuleDrawer()">新增细则</a-button>
|
||||
<a-table :data="ruleList" :columns="ruleColumns" row-key="ruleId" style="margin-top: 16px;">
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 0 ? 'green' : 'red'">
|
||||
{{ record.status === 0 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="openRuleDrawer(record)">编辑</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDeleteRule(record.ruleId)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
<RuleDrawer ref="ruleDrawerRef" @success="loadRules" :dimension="dimension" />
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { getRuleList, deleteRule } from '@/apis/performance-setting'
|
||||
import RuleDrawer from './RuleDrawer.vue'
|
||||
import type { PerformanceRule, PerformanceDimension } from '@/apis/performance-setting/type'
|
||||
|
||||
const visible = ref(false)
|
||||
const dimension = ref<PerformanceDimension>()
|
||||
const ruleList = ref<PerformanceRule[]>([])
|
||||
const ruleDrawerRef = ref()
|
||||
|
||||
const ruleColumns = [
|
||||
{ title: '细则名称', dataIndex: 'ruleName' },
|
||||
{ title: '描述', dataIndex: 'description' },
|
||||
{ title: '分数', dataIndex: 'score' },
|
||||
{ title: '权重', dataIndex: 'weight' },
|
||||
{ title: '奖金', dataIndex: 'bonus' },
|
||||
{ title: '状态', slotName: 'status' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const open = (dim: PerformanceDimension) => {
|
||||
dimension.value = dim
|
||||
visible.value = true
|
||||
loadRules()
|
||||
}
|
||||
|
||||
const loadRules = async () => {
|
||||
if (!dimension.value) return
|
||||
const res = await getRuleList({ dimensionName: dimension.value.dimensionName })
|
||||
ruleList.value = res.data || []
|
||||
}
|
||||
|
||||
const openRuleDrawer = (record?: PerformanceRule) => {
|
||||
ruleDrawerRef.value.open(record)
|
||||
}
|
||||
|
||||
const handleDeleteRule = async (id: string) => {
|
||||
await deleteRule(id)
|
||||
loadRules()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<a-card title="绩效维度管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="openDimensionDrawer()">新增维度</a-button>
|
||||
</template>
|
||||
<a-table :data="dimensionList" :columns="dimensionColumns" row-key="dimensionId">
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 0 ? 'green' : 'red'">
|
||||
{{ record.status === 0 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="openDimensionDrawer(record)">编辑</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDeleteDimension(record.dimensionId)">删除</a-button>
|
||||
<a-button type="text" @click="openRuleList(record)">管理细则</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 维度抽屉 -->
|
||||
<DimensionDrawer ref="dimensionDrawerRef" @success="loadDimensions" />
|
||||
|
||||
<!-- 细则管理 -->
|
||||
<RuleList ref="ruleListRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import DimensionDrawer from './components/DimensionDrawer.vue'
|
||||
import RuleList from './components/RuleList.vue'
|
||||
import { deleteDimension, getDimensionList } from '@/apis/performance-setting/index'
|
||||
import type { PerformanceDimension } from '@/apis/performance-setting/type.ts'
|
||||
|
||||
const dimensionList = ref<PerformanceDimension[]>([])
|
||||
const dimensionDrawerRef = ref()
|
||||
const ruleListRef = ref()
|
||||
|
||||
const dimensionColumns = [
|
||||
{ title: '维度名称', dataIndex: 'dimensionName' },
|
||||
{ title: '岗位', dataIndex: 'deptName' },
|
||||
{ title: '描述', dataIndex: 'description' },
|
||||
{ title: '状态', slotName: 'status' },
|
||||
{ title: '操作', slotName: 'action' },
|
||||
]
|
||||
|
||||
const loadDimensions = async () => {
|
||||
const res = await getDimensionList()
|
||||
dimensionList.value = res.data || []
|
||||
}
|
||||
|
||||
const openDimensionDrawer = (record?: PerformanceDimension) => {
|
||||
dimensionDrawerRef.value.open(record)
|
||||
}
|
||||
|
||||
const handleDeleteDimension = async (id: string) => {
|
||||
await deleteDimension(id)
|
||||
loadDimensions()
|
||||
}
|
||||
|
||||
const openRuleList = (dimension: PerformanceDimension) => {
|
||||
ruleListRef.value.open(dimension)
|
||||
}
|
||||
|
||||
onMounted(loadDimensions)
|
||||
</script>
|
|
@ -1,88 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import WindTurbine from './icons/WindTurbine.vue'
|
||||
|
||||
interface Turbine {
|
||||
id: number
|
||||
turbineNo: string
|
||||
status: 0 | 1 | 2 // 0 待施工 1 施工中 2 已完成
|
||||
lat?: number
|
||||
lng?: number
|
||||
}
|
||||
|
||||
const props = defineProps<{ modelValue: Turbine }>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: Turbine): void
|
||||
(e: 'map'): void
|
||||
}>()
|
||||
|
||||
const turbine = computed({
|
||||
get: () => props.modelValue,
|
||||
set: v => emit('update:modelValue', v)
|
||||
})
|
||||
|
||||
/* 状态文字 & 颜色 */
|
||||
const statusTextMap = { 0: '待施工', 1: '施工中', 2: '已完成' }
|
||||
const statusColorMap = { 0: '#FF7D00', 1: '#165DFF', 2: '#00B42A' }
|
||||
|
||||
/* 点击循环切换 */
|
||||
function toggleStatus() {
|
||||
const next = ((turbine.value.status + 1) % 3) as 0 | 1 | 2
|
||||
turbine.value = { ...turbine.value, status: next }
|
||||
}
|
||||
|
||||
function updateNo(val: string) {
|
||||
turbine.value = { ...turbine.value, turbineNo: val }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="turbine-card">
|
||||
<!-- 可点击的状态标签 -->
|
||||
<div class="status-tag" :style="{ backgroundColor: statusColorMap[turbine.status] }" @click="toggleStatus">
|
||||
{{ statusTextMap[turbine.status] }}
|
||||
</div>
|
||||
|
||||
<!-- 风机图标 -->
|
||||
<WindTurbine />
|
||||
|
||||
<!-- 机组编号输入框 -->
|
||||
<a-input :model-value="turbine.turbineNo" @update:model-value="updateNo" size="small" class="turbine-input"
|
||||
placeholder="编号" />
|
||||
|
||||
<!-- 地图选点按钮 -->
|
||||
<a-button size="mini" @click="$emit('map')">
|
||||
<template #icon><icon-location /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.turbine-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 8px;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
background: var(--color-bg-2);
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.turbine-input {
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -1,41 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import TurbineCard from './TurbineCard.vue'
|
||||
|
||||
interface Turbine {
|
||||
id: number
|
||||
turbineNo: string
|
||||
status: 0 | 1 | 2
|
||||
lat?: number
|
||||
lng?: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Turbine[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: Turbine[]): void
|
||||
(e: 'map', turbine: Turbine): void
|
||||
}>()
|
||||
|
||||
const turbines = computed({
|
||||
get: () => props.modelValue,
|
||||
set: v => emit('update:modelValue', v)
|
||||
})
|
||||
|
||||
function updateTurbine(index: number, t: Turbine) {
|
||||
const arr = [...turbines.value]
|
||||
arr.splice(index, 1, t)
|
||||
turbines.value = arr
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row v-if="turbines.length" :gutter="[16, 16]" wrap>
|
||||
<a-col v-for="(t, i) in turbines" :key="t.id" :xs="12" :sm="8" :md="6" :lg="4" :xl="3">
|
||||
<TurbineCard v-model="turbines[i]" @update:model-value="v => updateTurbine(i, v)" @map="$emit('map', t)" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-empty v-else description="请先设置项目规模" />
|
||||
</template>
|
|
@ -7,9 +7,7 @@
|
|||
<template #icon><icon-arrow-left /></template>
|
||||
</a-button>
|
||||
<h2 class="ml-2">{{ projectTitle }}</h2>
|
||||
<a-tag class="ml-2" :color="getStatusColor(projectData.status)" v-if="projectData.status">{{
|
||||
projectData.status
|
||||
}}</a-tag>
|
||||
<a-tag class="ml-2" :color="getStatusColor(projectData.status)" v-if="projectData.status">{{ projectData.status }}</a-tag>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<a-button v-permission="['project:update']" type="primary" class="mr-2" @click="editProject">
|
||||
|
@ -26,18 +24,17 @@
|
|||
<a-tab-pane key="1" title="详细信息">
|
||||
<a-card class="general-card" title="项目基本信息">
|
||||
<a-descriptions :data="projectInfos" layout="horizontal" bordered column="3" />
|
||||
|
||||
|
||||
<a-divider />
|
||||
|
||||
|
||||
<div class="card-header">
|
||||
<div class="card-title">项目收支情况</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="finance-cards grid grid-cols-3 gap-4 mb-6">
|
||||
<a-card class="finance-card">
|
||||
<div class="finance-title">利润</div>
|
||||
<div class="finance-amount" :class="{ 'text-danger': finance.profit < 0 }">{{ finance.profit.toFixed(2) }}
|
||||
</div>
|
||||
<div class="finance-amount" :class="{ 'text-danger': finance.profit < 0 }">{{ finance.profit.toFixed(2) }}</div>
|
||||
</a-card>
|
||||
<a-card class="finance-card">
|
||||
<div class="finance-title">收入</div>
|
||||
|
@ -48,11 +45,11 @@
|
|||
<div class="finance-amount text-warning">{{ finance.expense.toFixed(2) }}</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
|
||||
<a-descriptions title="合同金额" :data="contractInfos" layout="horizontal" bordered />
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
|
||||
<a-tab-pane key="2" title="执行情况">
|
||||
<a-card class="general-card" title="项目进度跟踪">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
|
@ -69,7 +66,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<a-card :bordered="false" class="mt-4">
|
||||
<a-row :gutter="16" class="task-container">
|
||||
<a-col :span="6" v-for="(column, index) in taskColumns" :key="index">
|
||||
|
@ -78,34 +75,38 @@
|
|||
<span class="column-title">{{ column.title }}</span>
|
||||
<a-tag>{{ column.tasks.length }}</a-tag>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="task-list">
|
||||
<div v-for="task in column.tasks" :key="task.id" class="task-card" @click="openTaskDetail(task)">
|
||||
<div
|
||||
v-for="task in column.tasks"
|
||||
:key="task.id"
|
||||
class="task-card"
|
||||
@click="openTaskDetail(task)"
|
||||
>
|
||||
<div class="task-card-title">{{ task.taskName }}</div>
|
||||
<div class="task-card-desc text-gray-500 text-xs mt-1">{{ task.description || '暂无描述' }}</div>
|
||||
<div class="task-card-footer flex items-center justify-between mt-2">
|
||||
<div class="flex items-center">
|
||||
<icon-calendar class="mr-1 text-gray-500" />
|
||||
<span class="text-xs">{{ formatDate(task.taskPeriod?.[0]) }} - {{
|
||||
formatDate(task.taskPeriod?.[1])
|
||||
}}</span>
|
||||
<span class="text-xs">{{ formatDate(task.taskPeriod?.[0]) }} - {{ formatDate(task.taskPeriod?.[1]) }}</span>
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<a-progress :percent="task.progress" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-card-members mt-2">
|
||||
<a-avatar :size="24" v-for="(user, idx) in task.participants?.slice(0, 3)" :key="idx">{{
|
||||
user.substring(0, 1) }}</a-avatar>
|
||||
<a-avatar :size="24" v-if="task.participants?.length > 3">+{{ task.participants.length - 3
|
||||
}}</a-avatar>
|
||||
<a-avatar :size="24" v-for="(user, idx) in task.participants?.slice(0, 3)" :key="idx">{{ user.substring(0, 1) }}</a-avatar>
|
||||
<a-avatar :size="24" v-if="task.participants?.length > 3">+{{ task.participants.length - 3 }}</a-avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="add-task-placeholder" @click="handleAddTaskClick(column.status)">
|
||||
<icon-plus />
|
||||
<span>添加任务</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="add-task-placeholder"
|
||||
@click="handleAddTaskClick(column.status)"
|
||||
>
|
||||
<icon-plus />
|
||||
<span>添加任务</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -113,11 +114,15 @@
|
|||
</a-card>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
|
||||
<a-tab-pane key="3" title="附件">
|
||||
<a-card class="general-card" title="项目附件">
|
||||
<div class="attachment-section">
|
||||
<a-upload list-type="picture-card" :file-list="attachments" @change="handleAttachmentChange">
|
||||
<a-upload
|
||||
list-type="picture-card"
|
||||
:file-list="attachments"
|
||||
@change="handleAttachmentChange"
|
||||
>
|
||||
<template #upload-button>
|
||||
<div class="upload-button-content">
|
||||
<icon-plus />
|
||||
|
@ -131,9 +136,13 @@
|
|||
</a-tabs>
|
||||
|
||||
<!-- 新增任务弹窗 -->
|
||||
<a-modal v-model:visible="addTaskModalVisible" title="新增任务" @cancel="resetTaskForm" @before-ok="handleTaskSubmit">
|
||||
<a-form ref="taskFormRef" :model="taskForm" label-position="left" :label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 18 }">
|
||||
<a-modal
|
||||
v-model:visible="addTaskModalVisible"
|
||||
title="新增任务"
|
||||
@cancel="resetTaskForm"
|
||||
@before-ok="handleTaskSubmit"
|
||||
>
|
||||
<a-form ref="taskFormRef" :model="taskForm" label-position="left" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
|
||||
<a-form-item field="taskName" label="任务名称" required>
|
||||
<a-input v-model="taskForm.taskName" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
|
@ -163,10 +172,13 @@
|
|||
</a-modal>
|
||||
|
||||
<!-- 新增任务组弹窗 -->
|
||||
<a-modal v-model:visible="addTaskGroupModalVisible" title="新增任务组" @cancel="resetTaskGroupForm"
|
||||
@before-ok="handleTaskGroupSubmit">
|
||||
<a-form ref="taskGroupFormRef" :model="taskGroupForm" label-position="left" :label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 18 }">
|
||||
<a-modal
|
||||
v-model:visible="addTaskGroupModalVisible"
|
||||
title="新增任务组"
|
||||
@cancel="resetTaskGroupForm"
|
||||
@before-ok="handleTaskGroupSubmit"
|
||||
>
|
||||
<a-form ref="taskGroupFormRef" :model="taskGroupForm" label-position="left" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
|
||||
<a-form-item field="groupName" label="组名称" required>
|
||||
<a-input v-model="taskGroupForm.groupName" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
|
@ -174,14 +186,18 @@
|
|||
</a-modal>
|
||||
|
||||
<!-- 任务详情弹窗 -->
|
||||
<a-modal v-model:visible="taskDetailModalVisible" title="任务详情" @cancel="closeTaskDetail">
|
||||
<a-modal
|
||||
v-model:visible="taskDetailModalVisible"
|
||||
title="任务详情"
|
||||
@cancel="closeTaskDetail"
|
||||
>
|
||||
<div v-if="currentTask">
|
||||
<a-descriptions :data="taskDetailInfos" layout="horizontal" bordered />
|
||||
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>任务进度</h4>
|
||||
<a-progress :percent="currentTask.progress || 0" />
|
||||
|
||||
|
||||
<a-form class="mt-4">
|
||||
<a-form-item label="更新进度">
|
||||
<a-input-number v-model="updateProgress" :min="0" :max="100" style="width: 100%" />
|
||||
|
@ -273,7 +289,7 @@ const contractInfos = computed(() => [
|
|||
|
||||
const taskDetailInfos = computed(() => {
|
||||
if (!currentTask.value) return []
|
||||
|
||||
|
||||
return [
|
||||
{ label: '任务名称', value: currentTask.value.taskName },
|
||||
{ label: '任务编号', value: currentTask.value.taskCode },
|
||||
|
@ -334,19 +350,19 @@ const fetchProjectData = async () => {
|
|||
|
||||
const fetchTaskData = async () => {
|
||||
try {
|
||||
const res = await listTask({
|
||||
const res = await listTask({
|
||||
projectId: projectId.value,
|
||||
page: 1,
|
||||
size: 100
|
||||
})
|
||||
|
||||
|
||||
// 重置任务列表
|
||||
taskColumns.value.forEach(column => {
|
||||
column.tasks = []
|
||||
})
|
||||
|
||||
|
||||
const tasks = res.data?.list || []
|
||||
|
||||
|
||||
// 分配任务到对应的列
|
||||
tasks.forEach((task: any) => {
|
||||
const column = taskColumns.value.find(col => col.status === task.status)
|
||||
|
@ -478,10 +494,10 @@ const submitProgressUpdate = async () => {
|
|||
try {
|
||||
await updateTaskProgress({ progress: updateProgress.value }, currentTask.value.id)
|
||||
Message.success('更新进度成功')
|
||||
|
||||
|
||||
// 更新本地数据
|
||||
currentTask.value.progress = updateProgress.value
|
||||
|
||||
|
||||
// 刷新任务列表
|
||||
await fetchTaskData()
|
||||
} catch (error) {
|
||||
|
@ -500,41 +516,33 @@ onMounted(() => {
|
|||
.finance-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.finance-title {
|
||||
color: #7f7f7f;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.finance-amount {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f53f3f;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #00b42a;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #ff7d00;
|
||||
}
|
||||
|
||||
.task-container {
|
||||
overflow-x: auto;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.task-column {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.task-column-header {
|
||||
padding: 12px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
@ -543,11 +551,9 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
|
@ -557,20 +563,16 @@ onMounted(() => {
|
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.task-card:hover {
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.task-card-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.task-card-members {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.add-task-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -582,31 +584,26 @@ onMounted(() => {
|
|||
cursor: pointer;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.add-task-placeholder:hover {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.upload-button-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.general-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -1,358 +0,0 @@
|
|||
<template>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" width="80px" height="160px" viewBox="0 0 210 234" enable-background="new 0 0 210 234"
|
||||
xml:space="preserve">
|
||||
<image id="image0" width="210" height="234" x="0" y="0" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANIAAADqCAMAAADgbuuwAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
|
||||
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACc1BMVEXx8fH29vb8+/z////+
|
||||
/v74+Pjz8/Py8vLv7+/39/fs7Oz6+vrw8PD19fX5+fn9/f3q6up+fn60tLTu7u7p6ekPDw9SUVLr
|
||||
6+vo6OiZmZkHBwcBAQHMzMwAAAA7Ozvn5+fk5OSrq6vj4+Pl5eUkJCRvb2/c3Nx4eHgFBQWysrIQ
|
||||
EBANDQ03Nzevr68MDAxHR0cXFxeQkJAKCgoDAwMYGBjFxcUJCQlxcXEICAixsbEVFRVBQUECAgIG
|
||||
BgaamprV1dVbW1sUFBTJycmNjY0TExO4uLgbGxvd3d2lpaXh4eFjY2O+vr5/f38rKytmZmZFRUUt
|
||||
LS23t7epqana2tqWlpbGxsaIiIiwsLB5eXmBgYGLi4uSkpKzs7PBwcEREREODg4zMzNWVlYjIyN2
|
||||
dnZYWFibm5vS0tLb29vPz884ODhAQEDe3t7U1NQcHBx7e3u2trZtbW3T09MpKSlMTEykpKTZ2dks
|
||||
LCxqamq7u7teXl4/Pz9sbGzNzc1kZGQWFhaPj4+hoaF9fX3IyMgdHR1iYmK9vb2AgIDi4uLCwsLX
|
||||
19fExMSOjo5LS0tdXV2npqesrKwiIiKoqKg5OTmHh4coKCi/v78vLy/g4OA2NjZoaGgxMTFPT08m
|
||||
JiYhISFfX19CQkI+Pj5ycnLQ0NBpaWlhYWEeHh7Hx8dwcHBzc3OXl5fAwMA9PT1VVVWUlJR6enpr
|
||||
a2tERES5ublTU1NaWlqVlZUfHx8aGhpJSUmGhoaTk5Otra2ioqLOzs6dnZ26urpcXFxlZWWgoKBN
|
||||
TU2FhYWJiYkyMjIwMDDW1tZQUFCEhISMjIxXV1dOTk4lJSVISEiCgoJUVFSQ4NxnAAAAAWJLR0QD
|
||||
EQxM8gAAAAd0SU1FB+kHFwcmCqQ1L2MAAEkTSURBVHjazb2LnyXbVR62d712PXZVHVxnqEMfFz2a
|
||||
hqvjO2gQc9CoJbqRr/VCCeAABoRB94JsBEIgE/FKiCwhIFxQYuOA4JpHHBSQkYgBB5PEIQlxiO28
|
||||
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" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.turbine-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -12,12 +12,20 @@
|
|||
-->
|
||||
<template>
|
||||
<GiPageLayout>
|
||||
<GiTable row-key="id" :data="dataList" :columns="tableColumns" :loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }" :pagination="pagination" :disabled-tools="['size']"
|
||||
@page-change="onPageChange" @page-size-change="onPageSizeChange" @refresh="search">
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="tableColumns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #top>
|
||||
<GiForm v-model="searchForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset">
|
||||
</GiForm>
|
||||
<GiForm v-model="searchForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset"></GiForm>
|
||||
</template>
|
||||
<template #toolbar-left>
|
||||
<a-button v-permission="['project:create']" type="primary" @click="openAddModal">
|
||||
|
@ -63,7 +71,12 @@
|
|||
<a-space>
|
||||
<a-link v-permission="['project:detail']" title="详情" @click="viewDetail(record)">详情</a-link>
|
||||
<a-link v-permission="['project:update']" title="修改" @click="openEditModal(record)">修改</a-link>
|
||||
<a-link v-permission="['project:delete']" status="danger" title="删除" @click="confirmDelete(record)">
|
||||
<a-link
|
||||
v-permission="['project:delete']"
|
||||
status="danger"
|
||||
title="删除"
|
||||
@click="confirmDelete(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
</a-space>
|
||||
|
@ -71,10 +84,22 @@
|
|||
</GiTable>
|
||||
|
||||
<!-- 新增/编辑项目弹窗 -->
|
||||
<a-modal v-model:visible="addModalVisible" :title="modalTitle" @cancel="resetForm"
|
||||
:ok-button-props="{ loading: submitLoading }" @ok="handleSubmit" width="800px" modal-class="project-form-modal">
|
||||
<a-form ref="formRef" :model="form" :rules="formRules" layout="vertical"
|
||||
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }">
|
||||
<a-modal
|
||||
v-model:visible="addModalVisible"
|
||||
:title="modalTitle"
|
||||
@cancel="resetForm"
|
||||
:ok-button-props="{ loading: submitLoading }"
|
||||
@ok="handleSubmit"
|
||||
width="800px"
|
||||
modal-class="project-form-modal"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">基本信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
|
@ -83,17 +108,6 @@
|
|||
<a-input v-model="form.projectName" placeholder="请输入项目名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="farmAddress" label="地址">
|
||||
<a-input v-model="form.farmAddress" placeholder="请输入地址" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col><a-button size="mini" @click="() => { Message.info(`待开发`) }">
|
||||
<template #icon><icon-location /></template>
|
||||
地图选点
|
||||
</a-button></a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="projectManagerId" label="项目经理" required>
|
||||
<a-select v-model="form.projectManagerId" placeholder="请选择项目经理" :loading="userLoading">
|
||||
|
@ -104,54 +118,10 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionUnit" label="业主">
|
||||
<a-input v-model="form.inspectionUnit" placeholder="请输入业主单位" @input="(val) => (form.farmName = val)" />
|
||||
<!--风场名称同步业主 -->
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionContact" label="业主单位联系人">
|
||||
<a-input v-model="form.inspectionContact" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionPhone" label="业主单位联系电话">
|
||||
<a-input v-model="form.inspectionPhone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="client" label="委托单位">
|
||||
<a-input v-model="form.client" placeholder="请输入委托单位" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="clientContact" label="委托单位联系人">
|
||||
<a-input v-model="form.clientContact" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="clientPhone" label="委托单位联系电话">
|
||||
<a-input v-model="form.clientPhone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-form-item field="projectContent" label="项目内容">
|
||||
<a-textarea v-model="form.coverUrl" placeholder="请输入项目内容" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="status" label="项目状态">
|
||||
<a-form-item field="status" label="项目状态" >
|
||||
<a-select v-model="form.status" placeholder="请选择状态">
|
||||
<a-option v-for="option in PROJECT_STATUS_OPTIONS" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
|
@ -160,57 +130,166 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="scale" label="项目规模">
|
||||
<a-input-number v-model="form.scale" placeholder="请输入项目规模" :min="0" :max="999" :step="1" />
|
||||
<a-form-item field="scale" label="项目规模" >
|
||||
<a-input v-model="form.scale" placeholder="请输入项目规模" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="startDate" label="开始时间">
|
||||
<a-form-item field="startDate" label="开始时间" >
|
||||
<a-date-picker v-model="form.startDate" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="endDate" label="结束时间">
|
||||
<a-form-item field="endDate" label="结束时间" >
|
||||
<a-date-picker v-model="form.endDate" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item field="coverUrl" label="项目封面">
|
||||
<a-input v-model="form.coverUrl" placeholder="请输入项目封面URL" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 委托方信息 -->
|
||||
<a-divider orientation="left">委托方信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="turbineModel" label="风机型号">
|
||||
<a-form-item field="client" label="委托单位" >
|
||||
<a-input v-model="form.client" placeholder="请输入委托单位" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="clientContact" label="委托单位联系人" >
|
||||
<a-input v-model="form.clientContact" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="clientPhone" label="委托单位联系电话" >
|
||||
<a-input v-model="form.clientPhone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 检查方信息 -->
|
||||
<a-divider orientation="left">检查方信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionUnit" label="检查单位" >
|
||||
<a-input v-model="form.inspectionUnit" placeholder="请输入检查单位" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionContact" label="检查单位联系人" >
|
||||
<a-input v-model="form.inspectionContact" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="inspectionPhone" label="检查单位联系电话" >
|
||||
<a-input v-model="form.inspectionPhone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 风场信息 -->
|
||||
<a-divider orientation="left">风场信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="farmName" label="风场名称" >
|
||||
<a-input v-model="form.farmName" placeholder="请输入风场名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="farmAddress" label="风场地址" >
|
||||
<a-input v-model="form.farmAddress" placeholder="请输入风场地址" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="turbineModel" label="风机型号" >
|
||||
<a-input v-model="form.turbineModel" placeholder="请输入风机型号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 风场信息 -->
|
||||
<a-divider orientation="left">风场信息可视化</a-divider>
|
||||
|
||||
<!-- 项目团队 -->
|
||||
<a-divider orientation="left">项目团队</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="机组网格布局">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<TurbineGrid v-model:="form.turbineList"></TurbineGrid>
|
||||
</a-space>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="constructionTeamLeaderId" label="施工组长" >
|
||||
<a-select v-model="form.constructionTeamLeaderId" placeholder="请选择施工组长" :loading="userLoading">
|
||||
<a-option v-for="user in userOptions" :key="user.value" :value="user.value">
|
||||
{{ user.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="constructorIds" label="施工人员" >
|
||||
<a-select v-model="form.constructorIds" multiple placeholder="请选择施工人员" :loading="userLoading">
|
||||
<a-option v-for="user in userOptions" :key="user.value" :value="user.value">
|
||||
{{ user.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="qualityOfficerId" label="质量员" >
|
||||
<a-select v-model="form.qualityOfficerId" placeholder="请选择质量员" :loading="userLoading">
|
||||
<a-option v-for="user in userOptions" :key="user.value" :value="user.value">
|
||||
{{ user.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="auditorId" label="安全员" >
|
||||
<a-select v-model="form.auditorId" placeholder="请选择安全员" :loading="userLoading">
|
||||
<a-option v-for="user in userOptions" :key="user.value" :value="user.value">
|
||||
{{ user.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider orientation="middle">地图</a-divider>
|
||||
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 导入项目弹窗 -->
|
||||
<a-modal v-model:visible="importModalVisible" title="导入文件" @cancel="handleCancelImport" @before-ok="handleImport">
|
||||
<a-modal
|
||||
v-model:visible="importModalVisible"
|
||||
title="导入文件"
|
||||
@cancel="handleCancelImport"
|
||||
@before-ok="handleImport"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center p-8">
|
||||
<div class="text-primary text-4xl mb-4">
|
||||
<icon-file-upload />
|
||||
</div>
|
||||
<div class="text-lg font-medium mb-2">批量导入文件</div>
|
||||
<div class="text-gray-500 mb-4">拖动文件到此处,或点击下方按钮上传</div>
|
||||
<a-upload :file-list="fileList" :limit="1" @change="handleFileChange">
|
||||
<a-upload
|
||||
:file-list="fileList"
|
||||
:limit="1"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-button type="primary">选择文件</a-button>
|
||||
</template>
|
||||
|
@ -232,7 +311,7 @@ import type { ColumnItem } from '@/components/GiForm'
|
|||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import type { ProjectResp, ProjectPageQuery } from '@/apis/project/type'
|
||||
import * as T from '@/apis/project/type'
|
||||
import TurbineGrid from './TurbineGrid.vue'
|
||||
|
||||
defineOptions({ name: 'ProjectManagement' })
|
||||
|
||||
// 项目状态常量定义 (API返回数字类型)
|
||||
|
@ -245,7 +324,7 @@ const PROJECT_STATUS = {
|
|||
// 项目状态映射
|
||||
const PROJECT_STATUS_MAP = {
|
||||
0: '待施工',
|
||||
1: '施工中',
|
||||
1: '施工中',
|
||||
2: '已完成'
|
||||
} as const
|
||||
|
||||
|
@ -300,11 +379,11 @@ const queryFormColumns: ColumnItem[] = reactive([
|
|||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '业主',
|
||||
field: 'inspectionUnit',
|
||||
label: '风场名称',
|
||||
field: 'fieldName', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||
span: { xs: 24, sm: 8, xxl: 8 },
|
||||
props: {
|
||||
placeholder: '请输入业主名称',
|
||||
placeholder: '请输入风场名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -326,22 +405,21 @@ const form = reactive({
|
|||
client: '', // 委托单位
|
||||
clientContact: '', // 委托单位联系人
|
||||
clientPhone: '', // 委托单位联系电话
|
||||
inspectionUnit: '', // 业主单位
|
||||
inspectionContact: '', // 业主单位联系人
|
||||
inspectionPhone: '', // 业主单位联系电话
|
||||
farmName: '', // 风场名称 现在等同业主单位
|
||||
farmAddress: '', // 风场地址 项目地址
|
||||
scale: '', // 项目规模 风机数量
|
||||
inspectionUnit: '', // 检查单位
|
||||
inspectionContact: '', // 检查单位联系人
|
||||
inspectionPhone: '', // 检查单位联系电话
|
||||
farmName: '', // 风场名称
|
||||
farmAddress: '', // 风场地址
|
||||
scale: '', // 项目规模
|
||||
turbineModel: '', // 风机型号
|
||||
status: '', // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
||||
status: 0, // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
||||
startDate: '', // 开始时间
|
||||
endDate: '', // 结束时间
|
||||
coverUrl: '', // 项目封面 现在填的是项目内容
|
||||
coverUrl: '', // 项目封面
|
||||
constructionTeamLeaderId: '', // 施工组长id
|
||||
constructorIds: '', // 施工人员id
|
||||
qualityOfficerId: '', // 质量员id
|
||||
auditorId: '', // 安全员id
|
||||
turbineList: [] as { id: number; turbineNo: string; lat?: number; lng?: number; status: 0 | 1 | 2 }[], //风机组
|
||||
auditorId: '' // 安全员id
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
|
@ -352,9 +430,7 @@ const pagination = reactive({
|
|||
showJumper: true,
|
||||
showPageSize: true
|
||||
})
|
||||
const openMapModal = (item: any) => {
|
||||
Message.info(`地图选点功能待开发,当前机组编号:${item.turbineNo}`)
|
||||
}
|
||||
|
||||
const tableColumns = ref<TableColumnData[]>([
|
||||
{
|
||||
title: '序号',
|
||||
|
@ -363,91 +439,91 @@ const tableColumns = ref<TableColumnData[]>([
|
|||
render: ({ rowIndex }) => rowIndex + 1 + (pagination.current - 1) * pagination.pageSize,
|
||||
fixed: !isMobile() ? 'left' : undefined,
|
||||
},
|
||||
{
|
||||
title: '项目编号',
|
||||
dataIndex: 'projectCode',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
{
|
||||
title: '项目编号',
|
||||
dataIndex: 'projectCode',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
fixed: !isMobile() ? 'left' : undefined,
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'projectName',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'projectName',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
fixed: !isMobile() ? 'left' : undefined,
|
||||
},
|
||||
{
|
||||
title: '地点',
|
||||
slotName: 'fieldInfo',
|
||||
minWidth: 180,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '风场名称/风场地址',
|
||||
slotName: 'fieldInfo',
|
||||
minWidth: 180,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
align: 'center',
|
||||
width: 100
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '委托单位',
|
||||
dataIndex: 'commissionUnit',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '委托单位',
|
||||
dataIndex: 'commissionUnit',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '委托单位联系人/电话',
|
||||
slotName: 'commissionInfo',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '委托单位联系人/电话',
|
||||
slotName: 'commissionInfo',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '业主',
|
||||
dataIndex: 'inspectionUnit',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '检查单位',
|
||||
dataIndex: 'inspectionUnit',
|
||||
minWidth: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '业主联系人/电话',
|
||||
slotName: 'inspectionInfo',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '检查单位联系人/电话',
|
||||
slotName: 'inspectionInfo',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '项目规模',
|
||||
dataIndex: 'projectScale',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '项目规模',
|
||||
dataIndex: 'projectScale',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '机组型号',
|
||||
dataIndex: 'orgNumber',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '机组型号',
|
||||
dataIndex: 'orgNumber',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '项目经理/施工人员',
|
||||
slotName: 'projectManager',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '项目经理/施工人员',
|
||||
slotName: 'projectManager',
|
||||
minWidth: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '项目周期',
|
||||
slotName: 'projectPeriod',
|
||||
minWidth: 180,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
{
|
||||
title: '项目周期',
|
||||
slotName: 'projectPeriod',
|
||||
minWidth: 180,
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -457,20 +533,7 @@ const tableColumns = ref<TableColumnData[]>([
|
|||
fixed: !isMobile() ? 'right' : undefined,
|
||||
},
|
||||
])
|
||||
watch(() => form.scale, (newVal) => {
|
||||
const count = Number(newVal)
|
||||
if (count > 0 && count <= 999) {
|
||||
form.turbineList = Array.from({ length: count }, (_, i) => ({
|
||||
id: i + 1,
|
||||
turbineNo: `${String(i + 1).padStart(3, '0')}`,
|
||||
lat: undefined,
|
||||
lng: undefined,
|
||||
status: form.status,
|
||||
}))
|
||||
} else {
|
||||
form.turbineList = []
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const modalTitle = computed(() => isEdit.value ? '编辑项目' : '新增项目')
|
||||
|
||||
const getStatusColor = (status: number) => {
|
||||
|
@ -494,37 +557,37 @@ const fetchData = async () => {
|
|||
page: pagination.current,
|
||||
size: pagination.pageSize
|
||||
}
|
||||
|
||||
|
||||
const res = await listProject(params)
|
||||
|
||||
|
||||
if (res.success && res.data) {
|
||||
// API直接返回数组数据
|
||||
const projects = Array.isArray(res.data) ? res.data : []
|
||||
|
||||
|
||||
// 数据映射和兼容性处理
|
||||
dataList.value = projects.map((item: any) => {
|
||||
const mappedItem: T.ProjectResp = {
|
||||
...item,
|
||||
// 添加别名字段以保持兼容性
|
||||
id: item.projectId,
|
||||
fieldName: item.farmName,
|
||||
fieldLocation: item.farmAddress,
|
||||
commissionUnit: item.client,
|
||||
commissionContact: item.clientContact,
|
||||
commissionPhone: item.clientPhone,
|
||||
orgNumber: item.turbineModel,
|
||||
projectManager: item.projectManagerName,
|
||||
...item,
|
||||
// 添加别名字段以保持兼容性
|
||||
id: item.projectId,
|
||||
fieldName: item.farmName,
|
||||
fieldLocation: item.farmAddress,
|
||||
commissionUnit: item.client,
|
||||
commissionContact: item.clientContact,
|
||||
commissionPhone: item.clientPhone,
|
||||
orgNumber: item.turbineModel,
|
||||
projectManager: item.projectManagerName,
|
||||
projectScale: item.scale,
|
||||
// 处理项目周期
|
||||
// 处理项目周期
|
||||
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : []
|
||||
};
|
||||
return mappedItem;
|
||||
})
|
||||
|
||||
|
||||
// 由于API没有返回total,使用当前数据长度
|
||||
// 如果是完整数据,可以用数据长度;如果有分页,需要从其他地方获取total
|
||||
pagination.total = projects.length
|
||||
|
||||
|
||||
// 如果返回的数据少于每页大小,说明已经是最后一页
|
||||
if (projects.length < pagination.pageSize) {
|
||||
pagination.total = (pagination.current - 1) * pagination.pageSize + projects.length
|
||||
|
@ -556,7 +619,7 @@ const reset = () => {
|
|||
fieldName: '', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||
status: undefined,
|
||||
})
|
||||
|
||||
|
||||
// 重置分页并重新搜索
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
@ -598,7 +661,7 @@ const resetForm = () => {
|
|||
qualityOfficerId: '', // 质量员id
|
||||
auditorId: '' // 安全员id
|
||||
})
|
||||
|
||||
|
||||
isEdit.value = false
|
||||
currentId.value = null
|
||||
}
|
||||
|
@ -611,13 +674,13 @@ const openAddModal = () => {
|
|||
const openEditModal = (record: T.ProjectResp) => {
|
||||
isEdit.value = true
|
||||
currentId.value = record.id || record.projectId || null
|
||||
|
||||
|
||||
// 重置表单
|
||||
Object.keys(form).forEach(key => {
|
||||
// @ts-ignore
|
||||
form[key] = ''
|
||||
})
|
||||
|
||||
|
||||
// 填充表单数据
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key in record && record[key as keyof T.ProjectResp] !== undefined) {
|
||||
|
@ -625,7 +688,7 @@ const openEditModal = (record: T.ProjectResp) => {
|
|||
form[key] = record[key as keyof T.ProjectResp]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 处理特殊字段映射
|
||||
if (record.farmName) form.farmName = record.farmName
|
||||
if (record.farmAddress) form.farmAddress = record.farmAddress
|
||||
|
@ -634,11 +697,11 @@ const openEditModal = (record: T.ProjectResp) => {
|
|||
if (record.clientPhone) form.clientPhone = record.clientPhone
|
||||
if (record.turbineModel) form.turbineModel = record.turbineModel
|
||||
if (record.scale) form.scale = record.scale
|
||||
|
||||
|
||||
// 处理日期字段
|
||||
if (record.startDate) form.startDate = record.startDate
|
||||
if (record.endDate) form.endDate = record.endDate
|
||||
|
||||
|
||||
addModalVisible.value = true
|
||||
}
|
||||
|
||||
|
@ -653,12 +716,12 @@ const submitLoading = ref(false)
|
|||
const handleSubmit = async () => {
|
||||
console.log('表单提交开始', form)
|
||||
submitLoading.value = true
|
||||
|
||||
|
||||
try {
|
||||
// 表单验证
|
||||
console.log('开始验证表单', formRef.value)
|
||||
await formRef.value.validate()
|
||||
|
||||
|
||||
// 准备提交的数据
|
||||
const submitData = {
|
||||
...form,
|
||||
|
@ -672,7 +735,7 @@ const handleSubmit = async () => {
|
|||
}
|
||||
|
||||
console.log('提交数据:', submitData)
|
||||
|
||||
|
||||
let res
|
||||
if (isEdit.value && currentId.value) {
|
||||
// 编辑模式
|
||||
|
@ -683,19 +746,18 @@ const handleSubmit = async () => {
|
|||
// 新增模式
|
||||
console.log('新增模式提交')
|
||||
res = await addProject(submitData)
|
||||
|
||||
Message.success('添加成功')
|
||||
}
|
||||
|
||||
|
||||
console.log('API响应结果:', res)
|
||||
|
||||
|
||||
// 检查操作是否成功
|
||||
if (res && res.success === false) {
|
||||
Message.error(res.msg || '操作失败')
|
||||
submitLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
addModalVisible.value = false
|
||||
fetchData()
|
||||
} catch (error: any) {
|
||||
|
@ -725,16 +787,16 @@ const deleteItem = async (record: T.ProjectResp) => {
|
|||
Message.error('项目ID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const res = await deleteProject(projectId)
|
||||
|
||||
|
||||
// 检查删除操作是否成功
|
||||
if (res && res.success === false) {
|
||||
Message.error(res.msg || '删除失败')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Message.success('删除成功')
|
||||
fetchData()
|
||||
} catch (error) {
|
||||
|
@ -749,7 +811,7 @@ const viewDetail = (record: T.ProjectResp) => {
|
|||
Message.error('项目ID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
router.push({
|
||||
name: 'ProjectDetail',
|
||||
params: {
|
||||
|
@ -777,24 +839,24 @@ const handleImport = async () => {
|
|||
Message.warning('请选择文件')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const fileItem = fileList.value[0] as any
|
||||
const file = fileItem?.file || fileItem
|
||||
|
||||
|
||||
if (!file) {
|
||||
Message.warning('请选择有效的文件')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 调用导入API
|
||||
const res = await importProject(file)
|
||||
|
||||
|
||||
if (res && res.success === false) {
|
||||
Message.error(res.msg || '导入失败')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Message.success('导入成功')
|
||||
handleCancelImport()
|
||||
fetchData() // 重新获取数据
|
||||
|
@ -813,7 +875,7 @@ const exportData = async () => {
|
|||
status: searchForm.status,
|
||||
fieldName: searchForm.fieldName, // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||
}
|
||||
|
||||
|
||||
await exportProject(params)
|
||||
Message.success('导出成功')
|
||||
} catch (error) {
|
||||
|
@ -860,11 +922,11 @@ onMounted(() => {
|
|||
.arco-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
||||
.arco-divider {
|
||||
margin: 16px 0;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -1,242 +0,0 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
@update:visible="val => emit('update:visible', val)"
|
||||
:title="title"
|
||||
:width="900"
|
||||
:footer="true"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-descriptions :data="descriptionsData" bordered :column="2">
|
||||
<a-descriptions-item label="员工姓名">{{ data?.employeeName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="员工类型">{{ employeeTypeText }}</a-descriptions-item>
|
||||
<a-descriptions-item label="身份证号">{{ data?.idCard }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">{{ data?.phone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="银行卡号">{{ data?.bankCard }}</a-descriptions-item>
|
||||
<a-descriptions-item label="开户银行">{{ data?.bankName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="项目名称">{{ data?.projectName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="项目周期">{{ data?.projectPeriod }}</a-descriptions-item>
|
||||
<a-descriptions-item label="工作天数">{{ data?.workDays }}天</a-descriptions-item>
|
||||
<a-descriptions-item label="海上作业天数">{{ data?.offshoreDays }}天</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider orientation="left">薪酬明细</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="基本工资"
|
||||
:value="data?.baseSalary"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="绩效奖金"
|
||||
:value="data?.performanceBonus"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px">
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="各类补助"
|
||||
:value="data?.allowances"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="应发总额"
|
||||
:value="data?.totalPayable"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px">
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="扣款金额"
|
||||
:value="data?.deductions"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic
|
||||
title="实发金额"
|
||||
:value="data?.netPay"
|
||||
:precision="2"
|
||||
:value-style="{ color: '#52c41a', fontSize: '24px', fontWeight: 'bold' }"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider orientation="left">绩效评价</a-divider>
|
||||
<a-table :data="data?.performanceItems" :columns="performanceColumns" :pagination="false">
|
||||
<template #score="{ record }">
|
||||
<a-tag :color="getScoreColor(record.score, record.maxScore)">
|
||||
{{ record.score }}/{{ record.maxScore }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #amount="{ record }">
|
||||
¥{{ (record.score / record.maxScore * record.amount).toFixed(2) }}
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-divider orientation="left">补助明细</a-divider>
|
||||
<a-table :data="data?.allowanceItems" :columns="allowanceColumns" :pagination="false">
|
||||
<template #total="{ record }">
|
||||
¥{{ record.total.toFixed(2) }}
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-divider orientation="left">审批流程</a-divider>
|
||||
<a-steps :current="approvalCurrent" direction="vertical">
|
||||
<a-step
|
||||
v-for="step in data?.approvalFlow"
|
||||
:key="step.step"
|
||||
:title="step.step"
|
||||
:description="`${step.role} - ${step.approver}`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-check-circle v-if="step.status === 'approved'" style="color: #52c41a" />
|
||||
<icon-close-circle v-else-if="step.status === 'rejected'" style="color: #ff4d4f" />
|
||||
<icon-clock-circle v-else style="color: #faad14" />
|
||||
</template>
|
||||
</a-step>
|
||||
</a-steps>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">关闭</a-button>
|
||||
<a-button type="primary" @click="handleExport">
|
||||
<template #icon><icon-download /></template>
|
||||
导出Excel
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { SalaryRecord } from '../types'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
data: SalaryRecord | null
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const title = computed(() => `${props.data?.employeeName} - 工资详情`)
|
||||
|
||||
const employeeTypeText = computed(() => {
|
||||
const typeMap = {
|
||||
intern: '实习生',
|
||||
fulltime: '正式员工',
|
||||
parttime: '兼职员工',
|
||||
}
|
||||
return props.data?.employeeType ? typeMap[props.data.employeeType] : ''
|
||||
})
|
||||
|
||||
const approvalCurrent = computed(() => {
|
||||
if (!props.data?.approvalFlow) return 0
|
||||
const approvedSteps = props.data.approvalFlow.filter((step) => step.status === 'approved').length
|
||||
return approvedSteps
|
||||
})
|
||||
|
||||
const descriptionsData = computed(() => [
|
||||
{ label: '员工姓名', value: props.data?.employeeName },
|
||||
{ label: '员工类型', value: employeeTypeText.value },
|
||||
{ label: '身份证号', value: props.data?.idCard },
|
||||
{ label: '联系电话', value: props.data?.phone },
|
||||
{ label: '银行卡号', value: props.data?.bankCard },
|
||||
{ label: '开户银行', value: props.data?.bankName },
|
||||
{ label: '项目名称', value: props.data?.projectName },
|
||||
{ label: '项目周期', value: props.data?.projectPeriod },
|
||||
{ label: '工作天数', value: `${props.data?.workDays}天` },
|
||||
{ label: '海上作业天数', value: `${props.data?.offshoreDays}天` },
|
||||
])
|
||||
|
||||
const performanceColumns = [
|
||||
{ title: '评价项', dataIndex: 'name', width: 200 },
|
||||
{ title: '权重', dataIndex: 'weight', width: 80 },
|
||||
{ title: '得分', dataIndex: 'score', slotName: 'score', width: 100 },
|
||||
{ title: '满分', dataIndex: 'maxScore', width: 80 },
|
||||
{ title: '金额', dataIndex: 'amount', slotName: 'amount', width: 100 },
|
||||
]
|
||||
|
||||
const allowanceColumns = [
|
||||
{ title: '补助类型', dataIndex: 'name', width: 200 },
|
||||
{ title: '标准', dataIndex: 'amount', width: 100 },
|
||||
{ title: '数量', dataIndex: 'quantity', width: 100 },
|
||||
{ title: '小计', dataIndex: 'total', slotName: 'total', width: 100 },
|
||||
]
|
||||
|
||||
const getScoreColor = (score: number, maxScore: number) => {
|
||||
const ratio = score / maxScore
|
||||
if (ratio >= 0.9) return 'green'
|
||||
if (ratio >= 0.7) return 'blue'
|
||||
if (ratio >= 0.6) return 'orange'
|
||||
return 'red'
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
if (!props.data) return
|
||||
|
||||
// 构建导出数据
|
||||
const exportData = {
|
||||
employee: {
|
||||
name: props.data.employeeName,
|
||||
type: employeeTypeText.value,
|
||||
idCard: props.data.idCard,
|
||||
phone: props.data.phone,
|
||||
bankCard: props.data.bankCard,
|
||||
bankName: props.data.bankName,
|
||||
},
|
||||
project: {
|
||||
name: props.data.projectName,
|
||||
period: props.data.projectPeriod,
|
||||
workDays: props.data.workDays,
|
||||
offshoreDays: props.data.offshoreDays,
|
||||
},
|
||||
salary: {
|
||||
baseSalary: props.data.baseSalary,
|
||||
performanceBonus: props.data.performanceBonus,
|
||||
allowances: props.data.allowances,
|
||||
totalPayable: props.data.totalPayable,
|
||||
deductions: props.data.deductions,
|
||||
netPay: props.data.netPay,
|
||||
},
|
||||
performanceItems: props.data.performanceItems,
|
||||
allowanceItems: props.data.allowanceItems,
|
||||
}
|
||||
|
||||
// 触发下载
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${props.data.employeeName}_工资单_${props.data.projectPeriod}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
</script>
|
|
@ -1,468 +0,0 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
:visible="visible"
|
||||
@update:visible="val => emit('update:visible', val)"
|
||||
:title="title"
|
||||
:width="800"
|
||||
:footer="true"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
|
||||
<!-- 基本信息 -->
|
||||
<a-card title="基本信息" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="员工姓名" field="employeeName">
|
||||
<a-input v-model="formData.employeeName" placeholder="请输入员工姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="员工类型" field="employeeType">
|
||||
<a-select v-model="formData.employeeType" placeholder="请选择员工类型">
|
||||
<a-option value="intern">实习生</a-option>
|
||||
<a-option value="fulltime">正式员工</a-option>
|
||||
<a-option value="parttime">兼职员工</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="身份证号" field="idCard">
|
||||
<a-input v-model="formData.idCard" placeholder="请输入身份证号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" field="phone">
|
||||
<a-input v-model="formData.phone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="银行卡号" field="bankCard">
|
||||
<a-input v-model="formData.bankCard" placeholder="请输入银行卡号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="开户银行" field="bankName">
|
||||
<a-input v-model="formData.bankName" placeholder="请输入开户银行" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 项目信息 -->
|
||||
<a-card title="项目信息" :bordered="false" style="margin-top: 16px">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="项目名称" field="projectName">
|
||||
<a-input v-model="formData.projectName" placeholder="请输入项目名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="项目周期" field="projectPeriod">
|
||||
<a-input v-model="formData.projectPeriod" placeholder="如:2025-07" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工作天数" field="workDays">
|
||||
<a-input-number
|
||||
v-model="formData.workDays"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
placeholder="请输入工作天数"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="海上作业天数" field="offshoreDays">
|
||||
<a-input-number
|
||||
v-model="formData.offshoreDays"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
placeholder="请输入海上作业天数"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 薪酬计算 -->
|
||||
<a-card title="薪酬计算" :bordered="false" style="margin-top: 16px">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="基本工资标准" field="baseSalaryStandard">
|
||||
<a-input-number
|
||||
v-model="formData.baseSalaryStandard"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入基本工资标准"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="基本工资" field="baseSalary">
|
||||
<a-input-number
|
||||
v-model="formData.baseSalary"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="自动计算"
|
||||
style="width: 100%"
|
||||
readonly
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 绩效评价 -->
|
||||
<a-divider orientation="left">绩效评价</a-divider>
|
||||
<a-table :data="formData.performanceItems" :columns="performanceColumns" :pagination="false">
|
||||
<template #score="{ record, rowIndex }">
|
||||
<a-input-number
|
||||
v-model="record.score"
|
||||
:min="0"
|
||||
:max="record.maxScore"
|
||||
:precision="0"
|
||||
@change="calculatePerformance"
|
||||
/>
|
||||
</template>
|
||||
<template #amount="{ record }">
|
||||
{{ record.amount }}
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 补助明细 -->
|
||||
<a-divider orientation="left">补助明细</a-divider>
|
||||
<a-table :data="formData.allowanceItems" :columns="allowanceColumns" :pagination="false">
|
||||
<template #quantity="{ record, rowIndex }">
|
||||
<a-input-number
|
||||
v-model="record.quantity"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
@change="calculateAllowances"
|
||||
/>
|
||||
</template>
|
||||
<template #total="{ record }">
|
||||
{{ record.total }}
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 计算结果 -->
|
||||
<a-divider orientation="left">计算结果</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="应发总额">
|
||||
<a-input-number
|
||||
v-model="formData.totalPayable"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
readonly
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="扣款金额">
|
||||
<a-input-number
|
||||
v-model="formData.deductions"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入扣款金额"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="实发金额">
|
||||
<a-input-number
|
||||
v-model="formData.netPay"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
readonly
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" @click="handleSubmit">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { SalaryRecord, AllowanceItem, PerformanceItem } from '../types'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
data: SalaryRecord | null
|
||||
mode: 'add' | 'edit'
|
||||
}
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const formRef = ref()
|
||||
const formData = reactive<SalaryRecord>({
|
||||
id: '',
|
||||
employeeId: '',
|
||||
employeeName: '',
|
||||
employeeType: 'intern',
|
||||
idCard: '',
|
||||
phone: '',
|
||||
bankCard: '',
|
||||
bankName: '',
|
||||
projectName: '',
|
||||
projectPeriod: '',
|
||||
workDays: 0,
|
||||
offshoreDays: 0,
|
||||
baseSalary: 0,
|
||||
baseSalaryStandard: 3500,
|
||||
performanceBonus: 0,
|
||||
allowances: 0,
|
||||
performanceItems: [],
|
||||
allowanceItems: [],
|
||||
totalPayable: 0,
|
||||
deductions: 0,
|
||||
netPay: 0,
|
||||
status: 'draft',
|
||||
approvalFlow: [],
|
||||
salaryMonth: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
})
|
||||
|
||||
const performanceColumns = [
|
||||
{ title: '评价项', dataIndex: 'name', width: 200 },
|
||||
{ title: '权重', dataIndex: 'weight', width: 80 },
|
||||
{ title: '得分', dataIndex: 'score', slotName: 'score', width: 100 },
|
||||
{ title: '满分', dataIndex: 'maxScore', width: 80 },
|
||||
{ title: '金额', dataIndex: 'amount', slotName: 'amount', width: 100 },
|
||||
]
|
||||
|
||||
const allowanceColumns = [
|
||||
{ title: '补助类型', dataIndex: 'name', width: 200 },
|
||||
{ title: '标准', dataIndex: 'amount', width: 100 },
|
||||
{ title: '单位', dataIndex: 'unit', width: 80 },
|
||||
{ title: '数量', dataIndex: 'quantity', slotName: 'quantity', width: 100 },
|
||||
{ title: '小计', dataIndex: 'total', slotName: 'total', width: 100 },
|
||||
]
|
||||
|
||||
const title = computed(() => (props.mode === 'add' ? '新建工资单' : '编辑工资单'))
|
||||
|
||||
const rules = {
|
||||
employeeName: [{ required: true, message: '请输入员工姓名' }],
|
||||
employeeType: [{ required: true, message: '请选择员工类型' }],
|
||||
idCard: [{ required: true, message: '请输入身份证号' }],
|
||||
phone: [{ required: true, message: '请输入联系电话' }],
|
||||
projectName: [{ required: true, message: '请输入项目名称' }],
|
||||
workDays: [{ required: true, message: '请输入工作天数' }],
|
||||
}
|
||||
|
||||
// 初始化实习生默认配置
|
||||
const initInternConfig = () => {
|
||||
formData.performanceItems = [
|
||||
{
|
||||
id: '1',
|
||||
name: '平时沟通态度',
|
||||
weight: 30,
|
||||
score: 0,
|
||||
maxScore: 100,
|
||||
amount: 200,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '积极主动性',
|
||||
weight: 50,
|
||||
score: 0,
|
||||
maxScore: 100,
|
||||
amount: 200,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '成长性心态',
|
||||
weight: 20,
|
||||
score: 0,
|
||||
maxScore: 100,
|
||||
amount: 100,
|
||||
},
|
||||
]
|
||||
|
||||
formData.allowanceItems = [
|
||||
{
|
||||
type: 'construction',
|
||||
name: '施工绩效',
|
||||
amount: 100,
|
||||
unit: 'day',
|
||||
quantity: 0,
|
||||
total: 0,
|
||||
},
|
||||
{
|
||||
type: 'offshore',
|
||||
name: '海上作业补助',
|
||||
amount: 30,
|
||||
unit: 'day',
|
||||
quantity: 0,
|
||||
total: 0,
|
||||
},
|
||||
{
|
||||
type: 'meal',
|
||||
name: '务餐补助',
|
||||
amount: 45,
|
||||
unit: 'day',
|
||||
quantity: 0,
|
||||
total: 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 计算基本工资
|
||||
const calculateBaseSalary = () => {
|
||||
formData.baseSalary = (formData.baseSalaryStandard / 30) * formData.workDays
|
||||
calculateTotal()
|
||||
}
|
||||
|
||||
// 计算绩效奖金
|
||||
const calculatePerformance = () => {
|
||||
formData.performanceBonus = formData.performanceItems.reduce(
|
||||
(sum, item) => sum + (item.score / item.maxScore) * item.amount,
|
||||
0,
|
||||
)
|
||||
calculateTotal()
|
||||
}
|
||||
|
||||
// 计算补助
|
||||
const calculateAllowances = () => {
|
||||
formData.allowanceItems.forEach((item) => {
|
||||
item.total = item.amount * item.quantity
|
||||
})
|
||||
formData.allowances = formData.allowanceItems.reduce((sum, item) => sum + item.total, 0)
|
||||
calculateTotal()
|
||||
}
|
||||
|
||||
// 计算总额
|
||||
const calculateTotal = () => {
|
||||
formData.totalPayable = formData.baseSalary + formData.performanceBonus + formData.allowances
|
||||
formData.netPay = formData.totalPayable - formData.deductions
|
||||
}
|
||||
|
||||
// 监听工作天数变化
|
||||
watch(
|
||||
() => formData.workDays,
|
||||
() => {
|
||||
calculateBaseSalary()
|
||||
// 更新务餐补助数量
|
||||
const mealItem = formData.allowanceItems.find((item) => item.type === 'meal')
|
||||
if (mealItem) {
|
||||
mealItem.quantity = formData.workDays
|
||||
calculateAllowances()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听海上作业天数变化
|
||||
watch(
|
||||
() => formData.offshoreDays,
|
||||
() => {
|
||||
// 更新海上作业补助数量
|
||||
const offshoreItem = formData.allowanceItems.find((item) => item.type === 'offshore')
|
||||
if (offshoreItem) {
|
||||
offshoreItem.quantity = formData.offshoreDays
|
||||
calculateAllowances()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听扣款变化
|
||||
watch(() => formData.deductions, calculateTotal)
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
// 这里调用API保存数据
|
||||
Message.success('保存成功')
|
||||
emit('success')
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.data,
|
||||
(newData) => {
|
||||
if (newData) {
|
||||
Object.assign(formData, newData)
|
||||
} else {
|
||||
// 重置表单
|
||||
Object.assign(formData, {
|
||||
id: '',
|
||||
employeeId: '',
|
||||
employeeName: '',
|
||||
employeeType: 'intern',
|
||||
idCard: '',
|
||||
phone: '',
|
||||
bankCard: '',
|
||||
bankName: '',
|
||||
projectName: '',
|
||||
projectPeriod: '',
|
||||
workDays: 0,
|
||||
offshoreDays: 0,
|
||||
baseSalary: 0,
|
||||
baseSalaryStandard: 3500,
|
||||
performanceBonus: 0,
|
||||
allowances: 0,
|
||||
performanceItems: [],
|
||||
allowanceItems: [],
|
||||
totalPayable: 0,
|
||||
deductions: 0,
|
||||
netPay: 0,
|
||||
status: 'draft',
|
||||
approvalFlow: [],
|
||||
salaryMonth: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
})
|
||||
initInternConfig()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 监听员工类型变化
|
||||
watch(
|
||||
() => formData.employeeType,
|
||||
(newType) => {
|
||||
if (newType === 'intern') {
|
||||
initInternConfig()
|
||||
} else {
|
||||
// 其他员工类型的配置
|
||||
formData.performanceItems = []
|
||||
formData.allowanceItems = []
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
|
@ -1,229 +0,0 @@
|
|||
<template>
|
||||
<div class="salary-management">
|
||||
<GiPageLayout>
|
||||
<template #header>
|
||||
<a-row justify="space-between" align="center">
|
||||
<a-col>
|
||||
<h1>工资管理系统</h1>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
新建工资单
|
||||
</a-button>
|
||||
<a-button @click="handleExport">
|
||||
<template #icon><icon-download /></template>
|
||||
导出Excel
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<a-card>
|
||||
<a-row :gutter="16" class="search-section">
|
||||
<a-col :span="6">
|
||||
<a-input
|
||||
v-model="searchParams.keyword"
|
||||
placeholder="搜索员工姓名/工号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model="searchParams.employeeType"
|
||||
placeholder="员工类型"
|
||||
allow-clear
|
||||
>
|
||||
<a-option value="intern">实习生</a-option>
|
||||
<a-option value="fulltime">正式员工</a-option>
|
||||
<a-option value="parttime">兼职员工</a-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model="searchParams.dateRange"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1500 }"
|
||||
>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="text" @click="handleDetail(record)">详情</a-button>
|
||||
<a-button type="text" status="danger" @click="handleDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</GiPageLayout>
|
||||
|
||||
<SalaryFormDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:data="currentData"
|
||||
:mode="drawerMode"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
|
||||
<SalaryDetailModal
|
||||
v-model:visible="detailVisible"
|
||||
:data="currentData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import SalaryFormDrawer from './components/SalaryFormDrawer.vue'
|
||||
import SalaryDetailModal from './components/SalaryDetailModal.vue'
|
||||
import type { SalaryRecord } from './types'
|
||||
|
||||
const loading = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const drawerMode = ref<'add' | 'edit'>('add')
|
||||
const currentData = ref<SalaryRecord | null>(null)
|
||||
|
||||
const searchParams = reactive({
|
||||
keyword: '',
|
||||
employeeType: '',
|
||||
dateRange: [],
|
||||
})
|
||||
|
||||
const tableData = ref<SalaryRecord[]>([])
|
||||
|
||||
const columns = [
|
||||
{ title: '员工姓名', dataIndex: 'employeeName', fixed: 'left', width: 100 },
|
||||
{ title: '员工类型', dataIndex: 'employeeType', width: 100 },
|
||||
{ title: '项目名称', dataIndex: 'projectName', width: 150 },
|
||||
{ title: '工作天数', dataIndex: 'workDays', width: 80 },
|
||||
{ title: '基本工资', dataIndex: 'baseSalary', width: 100 },
|
||||
{ title: '绩效奖金', dataIndex: 'performanceBonus', width: 100 },
|
||||
{ title: '各类补助', dataIndex: 'allowances', width: 100 },
|
||||
{ title: '应发总额', dataIndex: 'totalPayable', width: 100 },
|
||||
{ title: '实发金额', dataIndex: 'netPay', width: 100 },
|
||||
{ title: '状态', dataIndex: 'status', width: 80 },
|
||||
{ title: '创建时间', dataIndex: 'createdAt', width: 150 },
|
||||
{ title: '操作', slotName: 'action', fixed: 'right', width: 150 },
|
||||
]
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟数据
|
||||
tableData.value = [
|
||||
{
|
||||
id: '1',
|
||||
employeeId: 'E001',
|
||||
employeeName: '张三',
|
||||
employeeType: 'intern',
|
||||
idCard: '123456789012345678',
|
||||
phone: '13800000000',
|
||||
bankCard: '6222021234567890',
|
||||
bankName: '中国银行',
|
||||
projectName: '海上风电项目A',
|
||||
projectPeriod: '2025-06',
|
||||
workDays: 22,
|
||||
offshoreDays: 5,
|
||||
baseSalary: 3500,
|
||||
baseSalaryStandard: 3500,
|
||||
performanceBonus: 2200,
|
||||
allowances: 660,
|
||||
performanceItems: [],
|
||||
allowanceItems: [],
|
||||
totalPayable: 6360,
|
||||
deductions: 0,
|
||||
netPay: 6360,
|
||||
status: 'approved',
|
||||
approvalFlow: [],
|
||||
salaryMonth: '2025-07',
|
||||
createdAt: '2025-07-01',
|
||||
updatedAt: '2025-07-01',
|
||||
},
|
||||
]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
drawerMode.value = 'add'
|
||||
currentData.value = null
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record: SalaryRecord) => {
|
||||
drawerMode.value = 'edit'
|
||||
currentData.value = record
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
const handleDetail = (record: SalaryRecord) => {
|
||||
currentData.value = record
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (_record: SalaryRecord) => {
|
||||
try {
|
||||
await Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条记录吗?',
|
||||
})
|
||||
Message.success('删除成功')
|
||||
loadData()
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchParams.keyword = ''
|
||||
searchParams.employeeType = ''
|
||||
searchParams.dateRange = []
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
// 导出Excel逻辑
|
||||
Message.success('导出功能开发中...')
|
||||
}
|
||||
|
||||
const handleSuccess = () => {
|
||||
drawerVisible.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.salary-management {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
|
@ -1,127 +0,0 @@
|
|||
// 员工类型
|
||||
export type EmployeeType = 'intern' | 'fulltime' | 'parttime'
|
||||
|
||||
// 工资单状态
|
||||
export type SalaryStatus = 'draft' | 'pending' | 'approved' | 'rejected' | 'paid'
|
||||
|
||||
// 补助类型
|
||||
export interface AllowanceItem {
|
||||
type: string
|
||||
name: string
|
||||
amount: number
|
||||
unit: 'day' | 'project' | 'fixed'
|
||||
quantity: number
|
||||
total: number
|
||||
}
|
||||
|
||||
// 绩效评价项
|
||||
export interface PerformanceItem {
|
||||
id: string
|
||||
name: string
|
||||
weight: number
|
||||
score: number
|
||||
maxScore: number
|
||||
amount: number
|
||||
description?: string
|
||||
}
|
||||
|
||||
// 工资记录
|
||||
export interface SalaryRecord {
|
||||
id: string
|
||||
employeeId: string
|
||||
employeeName: string
|
||||
employeeType: EmployeeType
|
||||
idCard: string
|
||||
phone: string
|
||||
bankCard: string
|
||||
bankName: string
|
||||
|
||||
// 项目信息
|
||||
projectName: string
|
||||
projectPeriod: string
|
||||
workDays: number
|
||||
offshoreDays: number
|
||||
|
||||
// 薪酬结构
|
||||
baseSalary: number
|
||||
baseSalaryStandard: number
|
||||
performanceBonus: number
|
||||
allowances: number
|
||||
performanceItems: PerformanceItem[]
|
||||
allowanceItems: AllowanceItem[]
|
||||
|
||||
// 计算结果
|
||||
totalPayable: number
|
||||
deductions: number
|
||||
netPay: number
|
||||
|
||||
// 状态
|
||||
status: SalaryStatus
|
||||
approvalFlow: ApprovalStep[]
|
||||
|
||||
// 时间
|
||||
salaryMonth: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// 审批步骤
|
||||
export interface ApprovalStep {
|
||||
step: string
|
||||
role: string
|
||||
approver: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
comment?: string
|
||||
date?: string
|
||||
}
|
||||
|
||||
// 查询参数
|
||||
export interface SalaryQuery {
|
||||
keyword?: string
|
||||
employeeType?: EmployeeType
|
||||
status?: SalaryStatus
|
||||
dateRange?: string[]
|
||||
projectName?: string
|
||||
}
|
||||
|
||||
// 实习生专用配置
|
||||
export interface InternConfig {
|
||||
baseSalaryStandard: {
|
||||
withCertification: number
|
||||
withoutCertification: number
|
||||
}
|
||||
performanceRates: {
|
||||
construction: number
|
||||
offshore: number
|
||||
}
|
||||
allowances: {
|
||||
offshoreCert: number
|
||||
towerWork: {
|
||||
land: {
|
||||
bladeInspection: number
|
||||
lightning: number
|
||||
}
|
||||
sea: {
|
||||
bladeInspection: number
|
||||
lightning: number
|
||||
}
|
||||
}
|
||||
droneWork: number
|
||||
meal: number
|
||||
}
|
||||
}
|
||||
|
||||
// 工资单创建请求
|
||||
export interface SalaryCreateRequest {
|
||||
employeeId: string
|
||||
projectId: string
|
||||
workDays: number
|
||||
offshoreDays: number
|
||||
performanceItems: Omit<PerformanceItem, 'id'>[]
|
||||
allowanceItems: Omit<AllowanceItem, 'total'>[]
|
||||
deductions?: {
|
||||
type: string
|
||||
amount: number
|
||||
reason: string
|
||||
}[]
|
||||
}
|
|
@ -1,255 +0,0 @@
|
|||
import * as XLSX from 'xlsx'
|
||||
import type { SalaryRecord } from '../types'
|
||||
|
||||
// 导出工资单到Excel
|
||||
export const exportSalaryToExcel = (records: SalaryRecord[], filename: string) => {
|
||||
// 创建工作簿
|
||||
const workbook = XLSX.utils.book_new()
|
||||
|
||||
// 主表数据
|
||||
const mainData = records.map((record) => ({
|
||||
员工姓名: record.employeeName,
|
||||
员工类型: getEmployeeTypeText(record.employeeType),
|
||||
身份证号: record.idCard,
|
||||
联系电话: record.phone,
|
||||
银行卡号: record.bankCard,
|
||||
开户银行: record.bankName,
|
||||
项目名称: record.projectName,
|
||||
项目周期: record.projectPeriod,
|
||||
工作天数: record.workDays,
|
||||
海上作业天数: record.offshoreDays,
|
||||
基本工资: record.baseSalary,
|
||||
绩效奖金: record.performanceBonus,
|
||||
各类补助: record.allowances,
|
||||
应发总额: record.totalPayable,
|
||||
扣款金额: record.deductions,
|
||||
实发金额: record.netPay,
|
||||
状态: getStatusText(record.status),
|
||||
创建时间: record.createdAt,
|
||||
}))
|
||||
|
||||
// 创建主表
|
||||
const mainSheet = XLSX.utils.json_to_sheet(mainData)
|
||||
XLSX.utils.book_append_sheet(workbook, mainSheet, '工资汇总')
|
||||
|
||||
// 绩效明细表
|
||||
const performanceData: any[] = []
|
||||
records.forEach((record) => {
|
||||
record.performanceItems.forEach((item) => {
|
||||
performanceData.push({
|
||||
员工姓名: record.employeeName,
|
||||
项目名称: record.projectName,
|
||||
评价项: item.name,
|
||||
权重: item.weight,
|
||||
得分: item.score,
|
||||
满分: item.maxScore,
|
||||
金额: (item.score / item.maxScore * item.amount).toFixed(2),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (performanceData.length > 0) {
|
||||
const performanceSheet = XLSX.utils.json_to_sheet(performanceData)
|
||||
XLSX.utils.book_append_sheet(workbook, performanceSheet, '绩效明细')
|
||||
}
|
||||
|
||||
// 补助明细表
|
||||
const allowanceData: any[] = []
|
||||
records.forEach((record) => {
|
||||
record.allowanceItems.forEach((item) => {
|
||||
allowanceData.push({
|
||||
员工姓名: record.employeeName,
|
||||
项目名称: record.projectName,
|
||||
补助类型: item.name,
|
||||
标准: item.amount,
|
||||
单位: getUnitText(item.unit),
|
||||
数量: item.quantity,
|
||||
小计: item.total,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (allowanceData.length > 0) {
|
||||
const allowanceSheet = XLSX.utils.json_to_sheet(allowanceData)
|
||||
XLSX.utils.book_append_sheet(workbook, allowanceSheet, '补助明细')
|
||||
}
|
||||
|
||||
// 导出文件
|
||||
XLSX.writeFile(workbook, filename)
|
||||
}
|
||||
|
||||
// 导出单个工资单详情
|
||||
export const exportSingleSalaryToExcel = (record: SalaryRecord) => {
|
||||
const workbook = XLSX.utils.book_new()
|
||||
|
||||
// 基本信息
|
||||
const basicInfo = [{
|
||||
项目: '员工姓名',
|
||||
值: record.employeeName,
|
||||
}, {
|
||||
项目: '员工类型',
|
||||
值: getEmployeeTypeText(record.employeeType),
|
||||
}, {
|
||||
项目: '身份证号',
|
||||
值: record.idCard,
|
||||
}, {
|
||||
项目: '联系电话',
|
||||
值: record.phone,
|
||||
}, {
|
||||
项目: '银行卡号',
|
||||
值: record.bankCard,
|
||||
}, {
|
||||
项目: '开户银行',
|
||||
值: record.bankName,
|
||||
}, {
|
||||
项目: '项目名称',
|
||||
值: record.projectName,
|
||||
}, {
|
||||
项目: '项目周期',
|
||||
值: record.projectPeriod,
|
||||
}, {
|
||||
项目: '工作天数',
|
||||
值: record.workDays,
|
||||
}, {
|
||||
项目: '海上作业天数',
|
||||
值: record.offshoreDays,
|
||||
}]
|
||||
|
||||
const basicSheet = XLSX.utils.json_to_sheet(basicInfo)
|
||||
XLSX.utils.book_append_sheet(workbook, basicSheet, '基本信息')
|
||||
|
||||
// 薪酬汇总
|
||||
const salarySummary = [{
|
||||
项目: '基本工资',
|
||||
金额: record.baseSalary,
|
||||
}, {
|
||||
项目: '绩效奖金',
|
||||
金额: record.performanceBonus,
|
||||
}, {
|
||||
项目: '各类补助',
|
||||
金额: record.allowances,
|
||||
}, {
|
||||
项目: '应发总额',
|
||||
金额: record.totalPayable,
|
||||
}, {
|
||||
项目: '扣款金额',
|
||||
金额: record.deductions,
|
||||
}, {
|
||||
项目: '实发金额',
|
||||
金额: record.netPay,
|
||||
}]
|
||||
|
||||
const salarySheet = XLSX.utils.json_to_sheet(salarySummary)
|
||||
XLSX.utils.book_append_sheet(workbook, salarySheet, '薪酬汇总')
|
||||
|
||||
// 绩效明细
|
||||
if (record.performanceItems.length > 0) {
|
||||
const performanceSheet = XLSX.utils.json_to_sheet(record.performanceItems.map((item) => ({
|
||||
评价项: item.name,
|
||||
权重: item.weight,
|
||||
得分: item.score,
|
||||
满分: item.maxScore,
|
||||
金额: (item.score / item.maxScore * item.amount).toFixed(2),
|
||||
})))
|
||||
XLSX.utils.book_append_sheet(workbook, performanceSheet, '绩效明细')
|
||||
}
|
||||
|
||||
// 补助明细
|
||||
if (record.allowanceItems.length > 0) {
|
||||
const allowanceSheet = XLSX.utils.json_to_sheet(record.allowanceItems.map((item) => ({
|
||||
补助类型: item.name,
|
||||
标准: item.amount,
|
||||
单位: getUnitText(item.unit),
|
||||
数量: item.quantity,
|
||||
小计: item.total,
|
||||
})))
|
||||
XLSX.utils.book_append_sheet(workbook, allowanceSheet, '补助明细')
|
||||
}
|
||||
|
||||
// 导出文件
|
||||
const filename = `${record.employeeName}_工资单_${record.projectPeriod}.xlsx`
|
||||
XLSX.writeFile(workbook, filename)
|
||||
}
|
||||
|
||||
// 导出实习报酬申领表格式
|
||||
export const exportInternCompensationForm = (record: SalaryRecord) => {
|
||||
const workbook = XLSX.utils.book_new()
|
||||
|
||||
// 实习生基本信息
|
||||
const basicInfo = [
|
||||
['实习生基本信息', '', '', '', ''],
|
||||
['类别', '填写内容', '', '备注', ''],
|
||||
['姓名', record.employeeName, '', '', ''],
|
||||
['身份证号', record.idCard, '', '', ''],
|
||||
['联系电话', record.phone, '', '', ''],
|
||||
['银行卡号', record.bankCard, '', '', ''],
|
||||
['开户银行', record.bankName, '', '', ''],
|
||||
['', '', '', '', ''],
|
||||
['实习项目及项目实习相关信息', '', '', '', ''],
|
||||
['序号', '项目名称', '在项目时间', '出外业施工作业时间(天)', '备注(时间段)'],
|
||||
['1', record.projectName, record.workDays, record.workDays - record.offshoreDays, record.projectPeriod],
|
||||
['', '', '', '', ''],
|
||||
['薪酬、绩效、补助(一)', '', '', '', ''],
|
||||
['项目类别', '计算标准(元/天)或(元/台)', '计酬天数统计(日)', '应发金额', '备注'],
|
||||
['基本工资', record.baseSalaryStandard, record.workDays, record.baseSalary, ''],
|
||||
['施工绩效', 100, record.workDays - record.offshoreDays, record.performanceBonus, ''],
|
||||
['海上作业绩效补助', 30, record.offshoreDays, record.offshoreDays * 30, ''],
|
||||
['务餐补助', 45, record.workDays, record.allowances, ''],
|
||||
['合计应发金额', '', '', record.totalPayable, ''],
|
||||
]
|
||||
|
||||
const sheet = XLSX.utils.aoa_to_sheet(basicInfo)
|
||||
|
||||
// 设置列宽
|
||||
const cols = [
|
||||
{ wch: 20 },
|
||||
{ wch: 20 },
|
||||
{ wch: 15 },
|
||||
{ wch: 25 },
|
||||
{ wch: 20 },
|
||||
]
|
||||
sheet['!cols'] = cols
|
||||
|
||||
// 合并单元格
|
||||
sheet['!merges'] = [
|
||||
{ s: { r: 0, c: 0 }, e: { r: 0, c: 4 } },
|
||||
{ s: { r: 8, c: 0 }, e: { r: 8, c: 4 } },
|
||||
{ s: { r: 12, c: 0 }, e: { r: 12, c: 4 } },
|
||||
]
|
||||
|
||||
XLSX.utils.book_append_sheet(workbook, sheet, '实习报酬申领表')
|
||||
|
||||
// 导出文件
|
||||
const filename = `${record.employeeName}_实习报酬申领表_${record.projectPeriod}.xlsx`
|
||||
XLSX.writeFile(workbook, filename)
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
function getEmployeeTypeText(type: string) {
|
||||
const typeMap = {
|
||||
intern: '实习生',
|
||||
fulltime: '正式员工',
|
||||
parttime: '兼职员工',
|
||||
}
|
||||
return typeMap[type as keyof typeof typeMap] || type
|
||||
}
|
||||
|
||||
function getStatusText(status: string) {
|
||||
const statusMap = {
|
||||
draft: '草稿',
|
||||
pending: '待审批',
|
||||
approved: '已批准',
|
||||
rejected: '已拒绝',
|
||||
paid: '已发放',
|
||||
}
|
||||
return statusMap[status as keyof typeof statusMap] || status
|
||||
}
|
||||
|
||||
function getUnitText(unit: string) {
|
||||
const unitMap = {
|
||||
day: '天',
|
||||
project: '项目',
|
||||
fixed: '固定',
|
||||
}
|
||||
return unitMap[unit as keyof typeof unitMap] || unit
|
||||
}
|
|
@ -9,15 +9,13 @@
|
|||
<GiTable
|
||||
v-show="viewType === 'table'"
|
||||
ref="tableRef"
|
||||
row-key="deptId"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="false"
|
||||
:disabled-column-keys="['name']"
|
||||
:expanded-keys="expandedKeys"
|
||||
@expand="onExpand"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #expand-icon="{ expanded }">
|
||||
|
@ -111,10 +109,9 @@ const {
|
|||
} = useTable<DeptResp>(() => getDeptTree(queryForm), {
|
||||
immediate: true,
|
||||
onSuccess: () => {
|
||||
// nextTick(() => {
|
||||
// tableRef.value?.tableRef?.expandAll(true)
|
||||
// })
|
||||
// 移除自动全部展开,保持默认折叠
|
||||
nextTick(() => {
|
||||
tableRef.value?.tableRef?.expandAll(true)
|
||||
})
|
||||
},
|
||||
})
|
||||
// 查看视图类型
|
||||
|
@ -127,8 +124,6 @@ const menus = [
|
|||
]
|
||||
// 所有节点展开状态
|
||||
const nodeExpandAll = ref<boolean>(true)
|
||||
// 表格视图展开的节点
|
||||
const expandedKeys = ref<string[]>([])
|
||||
// 过滤树
|
||||
const searchData = (name: string) => {
|
||||
const loop = (data: DeptResp[]) => {
|
||||
|
@ -209,17 +204,6 @@ const handleAdd = (record: DeptResp) => {
|
|||
const onUpdate = (record: DeptResp) => {
|
||||
DeptAddModalRef.value?.onUpdate(record.deptId)
|
||||
}
|
||||
|
||||
const onExpand = (expanded: boolean, record: DeptResp) => {
|
||||
const key = record.deptId
|
||||
if (expanded) {
|
||||
if (!expandedKeys.value.includes(key)) {
|
||||
expandedKeys.value.push(key)
|
||||
}
|
||||
} else {
|
||||
expandedKeys.value = expandedKeys.value.filter(k => k !== key)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<template>
|
||||
<h1>这个暂时写不了,角色管理采用菜单权限的设置</h1>
|
||||
<GiPageLayout :header-style="{ padding: 0, borderBottom: 'none' }">
|
||||
<template #left>
|
||||
<RoleTree @node-click="handleSelectRole" />
|
||||
|
|
Loading…
Reference in New Issue