项目接口调试完成

main
wangjy 2024-10-10 17:57:26 +08:00
parent 60cb68401f
commit b5f23e89d8
11 changed files with 553 additions and 221 deletions

View File

@ -1,6 +1,6 @@
VITE_BASE_URL = "./" VITE_BASE_URL = "./"
VITE_BASE_NAME = "external" VITE_BASE_NAME = "pms-front-test"
VITE_APP_PROXYURL = 'http://183.230.174.15:8088/external' VITE_APP_PROXYURL = 'http://192.168.124.202:8080'
VITE_APP_REQUESTURL = '/external' VITE_APP_REQUESTURL = '/pms-front-test'
VITE_APP_ROUTERURL = '/external/' VITE_APP_ROUTERURL = '/pms-front-test/'

View File

@ -1,5 +1,5 @@
VITE_BASE_URL = '/pms-front-test' VITE_BASE_URL = '/pms-front-test'
VITE_BASE_NAME = "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_REQUESTURL = '/pms-front-test'
VITE_APP_ROUTERURL = '/pms-front-test/' VITE_APP_ROUTERURL = '/pms-front-test/'

View File

@ -27,6 +27,13 @@
</el-button> </el-button>
</div> </div>
</template> </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> <template v-else>
{{ scope.row[column.prop] }} {{ scope.row[column.prop] }}
</template> </template>
@ -160,4 +167,30 @@ onUnmounted(() => {
display: flex; display: flex;
justify-content: flex-end; 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> </style>

View File

@ -108,7 +108,7 @@ export const asyncRoutes = [
name: 'worklogList', name: 'worklogList',
authKey: 'Workorder', authKey: 'Workorder',
meta: { meta: {
title: '日志列表', title: '工作日志',
}, },
component: () => import('@/views/workLog/list.vue'), component: () => import('@/views/workLog/list.vue'),
}, },

View File

@ -25,22 +25,10 @@ export const useUserStore = defineStore('user', {
async login(params) { async login(params) {
try { try {
const res = await loginApi.login(params) const res = await loginApi.login(params)
const roleType = res.data.user.roles setToken(res.token)
.filter(item => item.roleType) await this.updateUserInfo()
.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)
ElMessage.success({ message: '登录成功' }) ElMessage.success({ message: '登录成功' })
const permissions = [...res.data.resourceKeys] const permissions = [...this.userInfo.permissions]
// , 'WorkOrderError', 'workOrderDestroy', 'workOrderReserver' // , 'WorkOrderError', 'workOrderDestroy', 'workOrderReserver'
sessionStorage.setItem('permissions', $utils.encryptByDES.CBC(JSON.stringify(permissions))) sessionStorage.setItem('permissions', $utils.encryptByDES.CBC(JSON.stringify(permissions)))
this.updateAsyncRoutes() this.updateAsyncRoutes()
@ -56,8 +44,8 @@ export const useUserStore = defineStore('user', {
removeToken() removeToken()
router.push('/') router.push('/')
}, },
async updateUserInfo(data) { async updateUserInfo() {
const userInfo = data.user const userInfo = await loginApi.getUserInfo()
sessionStorage.setItem('userInfo', $utils.encryptByDES.CBC(JSON.stringify(userInfo))) sessionStorage.setItem('userInfo', $utils.encryptByDES.CBC(JSON.stringify(userInfo)))
this.userInfo = userInfo this.userInfo = userInfo
}, },
@ -67,8 +55,8 @@ export const useUserStore = defineStore('user', {
updateAsyncRoutes() { updateAsyncRoutes() {
// 权限时处理异步路由 // 权限时处理异步路由
let sessionPermissions = JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('permissions'))) 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) result = editRedirect(result)
// console.log(result, 'result') // console.log(result, 'result')

View File

@ -1,14 +1,37 @@
import request from '@/utils/request' import request from '@/utils/request'
//登录板块api //登录板块api
export const loginApi = { export const loginApi = {
// 登录 // 登录
login: data => request('/login', 'post', data), login: data => request('/login', 'json', data),
// 退出登录 // 退出登录
loginOut: () => request('/aiops-api/logout', 'get'), loginOut: () => request('/aiops-api/logout', 'get'),
//修改密码 //修改密码
editPassword: data => request('/aiops-api/auth/user/updatePwd', 'post', data), 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 = () => { export const useUserApi = () => {
@ -16,6 +39,7 @@ export const useUserApi = () => {
// 系统板块 // 系统板块
export const systemApi = { export const systemApi = {
getUserList: data => request('/aiops-api/auth/user/list', 'post', data), getUserList: data => request('/aiops-api/auth/user/list', 'post', data),
getDictData: dictCode => request('/system/dict/data/type/' + dictCode, 'get'),
} }
// 系统定时任务 // 系统定时任务
export const systemJobApi = { export const systemJobApi = {

View File

@ -17,7 +17,7 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
if (getToken()) { if (getToken()) {
config.headers['token'] = getToken() config.headers['Authorization'] = 'Bearer ' + getToken()
} }
return config return config
}, },
@ -105,6 +105,9 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
method = 'POST' method = 'POST'
opts.headers['Content-Type'] = 'application/json' opts.headers['Content-Type'] = 'application/json'
opts.responseType = 'blob' opts.responseType = 'blob'
}else if (/captchaImage/i.test(method)) {
method = 'get'
opts.headers['Content-Type'] = 'application/json'
} }
service({ service({
url: url, url: url,
@ -156,7 +159,7 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
opts.SHOW_LOADING && loadingInstance && loadingInstance.close() opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
return return
} }
if (res.code === 0) { if (res.code === 200) {
resolve(res) // resolve(res) //
// resolve(res.data) // resolve(res.data)
} else if (res.code === 401) { } else if (res.code === 401) {
@ -175,7 +178,6 @@ const doRequest = (url, method = 'GET', params, opts, callback) => {
opts.SHOW_LOADING && loadingInstance && loadingInstance.close() opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
}) })
.catch(err => { .catch(err => {
console.log('ssss')
opts.SHOW_LOADING && loadingInstance && loadingInstance.close() opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
if (err.code === 'ERR_CANCELED') return if (err.code === 'ERR_CANCELED') return
ElMessage.error({ ElMessage.error({

View File

@ -73,6 +73,7 @@
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { loginApi } from '@/utils/api'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
const userStore = useUserStore() const userStore = useUserStore()
@ -82,23 +83,14 @@ const login = reactive({
pwd: '', pwd: '',
validateCode: '', validateCode: '',
}) })
const codeSrc = ref( const codeSrc = ref()
process.env.NODE_ENV === 'production' const uuid = ref()
? `${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 updateCode = async () => { const updateCode = async () => {
codeSrc.value = const res = await loginApi.getCode()
process.env.NODE_ENV === 'production' codeSrc.value = 'data:image/png;base64,' + res.img
? `${window.location.origin}${ uuid.value = res.uuid
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
} }
// getCode()
const confirmLogin = async () => { const confirmLogin = async () => {
if (!login.account) { if (!login.account) {
ElMessage.error({ message: `用户名不能为空!` }) ElMessage.error({ message: `用户名不能为空!` })
@ -113,9 +105,10 @@ const confirmLogin = async () => {
return return
} }
const params = { const params = {
password: $utils.encryptByMD5(`${login.account}${login.pwd}`), password: login.pwd,
username: login.account, username: login.account,
validateCode: login.validateCode, code: login.validateCode,
uuid: uuid.value,
} }
try { try {
await userStore.login(params) await userStore.login(params)
@ -127,6 +120,7 @@ const confirmLogin = async () => {
const usershadow = ref(false) const usershadow = ref(false)
const shadow = ref(false) const shadow = ref(false)
const codeShadow = ref(false) const codeShadow = ref(false)
updateCode()
</script> </script>
<style lang="scss"> <style lang="scss">
.login { .login {

View File

@ -1,25 +1,29 @@
<template> <template>
<div class="project-management"> <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-row :gutter="24">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="项目名称"> <el-form-item label="项目名称" prop="projectName">
<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-input <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="选择项目负责人" placeholder="选择项目负责人"
readonly readonly
:disabled="isEditing"
@click="openProjectManagerSelect" @click="openProjectManagerSelect"
/> />
</el-form-item> </el-form-item>
@ -27,36 +31,58 @@
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="项目状态"> <el-form-item label="项目状态" prop="projectState">
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width"> <el-select
<el-option label="进行中" value="ongoing" /> v-model="formData.projectState"
<el-option label="已完成" value="completed" /> placeholder="请选择项目状态"
<el-option label="已暂停" value="paused" /> :disabled="isEditing"
class="full-width"
>
<el-option label="未启动" value="0" />
<el-option label="进行中" value="1" />
<el-option label="已完成" value="2" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="预计工时"> <el-form-item label="预算天数" prop="budgetDate">
<el-input type="number" v-model="formData.budgetDays" :min="0" class="full-width" /> <el-input v-model.number="formData.budgetDate" :min="0" :disabled="isEditing" class="full-width" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="项目开始时间"> <el-form-item label="开始日期" prop="startDate">
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" /> <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-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="项目结束时间"> <el-form-item label="结束日期" prop="endDate">
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" /> <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-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </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"> <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> </div>
<CustomTable <CustomTable
@ -65,31 +91,47 @@
:total="total" :total="total"
:show-selection="false" :show-selection="false"
:show-index="true" :show-index="true"
:show-pagination="false"
:table-height="tableHeight" :table-height="tableHeight"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
> >
<template #name="{ row }"> <template #name="{ row }">
<el-input <el-input
v-model="row.name" v-if="row.isNew"
v-model="row.userName"
placeholder="选择人员" placeholder="选择人员"
readonly readonly
@click="openSelectUser(row)" @click="openSelectUser(row)"
/> />
<span v-else>{{ row.userName }}</span>
</template> </template>
<template #role="{ row }"> <template #post="{ row }">
<el-select <el-select
v-model="row.role" v-if="row.isEditing || row.isNew"
placeholder="请选择职位" v-model="row.postId"
@change="handleRoleChange(row)" 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> </el-select>
<span v-else>{{ postOptions.find(post => post.dictValue === row.postId)?.dictLabel }}</span>
</template> </template>
<template #operation="{ row }"> <template #operation="{ row }">
<div class="operation-buttons"> <div class="operation-buttons">
<el-button text type="primary" @click="showTimesheet(row)"></el-button> <template v-if="row.isNew">
<el-button text type="danger" @click="confirmDelete(row)"></el-button> <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> </div>
</template> </template>
</CustomTable> </CustomTable>
@ -114,13 +156,9 @@
/> />
<!-- 删除确认对话框 --> <!-- 删除确认对话框 -->
<el-dialog <el-dialog v-model="deleteDialogVisible" title="确认删除" width="30%">
v-model="deleteDialogVisible"
title="确认删除"
width="30%"
>
<div class="delete-confirm"> <div class="delete-confirm">
<img src="@/assets/warning.png" alt="警告" class="warning-icon"> <img src="@/assets/warning.png" alt="警告" class="warning-icon" />
<p>确定要删除该成员吗此操作不可逆</p> <p>确定要删除该成员吗此操作不可逆</p>
</div> </div>
<template #footer> <template #footer>
@ -134,42 +172,41 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import CustomTable from '@/components/table.vue' import CustomTable from '@/components/table.vue'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import SelectUser from '@/components/SelectUser.vue' import SelectUser from '@/components/SelectUser.vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { projectApi, systemApi } from '@/utils/api'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const formRef = ref(null)
const isEditing = ref(true) const isEditing = computed(() => !!formData.projectId)
const showSelectUser = ref(false) const showSelectUser = ref(false)
const showProjectManagerSelect = ref(false) const showProjectManagerSelect = ref(false)
const formData = reactive({ const formData = reactive({
projectId: null,
projectName: '', projectName: '',
projectCode: '', projectCode: '',
projectManager: '', projectLeaderName: '',
budgetDays: 0, projectLeader: '',
projectStatus: '', projectState: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
budgetDate: 0,
state: 0,
}) })
const roleOptions = ['开发', '设计', '测试', '产品', '项目经理'] const postOptions = ref([]) //
const columns = [ const columns = [
{ prop: 'name', label: '人员姓名', slot: 'name' }, { prop: 'name', label: '姓名' },
{ prop: 'role', label: '项目职位', slot: 'role' }, { prop: 'post', label: '项目职位' },
{ prop: 'workdays', label: '累计人天数' }, { prop: 'workdays', label: '工作天数' },
{ { prop: 'operation', label: '操作', width: '250px' },
prop: 'operation',
label: '操作',
width: '200',
fixed: 'right',
className: 'operation-column',
},
] ]
const tableData = ref([]) const tableData = ref([])
@ -178,10 +215,31 @@ const currentEditingRow = ref(null)
const currentSelectedUser = ref([]) const currentSelectedUser = ref([])
const projectManagerSelectedUser = 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) => { const validateDates = (rule, value, callback) => {
if (formData.startDate && formData.endDate) { if (formData.startDate && formData.endDate) {
if (new Date(formData.startDate) > new Date(formData.endDate)) { const start = new Date(formData.startDate)
callback(new Error('开始时间不能大于结束时间')) const end = new Date(formData.endDate)
if (start > end) {
callback(new Error('开始日期不能晚于结束日期'))
} else { } else {
callback() callback()
} }
@ -191,38 +249,140 @@ const validateDates = (rule, value, callback) => {
} }
const rules = { 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: [ startDate: [
{ required: true, message: '请选择开始时间', trigger: 'change' }, { required: true, message: '请选择开始日期', trigger: 'change' },
{ validator: validateDates, trigger: 'change' } { validator: validateDates, trigger: 'change' },
], ],
endDate: [ endDate: [
{ required: true, message: '请选择结束时间', trigger: 'change' }, { required: true, message: '请选择结束日期', trigger: 'change' },
{ validator: validateDates, 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 = { const newUser = {
id: Date.now(), id: Date.now(),
name: '', name: '',
role: '', post: '',
postId: '',
workdays: 0, workdays: 0,
userId: null userId: null,
isNew: true,
} }
tableData.value.push(newUser) tableData.value.unshift(newUser)
total.value = tableData.value.length 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 currentEditingRow.value = row
currentSelectedUser.value = row.userId ? [{ id: row.userId, name: row.name }] : [] currentSelectedUser.value = row.userId ? [{ id: row.userId, name: row.name }] : []
showSelectUser.value = true showSelectUser.value = true
} }
const handleUserConfirm = (users) => { const handleUserConfirm = selectedUsers => {
if (users.length > 0) { if (!formData.projectId) {
const selectedUser = users[0] ElMessage.warning('请先保存项目信息后再选择用户')
currentEditingRow.value.name = selectedUser.name return
}
if (selectedUsers.length > 0) {
const selectedUser = selectedUsers[0]
currentEditingRow.value.userName = selectedUser.name
currentEditingRow.value.userId = selectedUser.id currentEditingRow.value.userId = selectedUser.id
} else { } else {
currentEditingRow.value.name = '' currentEditingRow.value.name = ''
@ -238,20 +398,20 @@ const closeSelectUser = () => {
} }
const openProjectManagerSelect = () => { const openProjectManagerSelect = () => {
projectManagerSelectedUser.value = formData.projectManager projectManagerSelectedUser.value = formData.projectLeader
? [{ id: formData.projectManagerId, name: formData.projectManager }] ? [{ id: formData.projectLeaderId, name: formData.projectLeader }]
: [] : []
showProjectManagerSelect.value = true showProjectManagerSelect.value = true
} }
const handleProjectManagerConfirm = (users) => { const handleProjectManagerConfirm = users => {
if (users.length > 0) { if (users.length > 0) {
const selectedUser = users[0] const selectedUser = users[0]
formData.projectManager = selectedUser.name formData.projectLeaderName = selectedUser.name
formData.projectManagerId = selectedUser.id formData.projectLeader = selectedUser.id
} else { } else {
formData.projectManager = '' formData.projectLeaderName = ''
formData.projectManagerId = null formData.projectLeader = null
} }
showProjectManagerSelect.value = false showProjectManagerSelect.value = false
} }
@ -268,65 +428,122 @@ const handleCurrentChange = val => {
console.log('当前页:', val) console.log('当前页:', val)
} }
const handleRoleChange = (row) => { const showTimesheet = row => {
}
const showTimesheet = (row) => {
router.push({ router.push({
name: 'workLog', path: '/worklog/list',
params: { userId: row.userId }, query: { userId: row.userId, projectId: formData.projectId },
query: { userName: row.name }
}) })
} }
const deleteDialogVisible = ref(false) const deleteDialogVisible = ref(false)
const userToDelete = ref(null)
const confirmDelete = (row) => { const confirmDelete = row => {
userToDelete.value = row ElMessageBox.confirm('确定要删除该成员吗?', '提示', {
deleteDialogVisible.value = true confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteUser(row)
})
.catch(() => {
//
})
} }
const handleDelete = () => { const deleteUser = async row => {
const index = tableData.value.findIndex(item => item.id === userToDelete.value.id) try {
if (index !== -1) { await projectApi.deleteProjectUser(row.teamId)
tableData.value.splice(index, 1) ElMessage.success('成员删除成功')
total.value = tableData.value.length getProjectUser()
ElMessage.success('已删除该成员') } catch (error) {
deleteDialogVisible.value = false console.error('删除成员失败', error)
userToDelete.value = null ElMessage.error('删除成员失败')
} }
} }
const tableHeight = ref(400) const tableHeight = ref(400)
//
onMounted(() => { const getProjectCode = async () => {
const projectData = route.params.projectData || route.query.projectData const res = await projectApi.getProjectCode()
if (projectData) { formData.projectCode = res.data
Object.assign(formData, projectData) }
} else if (route.query.id) {
fetchProjectData(route.query.id)
}
})
const fetchProjectData = async id => { const fetchProjectData = async id => {
try { try {
const projectData = { const response = await projectApi.getProjectDetail(id)
id: id, const projectData = response.data
projectName: `项目${id}`, //
projectCode: `XM00${id}`, formData.startDate = projectData.startDate ? new Date(projectData.startDate).toISOString().split('T')[0] : ''
projectManager: '张三', formData.endDate = projectData.endDate ? new Date(projectData.endDate).toISOString().split('T')[0] : ''
budgetDays: 30, //
projectStatus: 'ongoing', Object.assign(formData, {
startDate: '2024-08-31', ...projectData,
endDate: '2024-09-30', startDate: formData.startDate,
} endDate: formData.endDate,
})
Object.assign(formData, projectData) updateProjectState() //
} catch (error) { } catch (error) {
console.error('获取项目数据失败', 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> </script>
<style scoped> <style scoped>
@ -341,6 +558,7 @@ const fetchProjectData = async id => {
} }
.custom-form { .custom-form {
width: 80%; width: 80%;
margin-bottom: 20px;
} }
.form-container { .form-container {
width: 100%; width: 100%;
@ -388,8 +606,16 @@ const fetchProjectData = async id => {
} }
.table-actions { .table-actions {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
align-self: flex-start; }
.form-actions {
display: flex;
gap: 10px;
} }
:deep(.el-table) { :deep(.el-table) {
@ -447,4 +673,4 @@ const fetchProjectData = async id => {
height: 64px; height: 64px;
margin-bottom: 16px; margin-bottom: 16px;
} }
</style> </style>

View File

@ -6,16 +6,19 @@
<el-input v-model="searchForm.projectName" placeholder="项目名称" /> <el-input v-model="searchForm.projectName" placeholder="项目名称" />
</el-form-item> </el-form-item>
<el-form-item label="负责人" class="form-item"> <el-form-item label="负责人" class="form-item">
<el-select v-model="searchForm.manager" placeholder="负责人"> <el-input
<el-option label="张三" value="张三" /> v-model="searchForm.projectLeaderName"
<el-option label="李四" value="李四" /> placeholder="负责人"
</el-select> readonly
@click="openUserSelectDialog"
>
</el-input>
</el-form-item> </el-form-item>
<el-form-item label="项目状态" class="form-item"> <el-form-item label="项目状态" class="form-item">
<el-select v-model="searchForm.status" placeholder="项目状态"> <el-select v-model="searchForm.projectState" placeholder="项目状态">
<el-option label="待启动" value="待启动" /> <el-option label="未启动" value="0" />
<el-option label="进行中" value="进行中" /> <el-option label="进行中" value="1" />
<el-option label="已完成" value="已完成" /> <el-option label="已完成" value="2" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item class="search-buttons"> <el-form-item class="search-buttons">
@ -64,6 +67,15 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 用户选择弹框 -->
<SelectUser
v-model:dialogVisible="userSelectDialogVisible"
:multiSelect="false"
:currentSelectedUser="currentSelectedUser"
@close="handleUserSelectClose"
@confirm="handleUserConfirm"
/>
</div> </div>
</template> </template>
@ -71,26 +83,68 @@
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import CustomTable from '@/components/table.vue' import CustomTable from '@/components/table.vue'
import SelectUser from '@/components/selectUser.vue'
import { WarningFilled } from '@element-plus/icons-vue' import { WarningFilled } from '@element-plus/icons-vue'
import { projectApi } from '@/utils/api'
const router = useRouter() const router = useRouter()
const searchForm = reactive({ const searchForm = reactive({
projectName: '', projectName: '',
manager: '', projectLeaderName: '',
status: '', projectLeader: '',
projectState: '',
}) })
const columns = [ const columns = [
{ prop: 'projectName', label: '项目名称' }, { prop: 'projectName', label: '项目名称' },
{ prop: 'projectCode', label: '项目编号' }, { prop: 'projectCode', label: '项目编号' },
{ prop: 'manager', label: '负责人' }, { prop: 'projectLeaderName', label: '负责人' },
{ prop: 'budgetDays', label: '预计工时(天)' }, { prop: 'budgetDate', label: '预计工时(天)' },
{ prop: 'startDate', label: '开始时间' }, {
{ prop: 'endDate', label: '结束时间' }, prop: 'startDate',
{ prop: 'status', label: '项目状态' }, label: '开始时间',
{ prop: 'memberCount', label: '参与项目人数' }, type: 'status',
{ prop: 'createdBy', label: '项目创建人' }, 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', prop: 'operation',
label: '操作', label: '操作',
@ -105,6 +159,35 @@ const total = ref(0)
const deleteDialogVisible = ref(false) const deleteDialogVisible = ref(false)
const currentDeleteItem = ref(null) 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 = () => { const onSearch = () => {
// //
console.log('Search with:', searchForm) console.log('Search with:', searchForm)
@ -125,7 +208,7 @@ const addProject = () => {
const handleEdit = row => { const handleEdit = row => {
router.push({ router.push({
path: '/project/detail', path: '/project/detail',
query: { id: row.id }, query: { id: row.projectId },
state: { projectData: row }, state: { projectData: row },
}) })
} }
@ -140,42 +223,35 @@ const handleCloseDeleteDialog = () => {
currentDeleteItem.value = null currentDeleteItem.value = null
} }
const confirmDelete = () => { const confirmDelete = async () => {
// //
console.log('Delete project:', currentDeleteItem.value) await projectApi.deleteProject(currentDeleteItem.value.projectId)
deleteDialogVisible.value = false deleteDialogVisible.value = false
currentDeleteItem.value = null currentDeleteItem.value = null
ElMessage.success('删除成功')
fetchProjectList() fetchProjectList()
} }
const handleSizeChange = val => { const handleSizeChange = val => {
console.log(`每页 ${val}`) pageSize.value = val
pageNum.value = 1 //
fetchProjectList() fetchProjectList()
} }
const handleCurrentChange = val => { const handleCurrentChange = val => {
console.log(`当前页: ${val}`) pageNum.value = val
fetchProjectList() fetchProjectList()
} }
const fetchProjectList = () => { const fetchProjectList = async () => {
// 使API // 使API
const fakeData = Array(20) const res = await projectApi.listProject({
.fill(null) ...searchForm,
.map((_, index) => ({ pageNum: pageNum.value,
id: index + 1, pageSize: pageSize.value,
projectName: `项目${index + 1}`, })
projectCode: `XM00${index + 1}`, tableData.value = res.rows
manager: ['张三', '李四', '王五'][index % 3], total.value = res.total
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
} }
onMounted(() => { onMounted(() => {
@ -278,4 +354,4 @@ onMounted(() => {
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
</style> </style>

View File

@ -60,17 +60,6 @@ export default ({ mode }) => {
}, },
// 设置代理 // 设置代理
proxy: { 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]: { [env.VITE_APP_REQUESTURL]: {
target: env.VITE_APP_PROXYURL, target: env.VITE_APP_PROXYURL,