项目接口调试完成
parent
60cb68401f
commit
b5f23e89d8
|
@ -1,6 +1,6 @@
|
|||
VITE_BASE_URL = "./"
|
||||
VITE_BASE_NAME = "external"
|
||||
VITE_APP_PROXYURL = 'http://183.230.174.15:8088/external'
|
||||
VITE_APP_REQUESTURL = '/external'
|
||||
VITE_APP_ROUTERURL = '/external/'
|
||||
VITE_BASE_NAME = "pms-front-test"
|
||||
VITE_APP_PROXYURL = 'http://192.168.124.202:8080'
|
||||
VITE_APP_REQUESTURL = '/pms-front-test'
|
||||
VITE_APP_ROUTERURL = '/pms-front-test/'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
VITE_BASE_URL = '/pms-front-test'
|
||||
VITE_BASE_NAME = "pms-front-test"
|
||||
VITE_APP_PROXYURL = 'http://183.230.174.15:8093/aiops-external'
|
||||
VITE_APP_PROXYURL = 'http://192.168.124.202:8080'
|
||||
VITE_APP_REQUESTURL = '/pms-front-test'
|
||||
VITE_APP_ROUTERURL = '/pms-front-test/'
|
|
@ -27,6 +27,13 @@
|
|||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.type === 'status'">
|
||||
<span v-if="column.callback" v-html="column.callback(scope.row[column.prop], scope.row)">
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ scope.row[column.prop] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ scope.row[column.prop] }}
|
||||
</template>
|
||||
|
@ -160,4 +167,30 @@ onUnmounted(() => {
|
|||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
:deep(.el-scrollbar__wrap--hidden-default){
|
||||
min-height: 100px;
|
||||
}
|
||||
:deep(.el-table) {
|
||||
--el-table-text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 如果操作列需要特殊处理,可以添加以下样式 */
|
||||
:deep(.operation-column .cell) {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
:deep(.el-table__inner-wrapper::before){
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -108,7 +108,7 @@ export const asyncRoutes = [
|
|||
name: 'worklogList',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '日志列表',
|
||||
title: '工作日志',
|
||||
},
|
||||
component: () => import('@/views/workLog/list.vue'),
|
||||
},
|
||||
|
|
|
@ -25,22 +25,10 @@ export const useUserStore = defineStore('user', {
|
|||
async login(params) {
|
||||
try {
|
||||
const res = await loginApi.login(params)
|
||||
const roleType = res.data.user.roles
|
||||
.filter(item => item.roleType)
|
||||
.map(per => per.roleType)
|
||||
.join(',')
|
||||
// setPerssionsTabbar(roleType)
|
||||
const roleKey = res.data.user.roles
|
||||
.filter(item => item.roleKey)
|
||||
.map(per => per.roleKey)
|
||||
.join(',')
|
||||
sessionStorage.setItem('roleType', $utils.encryptByDES.CBC(roleType))
|
||||
sessionStorage.setItem('roleKey', $utils.encryptByDES.CBC(roleKey))
|
||||
const token = $utils.encryptByMD5(`123456`)
|
||||
setToken(token)
|
||||
await this.updateUserInfo(res.data)
|
||||
setToken(res.token)
|
||||
await this.updateUserInfo()
|
||||
ElMessage.success({ message: '登录成功' })
|
||||
const permissions = [...res.data.resourceKeys]
|
||||
const permissions = [...this.userInfo.permissions]
|
||||
// , 'WorkOrderError', 'workOrderDestroy', 'workOrderReserver'
|
||||
sessionStorage.setItem('permissions', $utils.encryptByDES.CBC(JSON.stringify(permissions)))
|
||||
this.updateAsyncRoutes()
|
||||
|
@ -56,8 +44,8 @@ export const useUserStore = defineStore('user', {
|
|||
removeToken()
|
||||
router.push('/')
|
||||
},
|
||||
async updateUserInfo(data) {
|
||||
const userInfo = data.user
|
||||
async updateUserInfo() {
|
||||
const userInfo = await loginApi.getUserInfo()
|
||||
sessionStorage.setItem('userInfo', $utils.encryptByDES.CBC(JSON.stringify(userInfo)))
|
||||
this.userInfo = userInfo
|
||||
},
|
||||
|
@ -67,8 +55,8 @@ export const useUserStore = defineStore('user', {
|
|||
updateAsyncRoutes() {
|
||||
// 权限时处理异步路由
|
||||
let sessionPermissions = JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('permissions')))
|
||||
// console.log(asyncRoutes, sessionPermissions, 'asyncRoutes, sessionPermissions')
|
||||
let result = filterAsyncRoutes(asyncRoutes, sessionPermissions)
|
||||
// let result = filterAsyncRoutes(asyncRoutes, sessionPermissions)
|
||||
let result = asyncRoutes
|
||||
|
||||
result = editRedirect(result)
|
||||
// console.log(result, 'result')
|
||||
|
|
|
@ -1,14 +1,37 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
//登录板块api
|
||||
export const loginApi = {
|
||||
// 登录
|
||||
login: data => request('/login', 'post', data),
|
||||
login: data => request('/login', 'json', data),
|
||||
// 退出登录
|
||||
loginOut: () => request('/aiops-api/logout', 'get'),
|
||||
//修改密码
|
||||
editPassword: data => request('/aiops-api/auth/user/updatePwd', 'post', data),
|
||||
// 获取验证码
|
||||
getCode: () => request('/captchaImage', 'captchaImage', {}, {
|
||||
SHOW_LOADING: false, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}),
|
||||
getUserInfo: () => request('/getInfo', 'get'),
|
||||
}
|
||||
|
||||
// 项目板块
|
||||
export const projectApi = {
|
||||
// 查询项目列表
|
||||
listProject: query => request('/business/project/list', 'get', query),
|
||||
deleteProject: id => request(`/business/project/${id}`, 'delete'),
|
||||
addProject: data => request('/business/project/add', 'json', data),
|
||||
updateProject: data => request('/business/project/update', 'json', data),
|
||||
getProjectCode: () => request('/business/project/getCode', 'get'),
|
||||
getProjectDetail: id => request(`/business/project/info/${id}`, 'get'),
|
||||
// 项目成员
|
||||
getProjectUser: id => request(`/business/project/${id}`, 'get'),
|
||||
updateProjectUser: data => request('/business/project/team', 'json', data),
|
||||
deleteProjectUser: id => request(`/business/project/team/${id}`, 'delete'),
|
||||
}
|
||||
|
||||
// 用户板块
|
||||
export const useUserApi = () => {
|
||||
|
||||
|
||||
|
@ -16,6 +39,7 @@ export const useUserApi = () => {
|
|||
// 系统板块
|
||||
export const systemApi = {
|
||||
getUserList: data => request('/aiops-api/auth/user/list', 'post', data),
|
||||
getDictData: dictCode => request('/system/dict/data/type/' + dictCode, 'get'),
|
||||
}
|
||||
// 系统定时任务
|
||||
export const systemJobApi = {
|
||||
|
|
|
@ -17,7 +17,7 @@ const service = axios.create({
|
|||
service.interceptors.request.use(
|
||||
config => {
|
||||
if (getToken()) {
|
||||
config.headers['token'] = getToken()
|
||||
config.headers['Authorization'] = 'Bearer ' + getToken()
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
@ -105,6 +105,9 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
|
|||
method = 'POST'
|
||||
opts.headers['Content-Type'] = 'application/json'
|
||||
opts.responseType = 'blob'
|
||||
}else if (/captchaImage/i.test(method)) {
|
||||
method = 'get'
|
||||
opts.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
service({
|
||||
url: url,
|
||||
|
@ -156,7 +159,7 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
|
|||
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
|
||||
return
|
||||
}
|
||||
if (res.code === 0) {
|
||||
if (res.code === 200) {
|
||||
resolve(res) //
|
||||
// resolve(res.data)
|
||||
} else if (res.code === 401) {
|
||||
|
@ -175,7 +178,6 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
|
|||
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('ssss')
|
||||
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
|
||||
if (err.code === 'ERR_CANCELED') return
|
||||
ElMessage.error({
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { loginApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const userStore = useUserStore()
|
||||
|
@ -82,23 +83,14 @@ const login = reactive({
|
|||
pwd: '',
|
||||
validateCode: '',
|
||||
})
|
||||
const codeSrc = ref(
|
||||
process.env.NODE_ENV === 'production'
|
||||
? `${window.location.origin}${
|
||||
import.meta.env.VITE_APP_REQUESTURL
|
||||
}/captcha/captchaImage?type=math&&t=${Math.random()}`
|
||||
: `${import.meta.env.VITE_APP_REQUESTURL}/captcha/captchaImage?type=math`,
|
||||
) //${window.location.origin}
|
||||
const codeSrc = ref()
|
||||
const uuid = ref()
|
||||
const updateCode = async () => {
|
||||
codeSrc.value =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? `${window.location.origin}${
|
||||
import.meta.env.VITE_APP_REQUESTURL
|
||||
}/captcha/captchaImage?type=math&&t=${Math.random()}`
|
||||
: `${import.meta.env.VITE_APP_REQUESTURL}/captcha/captchaImage?type=math&&t=${Math.random()}` //char math
|
||||
const res = await loginApi.getCode()
|
||||
codeSrc.value = 'data:image/png;base64,' + res.img
|
||||
uuid.value = res.uuid
|
||||
}
|
||||
|
||||
// getCode()
|
||||
const confirmLogin = async () => {
|
||||
if (!login.account) {
|
||||
ElMessage.error({ message: `用户名不能为空!` })
|
||||
|
@ -113,9 +105,10 @@ const confirmLogin = async () => {
|
|||
return
|
||||
}
|
||||
const params = {
|
||||
password: $utils.encryptByMD5(`${login.account}${login.pwd}`),
|
||||
password: login.pwd,
|
||||
username: login.account,
|
||||
validateCode: login.validateCode,
|
||||
code: login.validateCode,
|
||||
uuid: uuid.value,
|
||||
}
|
||||
try {
|
||||
await userStore.login(params)
|
||||
|
@ -127,6 +120,7 @@ const confirmLogin = async () => {
|
|||
const usershadow = ref(false)
|
||||
const shadow = ref(false)
|
||||
const codeShadow = ref(false)
|
||||
updateCode()
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.login {
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
<template>
|
||||
<div class="project-management">
|
||||
<el-form :model="formData" :rules="rules" label-width="120px" class="custom-form">
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="custom-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="项目名称">
|
||||
<el-select v-model="formData.projectName" placeholder="请选择项目名称" class="full-width longInput">
|
||||
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号">
|
||||
<el-input v-model="formData.projectCode" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目负责人">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="formData.projectManager"
|
||||
v-model="formData.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
:disabled="isEditing"
|
||||
class="full-width longInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编码" prop="projectCode">
|
||||
<el-input v-model="formData.projectCode" class="full-width" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目负责人" prop="projectLeader">
|
||||
<el-input
|
||||
v-model="formData.projectLeaderName"
|
||||
placeholder="选择项目负责人"
|
||||
readonly
|
||||
:disabled="isEditing"
|
||||
@click="openProjectManagerSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
@ -27,36 +31,58 @@
|
|||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目状态">
|
||||
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width">
|
||||
<el-option label="进行中" value="ongoing" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已暂停" value="paused" />
|
||||
<el-form-item label="项目状态" prop="projectState">
|
||||
<el-select
|
||||
v-model="formData.projectState"
|
||||
placeholder="请选择项目状态"
|
||||
:disabled="isEditing"
|
||||
class="full-width"
|
||||
>
|
||||
<el-option label="未启动" value="0" />
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预计工时">
|
||||
<el-input type="number" v-model="formData.budgetDays" :min="0" class="full-width" />
|
||||
<el-form-item label="预算天数" prop="budgetDate">
|
||||
<el-input v-model.number="formData.budgetDate" :min="0" :disabled="isEditing" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目开始时间">
|
||||
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" />
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker
|
||||
v-model="formData.startDate"
|
||||
type="date"
|
||||
placeholder="选择开始日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目结束时间">
|
||||
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" />
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker
|
||||
v-model="formData.endDate"
|
||||
type="date"
|
||||
placeholder="选择结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="form-actions jcc">
|
||||
<el-button type="primary" @click="saveProject">保存</el-button>
|
||||
<el-button @click="cancelEdit">取消</el-button>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" :icon="Plus" @click="editUser">新增成员</el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="addUser">新增成员</el-button>
|
||||
</div>
|
||||
|
||||
<CustomTable
|
||||
|
@ -65,31 +91,47 @@
|
|||
:total="total"
|
||||
:show-selection="false"
|
||||
:show-index="true"
|
||||
:show-pagination="false"
|
||||
:table-height="tableHeight"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #name="{ row }">
|
||||
<el-input
|
||||
v-model="row.name"
|
||||
v-if="row.isNew"
|
||||
v-model="row.userName"
|
||||
placeholder="选择人员"
|
||||
readonly
|
||||
@click="openSelectUser(row)"
|
||||
/>
|
||||
<span v-else>{{ row.userName }}</span>
|
||||
</template>
|
||||
<template #role="{ row }">
|
||||
<el-select
|
||||
v-model="row.role"
|
||||
placeholder="请选择职位"
|
||||
@change="handleRoleChange(row)"
|
||||
<template #post="{ row }">
|
||||
<el-select
|
||||
v-if="row.isEditing || row.isNew"
|
||||
v-model="row.postId"
|
||||
placeholder="请选择职位"
|
||||
@change="handlePostChange(row)"
|
||||
>
|
||||
<el-option v-for="role in roleOptions" :key="role" :label="role" :value="role" />
|
||||
<el-option v-for="post in postOptions" :key="post" :label="post.dictLabel" :value="post.dictValue" />
|
||||
</el-select>
|
||||
<span v-else>{{ postOptions.find(post => post.dictValue === row.postId)?.dictLabel }}</span>
|
||||
</template>
|
||||
<template #operation="{ row }">
|
||||
<div class="operation-buttons">
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工作日志</el-button>
|
||||
<el-button text type="danger" @click="confirmDelete(row)">删除</el-button>
|
||||
<template v-if="row.isNew">
|
||||
<el-button text type="primary" @click="confirmAddUser(row)">确认</el-button>
|
||||
<el-button text type="danger" @click="cancelAddUser(row)">取消</el-button>
|
||||
</template>
|
||||
<template v-else-if="row.isEditing">
|
||||
<el-button text type="primary" @click="saveUserEdit(row)">保存</el-button>
|
||||
<el-button text type="info" @click="cancelUserEdit(row)">取消</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button text type="primary" @click="editUser(row)">编辑</el-button>
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工作日志</el-button>
|
||||
<el-button text type="danger" @click="confirmDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</CustomTable>
|
||||
|
@ -114,13 +156,9 @@
|
|||
/>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog
|
||||
v-model="deleteDialogVisible"
|
||||
title="确认删除"
|
||||
width="30%"
|
||||
>
|
||||
<el-dialog v-model="deleteDialogVisible" title="确认删除" width="30%">
|
||||
<div class="delete-confirm">
|
||||
<img src="@/assets/warning.png" alt="警告" class="warning-icon">
|
||||
<img src="@/assets/warning.png" alt="警告" class="warning-icon" />
|
||||
<p>确定要删除该成员吗?此操作不可逆。</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
|
@ -134,42 +172,41 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import CustomTable from '@/components/table.vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import SelectUser from '@/components/SelectUser.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { projectApi, systemApi } from '@/utils/api'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const formRef = ref(null)
|
||||
|
||||
const isEditing = ref(true)
|
||||
const isEditing = computed(() => !!formData.projectId)
|
||||
const showSelectUser = ref(false)
|
||||
const showProjectManagerSelect = ref(false)
|
||||
const formData = reactive({
|
||||
projectId: null,
|
||||
projectName: '',
|
||||
projectCode: '',
|
||||
projectManager: '',
|
||||
budgetDays: 0,
|
||||
projectStatus: '',
|
||||
projectLeaderName: '',
|
||||
projectLeader: '',
|
||||
projectState: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
budgetDate: 0,
|
||||
state: 0,
|
||||
})
|
||||
|
||||
const roleOptions = ['开发', '设计', '测试', '产品', '项目经理']
|
||||
const postOptions = ref([]) // 根据实际情况调整
|
||||
|
||||
const columns = [
|
||||
{ prop: 'name', label: '人员姓名', slot: 'name' },
|
||||
{ prop: 'role', label: '项目职位', slot: 'role' },
|
||||
{ prop: 'workdays', label: '累计人天数' },
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: '200',
|
||||
fixed: 'right',
|
||||
className: 'operation-column',
|
||||
},
|
||||
{ prop: 'name', label: '姓名' },
|
||||
{ prop: 'post', label: '项目职位' },
|
||||
{ prop: 'workdays', label: '工作天数' },
|
||||
{ prop: 'operation', label: '操作', width: '250px' },
|
||||
]
|
||||
|
||||
const tableData = ref([])
|
||||
|
@ -178,10 +215,31 @@ const currentEditingRow = ref(null)
|
|||
const currentSelectedUser = ref([])
|
||||
const projectManagerSelectedUser = ref([])
|
||||
|
||||
// 自动更新项目状态
|
||||
watch([() => formData.startDate, () => formData.endDate], updateProjectState, { immediate: true })
|
||||
|
||||
function updateProjectState() {
|
||||
const now = new Date().getTime()
|
||||
const start = formData.startDate ? new Date(formData.startDate).getTime() : null
|
||||
const end = formData.endDate ? new Date(formData.endDate).getTime() : null
|
||||
|
||||
if (start && end) {
|
||||
if (now < start) {
|
||||
formData.projectState = '0' // 未开始
|
||||
} else if (now >= start && now <= end) {
|
||||
formData.projectState = '1' // 进行中
|
||||
} else {
|
||||
formData.projectState = '2' // 已结束
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateDates = (rule, value, callback) => {
|
||||
if (formData.startDate && formData.endDate) {
|
||||
if (new Date(formData.startDate) > new Date(formData.endDate)) {
|
||||
callback(new Error('开始时间不能大于结束时间'))
|
||||
const start = new Date(formData.startDate)
|
||||
const end = new Date(formData.endDate)
|
||||
if (start > end) {
|
||||
callback(new Error('开始日期不能晚于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
|
@ -191,38 +249,140 @@ const validateDates = (rule, value, callback) => {
|
|||
}
|
||||
|
||||
const rules = {
|
||||
projectName: [
|
||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||
{ max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' },
|
||||
],
|
||||
projectCode: [
|
||||
{ required: true, message: '请输入项目编码', trigger: 'blur' },
|
||||
{ max: 50, message: '项目编码不能超过50个字符', trigger: 'blur' },
|
||||
],
|
||||
projectLeader: [{ required: true, message: '请选择项目负责人', trigger: 'change' }],
|
||||
projectState: [{ required: true, message: '请选择项目状态', trigger: 'change' }],
|
||||
startDate: [
|
||||
{ required: true, message: '请选择开始时间', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' }
|
||||
{ required: true, message: '请选择开始日期', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' },
|
||||
],
|
||||
endDate: [
|
||||
{ required: true, message: '请选择结束时间', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' }
|
||||
]
|
||||
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' },
|
||||
],
|
||||
budgetDate: [
|
||||
{ required: true, message: '请输入预算天数', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '预算天数必须大于0', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
const editUser = () => {
|
||||
const saveProject = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
// 表单验证通过,继续保存操作
|
||||
const projectDataToSave = {
|
||||
...formData,
|
||||
startDate: formData.startDate ? new Date(formData.startDate).getTime() : null,
|
||||
endDate: formData.endDate ? new Date(formData.endDate).getTime() : null,
|
||||
}
|
||||
if (!formData.projectId) {
|
||||
const res = await projectApi.addProject(projectDataToSave)
|
||||
formData.projectId = res.data.projectId
|
||||
} else {
|
||||
await projectApi.updateProject(projectDataToSave)
|
||||
}
|
||||
ElMessage.success('项目保存成功')
|
||||
// 如果是新建项目,保存后不要立即返回,而是停留在当前页面
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
// 表单验证失败
|
||||
console.error('表单验证失败', error)
|
||||
ElMessage.error('请检查表单填写是否正确')
|
||||
} else {
|
||||
// API 调用失败
|
||||
console.error('保存项目失败', error)
|
||||
ElMessage.error('保存项目失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const addUser = () => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再添加用户')
|
||||
return
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
id: Date.now(),
|
||||
name: '',
|
||||
role: '',
|
||||
post: '',
|
||||
postId: '',
|
||||
workdays: 0,
|
||||
userId: null
|
||||
userId: null,
|
||||
isNew: true,
|
||||
}
|
||||
tableData.value.push(newUser)
|
||||
tableData.value.unshift(newUser)
|
||||
total.value = tableData.value.length
|
||||
}
|
||||
|
||||
const openSelectUser = (row) => {
|
||||
const handlePostChange = row => {
|
||||
// 可以在这里添加一些额外的逻辑,如果需要的话
|
||||
}
|
||||
|
||||
const confirmAddUser = async row => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再添加用户')
|
||||
return
|
||||
}
|
||||
|
||||
if (!row.userId || !row.postId) {
|
||||
ElMessage.warning('请选择用户并指定职位')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let params = {
|
||||
projectId: formData.projectId,
|
||||
userId: row.userId,
|
||||
postId: row.postId,
|
||||
}
|
||||
if (row.teamId) params.teamId = row.teamId
|
||||
await projectApi.updateProjectUser(params)
|
||||
row.isNew = false
|
||||
ElMessage.success('成员添加成功')
|
||||
getProjectUser()
|
||||
} catch (error) {
|
||||
console.error('添加成员失败', error)
|
||||
ElMessage.error('添加成员失败')
|
||||
}
|
||||
}
|
||||
|
||||
const cancelAddUser = row => {
|
||||
const index = tableData.value.findIndex(item => item.id === row.id)
|
||||
if (index !== -1) {
|
||||
tableData.value.splice(index, 1)
|
||||
total.value = tableData.value.length
|
||||
}
|
||||
}
|
||||
|
||||
const openSelectUser = row => {
|
||||
currentEditingRow.value = row
|
||||
currentSelectedUser.value = row.userId ? [{ id: row.userId, name: row.name }] : []
|
||||
showSelectUser.value = true
|
||||
}
|
||||
|
||||
const handleUserConfirm = (users) => {
|
||||
if (users.length > 0) {
|
||||
const selectedUser = users[0]
|
||||
currentEditingRow.value.name = selectedUser.name
|
||||
const handleUserConfirm = selectedUsers => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再选择用户')
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedUsers.length > 0) {
|
||||
const selectedUser = selectedUsers[0]
|
||||
currentEditingRow.value.userName = selectedUser.name
|
||||
currentEditingRow.value.userId = selectedUser.id
|
||||
} else {
|
||||
currentEditingRow.value.name = ''
|
||||
|
@ -238,20 +398,20 @@ const closeSelectUser = () => {
|
|||
}
|
||||
|
||||
const openProjectManagerSelect = () => {
|
||||
projectManagerSelectedUser.value = formData.projectManager
|
||||
? [{ id: formData.projectManagerId, name: formData.projectManager }]
|
||||
projectManagerSelectedUser.value = formData.projectLeader
|
||||
? [{ id: formData.projectLeaderId, name: formData.projectLeader }]
|
||||
: []
|
||||
showProjectManagerSelect.value = true
|
||||
}
|
||||
|
||||
const handleProjectManagerConfirm = (users) => {
|
||||
const handleProjectManagerConfirm = users => {
|
||||
if (users.length > 0) {
|
||||
const selectedUser = users[0]
|
||||
formData.projectManager = selectedUser.name
|
||||
formData.projectManagerId = selectedUser.id
|
||||
formData.projectLeaderName = selectedUser.name
|
||||
formData.projectLeader = selectedUser.id
|
||||
} else {
|
||||
formData.projectManager = ''
|
||||
formData.projectManagerId = null
|
||||
formData.projectLeaderName = ''
|
||||
formData.projectLeader = null
|
||||
}
|
||||
showProjectManagerSelect.value = false
|
||||
}
|
||||
|
@ -268,65 +428,122 @@ const handleCurrentChange = val => {
|
|||
console.log('当前页:', val)
|
||||
}
|
||||
|
||||
const handleRoleChange = (row) => {
|
||||
}
|
||||
|
||||
const showTimesheet = (row) => {
|
||||
const showTimesheet = row => {
|
||||
router.push({
|
||||
name: 'workLog',
|
||||
params: { userId: row.userId },
|
||||
query: { userName: row.name }
|
||||
path: '/worklog/list',
|
||||
query: { userId: row.userId, projectId: formData.projectId },
|
||||
})
|
||||
}
|
||||
|
||||
const deleteDialogVisible = ref(false)
|
||||
const userToDelete = ref(null)
|
||||
|
||||
const confirmDelete = (row) => {
|
||||
userToDelete.value = row
|
||||
deleteDialogVisible.value = true
|
||||
const confirmDelete = row => {
|
||||
ElMessageBox.confirm('确定要删除该成员吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
deleteUser(row)
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消删除操作
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
const index = tableData.value.findIndex(item => item.id === userToDelete.value.id)
|
||||
if (index !== -1) {
|
||||
tableData.value.splice(index, 1)
|
||||
total.value = tableData.value.length
|
||||
ElMessage.success('已删除该成员')
|
||||
deleteDialogVisible.value = false
|
||||
userToDelete.value = null
|
||||
const deleteUser = async row => {
|
||||
try {
|
||||
await projectApi.deleteProjectUser(row.teamId)
|
||||
ElMessage.success('成员删除成功')
|
||||
getProjectUser()
|
||||
} catch (error) {
|
||||
console.error('删除成员失败', error)
|
||||
ElMessage.error('删除成员失败')
|
||||
}
|
||||
}
|
||||
|
||||
const tableHeight = ref(400)
|
||||
|
||||
onMounted(() => {
|
||||
const projectData = route.params.projectData || route.query.projectData
|
||||
if (projectData) {
|
||||
Object.assign(formData, projectData)
|
||||
} else if (route.query.id) {
|
||||
fetchProjectData(route.query.id)
|
||||
}
|
||||
})
|
||||
// 获取项目编码
|
||||
const getProjectCode = async () => {
|
||||
const res = await projectApi.getProjectCode()
|
||||
formData.projectCode = res.data
|
||||
}
|
||||
|
||||
const fetchProjectData = async id => {
|
||||
try {
|
||||
const projectData = {
|
||||
id: id,
|
||||
projectName: `项目${id}`,
|
||||
projectCode: `XM00${id}`,
|
||||
projectManager: '张三',
|
||||
budgetDays: 30,
|
||||
projectStatus: 'ongoing',
|
||||
startDate: '2024-08-31',
|
||||
endDate: '2024-09-30',
|
||||
}
|
||||
|
||||
Object.assign(formData, projectData)
|
||||
const response = await projectApi.getProjectDetail(id)
|
||||
const projectData = response.data
|
||||
// 将时间戳转换为日期字符串
|
||||
formData.startDate = projectData.startDate ? new Date(projectData.startDate).toISOString().split('T')[0] : ''
|
||||
formData.endDate = projectData.endDate ? new Date(projectData.endDate).toISOString().split('T')[0] : ''
|
||||
// 复制其他字段
|
||||
Object.assign(formData, {
|
||||
...projectData,
|
||||
startDate: formData.startDate,
|
||||
endDate: formData.endDate,
|
||||
})
|
||||
updateProjectState() // 更新项目状态
|
||||
} catch (error) {
|
||||
console.error('获取项目数据失败', error)
|
||||
ElMessage.error('获取项目数据失败')
|
||||
}
|
||||
}
|
||||
// 获取数据字典
|
||||
const getDictData = async () => {
|
||||
const res = await systemApi.getDictData('business_positions')
|
||||
postOptions.value = res.data
|
||||
}
|
||||
// 获取项目人员
|
||||
const getProjectUser = async () => {
|
||||
await getDictData()
|
||||
const res = await projectApi.getProjectUser(formData.projectId)
|
||||
tableData.value = res.data
|
||||
}
|
||||
|
||||
const editUser = row => {
|
||||
row.isEditing = true
|
||||
row.originalPost = row.postId // 保存原始职位,以便取消时恢复
|
||||
}
|
||||
|
||||
const saveUserEdit = async row => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await projectApi.updateProjectUser({
|
||||
projectId: formData.projectId,
|
||||
userId: row.userId,
|
||||
postId: row.postId,
|
||||
teamId: row.teamId,
|
||||
})
|
||||
row.isEditing = false
|
||||
delete row.originalPost
|
||||
ElMessage.success('用户信息更新成功')
|
||||
} catch (error) {
|
||||
console.error('更新用户信息失败', error)
|
||||
ElMessage.error('更新用户信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
const cancelUserEdit = row => {
|
||||
row.postId = row.originalPost
|
||||
row.isEditing = false
|
||||
delete row.originalPost
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const projectId = route.params.id || route.query.id
|
||||
formData.projectId = projectId
|
||||
if (projectId) {
|
||||
fetchProjectData(projectId)
|
||||
getProjectUser()
|
||||
} else {
|
||||
updateProjectState() // 对于新项目,也要初始化状态
|
||||
getProjectCode()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -341,6 +558,7 @@ const fetchProjectData = async id => {
|
|||
}
|
||||
.custom-form {
|
||||
width: 80%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-container {
|
||||
width: 100%;
|
||||
|
@ -388,8 +606,16 @@ const fetchProjectData = async id => {
|
|||
}
|
||||
|
||||
.table-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
|
@ -447,4 +673,4 @@ const fetchProjectData = async id => {
|
|||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -6,16 +6,19 @@
|
|||
<el-input v-model="searchForm.projectName" placeholder="项目名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人" class="form-item">
|
||||
<el-select v-model="searchForm.manager" placeholder="负责人">
|
||||
<el-option label="张三" value="张三" />
|
||||
<el-option label="李四" value="李四" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchForm.projectLeaderName"
|
||||
placeholder="负责人"
|
||||
readonly
|
||||
@click="openUserSelectDialog"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目状态" class="form-item">
|
||||
<el-select v-model="searchForm.status" placeholder="项目状态">
|
||||
<el-option label="待启动" value="待启动" />
|
||||
<el-option label="进行中" value="进行中" />
|
||||
<el-option label="已完成" value="已完成" />
|
||||
<el-select v-model="searchForm.projectState" placeholder="项目状态">
|
||||
<el-option label="未启动" value="0" />
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="search-buttons">
|
||||
|
@ -64,6 +67,15 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 用户选择弹框 -->
|
||||
<SelectUser
|
||||
v-model:dialogVisible="userSelectDialogVisible"
|
||||
:multiSelect="false"
|
||||
:currentSelectedUser="currentSelectedUser"
|
||||
@close="handleUserSelectClose"
|
||||
@confirm="handleUserConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -71,26 +83,68 @@
|
|||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CustomTable from '@/components/table.vue'
|
||||
import SelectUser from '@/components/selectUser.vue'
|
||||
import { WarningFilled } from '@element-plus/icons-vue'
|
||||
import { projectApi } from '@/utils/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const searchForm = reactive({
|
||||
projectName: '',
|
||||
manager: '',
|
||||
status: '',
|
||||
projectLeaderName: '',
|
||||
projectLeader: '',
|
||||
projectState: '',
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ prop: 'projectName', label: '项目名称' },
|
||||
{ prop: 'projectCode', label: '项目编号' },
|
||||
{ prop: 'manager', label: '负责人' },
|
||||
{ prop: 'budgetDays', label: '预计工时(天)' },
|
||||
{ prop: 'startDate', label: '开始时间' },
|
||||
{ prop: 'endDate', label: '结束时间' },
|
||||
{ prop: 'status', label: '项目状态' },
|
||||
{ prop: 'memberCount', label: '参与项目人数' },
|
||||
{ prop: 'createdBy', label: '项目创建人' },
|
||||
{ prop: 'projectLeaderName', label: '负责人' },
|
||||
{ prop: 'budgetDate', label: '预计工时(天)' },
|
||||
{
|
||||
prop: 'startDate',
|
||||
label: '开始时间',
|
||||
type: 'status',
|
||||
callback: (data, row) => {
|
||||
return data.split(' ')[0]
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'endDate',
|
||||
label: '结束时间',
|
||||
type: 'status',
|
||||
callback: (data, row) => {
|
||||
return data.split(' ')[0]
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'projectState',
|
||||
label: '项目状态',
|
||||
type: 'status',
|
||||
callback: (value, row) => {
|
||||
let status = '未知'
|
||||
let color = ''
|
||||
|
||||
switch (value) {
|
||||
case '0':
|
||||
status = '未启动'
|
||||
color = '#909399' // 灰色
|
||||
break
|
||||
case '1':
|
||||
status = '进行中'
|
||||
color = '#409EFF' // 蓝色
|
||||
break
|
||||
case '2':
|
||||
status = '已完成'
|
||||
color = '#67C23A' // 绿色
|
||||
break
|
||||
}
|
||||
|
||||
return `<span style="color: ${color}">${status}</span>`
|
||||
},
|
||||
},
|
||||
{ prop: 'teamNum', label: '参与项目人数' },
|
||||
{ prop: 'createByName', label: '项目创建人' },
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
|
@ -105,6 +159,35 @@ const total = ref(0)
|
|||
const deleteDialogVisible = ref(false)
|
||||
const currentDeleteItem = ref(null)
|
||||
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 用户选择相关
|
||||
const userSelectDialogVisible = ref(false)
|
||||
const currentSelectedUser = ref([])
|
||||
|
||||
const openUserSelectDialog = () => {
|
||||
userSelectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleUserSelectClose = () => {
|
||||
userSelectDialogVisible.value = false
|
||||
}
|
||||
|
||||
const handleUserConfirm = (selectedUsers) => {
|
||||
if (selectedUsers.length > 0) {
|
||||
const selectedUser = selectedUsers[0]
|
||||
searchForm.projectLeaderName = selectedUser.name
|
||||
searchForm.projectLeader = selectedUser.id
|
||||
currentSelectedUser.value = [selectedUser]
|
||||
} else {
|
||||
searchForm.projectLeaderName = ''
|
||||
searchForm.projectLeader = ''
|
||||
currentSelectedUser.value = []
|
||||
}
|
||||
userSelectDialogVisible.value = false
|
||||
}
|
||||
|
||||
const onSearch = () => {
|
||||
// 实现搜索逻辑
|
||||
console.log('Search with:', searchForm)
|
||||
|
@ -125,7 +208,7 @@ const addProject = () => {
|
|||
const handleEdit = row => {
|
||||
router.push({
|
||||
path: '/project/detail',
|
||||
query: { id: row.id },
|
||||
query: { id: row.projectId },
|
||||
state: { projectData: row },
|
||||
})
|
||||
}
|
||||
|
@ -140,42 +223,35 @@ const handleCloseDeleteDialog = () => {
|
|||
currentDeleteItem.value = null
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
const confirmDelete = async () => {
|
||||
// 实现删除逻辑
|
||||
console.log('Delete project:', currentDeleteItem.value)
|
||||
await projectApi.deleteProject(currentDeleteItem.value.projectId)
|
||||
deleteDialogVisible.value = false
|
||||
currentDeleteItem.value = null
|
||||
ElMessage.success('删除成功')
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const handleSizeChange = val => {
|
||||
console.log(`每页 ${val} 条`)
|
||||
pageSize.value = val
|
||||
pageNum.value = 1 // 重置为第一页
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = val => {
|
||||
console.log(`当前页: ${val}`)
|
||||
pageNum.value = val
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const fetchProjectList = () => {
|
||||
const fetchProjectList = async () => {
|
||||
// 这里使用假数据,实际应该调用API
|
||||
const fakeData = Array(20)
|
||||
.fill(null)
|
||||
.map((_, index) => ({
|
||||
id: index + 1,
|
||||
projectName: `项目${index + 1}`,
|
||||
projectCode: `XM00${index + 1}`,
|
||||
manager: ['张三', '李四', '王五'][index % 3],
|
||||
budgetDays: Math.floor(Math.random() * 100) + 10,
|
||||
startDate: '2024-08-31',
|
||||
endDate: '2024-09-15',
|
||||
status: ['待启动', '进行中', '已完成'][index % 3],
|
||||
memberCount: Math.floor(Math.random() * 10) + 3,
|
||||
createdBy: '张三',
|
||||
}))
|
||||
tableData.value = fakeData
|
||||
total.value = fakeData.length
|
||||
const res = await projectApi.listProject({
|
||||
...searchForm,
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
tableData.value = res.rows
|
||||
total.value = res.total
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -278,4 +354,4 @@ onMounted(() => {
|
|||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -60,17 +60,6 @@ export default ({ mode }) => {
|
|||
},
|
||||
// 设置代理
|
||||
proxy: {
|
||||
'/upload': {
|
||||
target: 'http://111.10.228.245:8088/external',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: path => path.replace('/upload/', '/upload/'),
|
||||
},
|
||||
'/aiopsfile': {
|
||||
target: 'http://111.10.228.245:8088/external',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
|
||||
[env.VITE_APP_REQUESTURL]: {
|
||||
target: env.VITE_APP_PROXYURL,
|
||||
|
|
Loading…
Reference in New Issue