feat: 完善前端页面 - 客户管理、项目管理、合同管理、用户管理完整功能

This commit is contained in:
zhangjf 2026-02-15 16:28:56 +08:00
parent 69a3d62c3e
commit ed19ab4739
6 changed files with 1205 additions and 23 deletions

View File

@ -0,0 +1,77 @@
import request from '../utils/request'
export const getProjectList = (params) => {
return request({
url: '/proj/api/v1/project/list',
method: 'get',
params
})
}
export const getProjectById = (id) => {
return request({
url: `/proj/api/v1/project/${id}`,
method: 'get'
})
}
export const createProject = (data) => {
return request({
url: '/proj/api/v1/project',
method: 'post',
data
})
}
export const updateProject = (id, data) => {
return request({
url: `/proj/api/v1/project/${id}`,
method: 'put',
data
})
}
export const deleteProject = (id) => {
return request({
url: `/proj/api/v1/project/${id}`,
method: 'delete'
})
}
export const updateProjectStatus = (id, status) => {
return request({
url: `/proj/api/v1/project/${id}/status/${status}`,
method: 'put'
})
}
export const getContractList = (params) => {
return request({
url: '/proj/api/v1/contract/list',
method: 'get',
params
})
}
export const createContract = (data) => {
return request({
url: '/proj/api/v1/contract',
method: 'post',
data
})
}
export const updateContract = (id, data) => {
return request({
url: `/proj/api/v1/contract/${id}`,
method: 'put',
data
})
}
export const deleteContract = (id) => {
return request({
url: `/proj/api/v1/contract/${id}`,
method: 'delete'
})
}

View File

@ -0,0 +1,55 @@
import request from '../utils/request'
export const getUserList = (params) => {
return request({
url: '/sys/api/v1/user/list',
method: 'get',
params
})
}
export const getUserById = (id) => {
return request({
url: `/sys/api/v1/user/${id}`,
method: 'get'
})
}
export const createUser = (data) => {
return request({
url: '/sys/api/v1/user',
method: 'post',
data
})
}
export const updateUser = (id, data) => {
return request({
url: `/sys/api/v1/user/${id}`,
method: 'put',
data
})
}
export const deleteUser = (id) => {
return request({
url: `/sys/api/v1/user/${id}`,
method: 'delete'
})
}
export const resetPassword = (id, newPassword) => {
return request({
url: `/sys/api/v1/user/${id}/reset-password`,
method: 'put',
params: { newPassword }
})
}
export const updateUserStatus = (id, status) => {
return request({
url: `/sys/api/v1/user/${id}/status`,
method: 'put',
params: { status }
})
}

View File

@ -60,13 +60,77 @@
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="客户编码" prop="customerCode">
<el-input v-model="form.customerCode" placeholder="请输入客户编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户名称" prop="customerName">
<el-input v-model="form.customerName" placeholder="请输入客户名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="客户类型" prop="customerType">
<el-select v-model="form.customerType" placeholder="请选择" style="width: 100%">
<el-option label="企业客户" value="ENTERPRISE" />
<el-option label="个人客户" value="PERSONAL" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业">
<el-input v-model="form.industry" placeholder="请输入所属行业" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人">
<el-input v-model="form.legalPerson" placeholder="请输入联系人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话">
<el-input v-model="form.phone" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="地址">
<el-input v-model="form.address" placeholder="请输入地址" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getCustomerList, deleteCustomer } from '../../api/customer'
import { getCustomerList, createCustomer, updateCustomer, deleteCustomer } from '../../api/customer'
const loading = ref(false)
const tableData = ref([])
@ -111,12 +175,74 @@ const handleReset = () => {
handleSearch()
}
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formRef = ref(null)
const form = reactive({
customerId: null,
customerCode: '',
customerName: '',
customerType: 'ENTERPRISE',
industry: '',
legalPerson: '',
phone: '',
email: '',
address: '',
remark: ''
})
const rules = {
customerCode: [{ required: true, message: '请输入客户编码', trigger: 'blur' }],
customerName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
customerType: [{ required: true, message: '请选择客户类型', trigger: 'change' }]
}
const handleAdd = () => {
console.log('新增客户')
dialogTitle.value = '新增客户'
resetForm()
dialogVisible.value = true
}
const handleEdit = (row) => {
console.log('编辑客户:', row)
dialogTitle.value = '编辑客户'
resetForm()
Object.assign(form, row)
dialogVisible.value = true
}
const resetForm = () => {
form.customerId = null
form.customerCode = ''
form.customerName = ''
form.customerType = 'ENTERPRISE'
form.industry = ''
form.legalPerson = ''
form.phone = ''
form.email = ''
form.address = ''
form.remark = ''
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (form.customerId) {
await updateCustomer(form.customerId, form)
ElMessage.success('更新成功')
} else {
await createCustomer(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('保存失败:', error)
}
}
})
}
const handleDelete = (row) => {

View File

@ -4,14 +4,314 @@
<template #header>
<div class="card-header">
<span>合同管理</span>
<el-button type="primary">新增合同</el-button>
<el-button type="primary" @click="handleAdd">新增合同</el-button>
</div>
</template>
<p>合同管理页面 - 开发中</p>
<el-form :inline="true" class="search-form">
<el-form-item label="合同名称">
<el-input v-model="searchForm.contractName" placeholder="请输入合同名称" />
</el-form-item>
<el-form-item label="合同状态">
<el-select v-model="searchForm.contractStatus" placeholder="请选择" clearable>
<el-option label="草稿" value="DRAFT" />
<el-option label="已签署" value="SIGNED" />
<el-option label="执行中" value="EXECUTING" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已终止" value="TERMINATED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border v-loading="loading">
<el-table-column prop="contractCode" label="合同编码" width="120" />
<el-table-column prop="contractName" label="合同名称" />
<el-table-column prop="contractStatus" label="合同状态" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.contractStatus)">
{{ getStatusText(scope.row.contractStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="contractAmount" label="合同金额" width="120">
<template #default="scope">
{{ formatAmount(scope.row.contractAmount) }}
</template>
</el-table-column>
<el-table-column prop="signDate" label="签署日期" width="120" />
<el-table-column prop="effectiveDate" label="生效日期" width="120" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同编码" prop="contractCode">
<el-input v-model="form.contractCode" placeholder="请输入合同编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同名称" prop="contractName">
<el-input v-model="form.contractName" placeholder="请输入合同名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同金额" prop="contractAmount">
<el-input-number v-model="form.contractAmount" :precision="2" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同类型">
<el-select v-model="form.contractType" placeholder="请选择" style="width: 100%">
<el-option label="销售合同" value="SALES" />
<el-option label="采购合同" value="PURCHASE" />
<el-option label="服务合同" value="SERVICE" />
<el-option label="其他" value="OTHER" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="签署日期">
<el-date-picker v-model="form.signDate" type="date" placeholder="选择日期" style="width: 100%" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生效日期">
<el-date-picker v-model="form.effectiveDate" type="date" placeholder="选择日期" style="width: 100%" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="到期日期">
<el-date-picker v-model="form.expiryDate" type="date" placeholder="选择日期" style="width: 100%" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="签署地点">
<el-input v-model="form.signLocation" placeholder="请输入签署地点" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="付款条款">
<el-input v-model="form.paymentTerms" type="textarea" rows="2" placeholder="请输入付款条款" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getContractList, createContract, updateContract, deleteContract } from '../../api/project'
const loading = ref(false)
const tableData = ref([])
const searchForm = reactive({
contractName: '',
contractStatus: ''
})
const page = reactive({
current: 1,
size: 10,
total: 0
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formRef = ref(null)
const form = reactive({
contractId: null,
contractCode: '',
contractName: '',
contractType: 'SALES',
contractAmount: 0,
signDate: '',
effectiveDate: '',
expiryDate: '',
signLocation: '',
paymentTerms: '',
remark: ''
})
const rules = {
contractCode: [{ required: true, message: '请输入合同编码', trigger: 'blur' }],
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
contractAmount: [{ required: true, message: '请输入合同金额', trigger: 'blur' }]
}
const fetchData = async () => {
loading.value = true
try {
const res = await getContractList({
current: page.current,
size: page.size,
contractName: searchForm.contractName,
contractStatus: searchForm.contractStatus
})
tableData.value = res.records
page.total = res.total
} catch (error) {
console.error('获取合同列表失败:', error)
} finally {
loading.value = false
}
}
const handleSearch = () => {
page.current = 1
fetchData()
}
const handleReset = () => {
searchForm.contractName = ''
searchForm.contractStatus = ''
handleSearch()
}
const handleAdd = () => {
dialogTitle.value = '新增合同'
resetForm()
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogTitle.value = '编辑合同'
resetForm()
Object.assign(form, row)
dialogVisible.value = true
}
const resetForm = () => {
form.contractId = null
form.contractCode = ''
form.contractName = ''
form.contractType = 'SALES'
form.contractAmount = 0
form.signDate = ''
form.effectiveDate = ''
form.expiryDate = ''
form.signLocation = ''
form.paymentTerms = ''
form.remark = ''
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (form.contractId) {
await updateContract(form.contractId, form)
ElMessage.success('更新成功')
} else {
await createContract(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('保存失败:', error)
}
}
})
}
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该合同吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteContract(row.contractId)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
console.error('删除失败:', error)
}
})
}
const handleSizeChange = (val) => {
page.size = val
fetchData()
}
const handleCurrentChange = (val) => {
page.current = val
fetchData()
}
const getStatusType = (status) => {
const map = {
'DRAFT': 'info',
'SIGNED': 'warning',
'EXECUTING': 'primary',
'COMPLETED': 'success',
'TERMINATED': 'danger'
}
return map[status] || 'info'
}
const getStatusText = (status) => {
const map = {
'DRAFT': '草稿',
'SIGNED': '已签署',
'EXECUTING': '执行中',
'COMPLETED': '已完成',
'TERMINATED': '已终止'
}
return map[status] || status
}
const formatAmount = (amount) => {
if (!amount) return '¥0.00'
return '¥' + parseFloat(amount).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')
}
onMounted(() => {
fetchData()
})
</script>
<style scoped>
.page-container {
padding: 20px;
@ -22,4 +322,13 @@
justify-content: space-between;
align-items: center;
}
.search-form {
margin-bottom: 20px;
}
.pagination {
margin-top: 20px;
justify-content: flex-end;
}
</style>

View File

@ -4,14 +4,328 @@
<template #header>
<div class="card-header">
<span>项目管理</span>
<el-button type="primary">新增项目</el-button>
<el-button type="primary" @click="handleAdd">新增项目</el-button>
</div>
</template>
<p>项目管理页面 - 开发中</p>
<el-form :inline="true" class="search-form">
<el-form-item label="项目名称">
<el-input v-model="searchForm.projectName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="项目状态">
<el-select v-model="searchForm.projectStatus" placeholder="请选择" clearable>
<el-option label="草稿" value="DRAFT" />
<el-option label="进行中" value="IN_PROGRESS" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border v-loading="loading">
<el-table-column prop="projectCode" label="项目编码" width="120" />
<el-table-column prop="projectName" label="项目名称" />
<el-table-column prop="projectStatus" label="项目状态" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.projectStatus)">
{{ getStatusText(scope.row.projectStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="projectAmount" label="项目金额" width="120">
<template #default="scope">
{{ formatAmount(scope.row.projectAmount) }}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" width="120" />
<el-table-column prop="endDate" label="结束日期" width="120" />
<el-table-column prop="projectManager" label="项目经理" width="120" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="success" link @click="handleUpdateStatus(scope.row)">状态</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目编码" prop="projectCode">
<el-input v-model="form.projectCode" placeholder="请输入项目编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目金额" prop="projectAmount">
<el-input-number v-model="form.projectAmount" :precision="2" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目经理" prop="projectManager">
<el-input v-model="form.projectManager" placeholder="请输入项目经理" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始日期" prop="startDate">
<el-date-picker v-model="form.startDate" type="date" placeholder="选择日期" style="width: 100%" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="endDate">
<el-date-picker v-model="form.endDate" type="date" placeholder="选择日期" style="width: 100%" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="项目描述" prop="projectDesc">
<el-input v-model="form.projectDesc" type="textarea" rows="3" placeholder="请输入项目描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
<!-- 状态更新对话框 -->
<el-dialog v-model="statusDialogVisible" title="更新项目状态" width="400px">
<el-form label-width="100px">
<el-form-item label="项目状态">
<el-select v-model="statusForm.projectStatus" placeholder="请选择状态" style="width: 100%">
<el-option label="草稿" value="DRAFT" />
<el-option label="进行中" value="IN_PROGRESS" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="statusDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleStatusSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getProjectList, createProject, updateProject, deleteProject, updateProjectStatus } from '../../api/project'
const loading = ref(false)
const tableData = ref([])
const searchForm = reactive({
projectName: '',
projectStatus: ''
})
const page = reactive({
current: 1,
size: 10,
total: 0
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formRef = ref(null)
const form = reactive({
projectId: null,
projectCode: '',
projectName: '',
projectAmount: 0,
projectManager: '',
startDate: '',
endDate: '',
projectDesc: ''
})
const rules = {
projectCode: [{ required: true, message: '请输入项目编码', trigger: 'blur' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
projectAmount: [{ required: true, message: '请输入项目金额', trigger: 'blur' }]
}
const statusDialogVisible = ref(false)
const statusForm = reactive({
projectId: null,
projectStatus: ''
})
const fetchData = async () => {
loading.value = true
try {
const res = await getProjectList({
current: page.current,
size: page.size,
projectName: searchForm.projectName,
projectStatus: searchForm.projectStatus
})
tableData.value = res.records
page.total = res.total
} catch (error) {
console.error('获取项目列表失败:', error)
} finally {
loading.value = false
}
}
const handleSearch = () => {
page.current = 1
fetchData()
}
const handleReset = () => {
searchForm.projectName = ''
searchForm.projectStatus = ''
handleSearch()
}
const handleAdd = () => {
dialogTitle.value = '新增项目'
resetForm()
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogTitle.value = '编辑项目'
resetForm()
Object.assign(form, row)
dialogVisible.value = true
}
const resetForm = () => {
form.projectId = null
form.projectCode = ''
form.projectName = ''
form.projectAmount = 0
form.projectManager = ''
form.startDate = ''
form.endDate = ''
form.projectDesc = ''
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (form.projectId) {
await updateProject(form.projectId, form)
ElMessage.success('更新成功')
} else {
await createProject(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('保存失败:', error)
}
}
})
}
const handleUpdateStatus = (row) => {
statusForm.projectId = row.projectId
statusForm.projectStatus = row.projectStatus
statusDialogVisible.value = true
}
const handleStatusSubmit = async () => {
try {
await updateProjectStatus(statusForm.projectId, statusForm.projectStatus)
ElMessage.success('状态更新成功')
statusDialogVisible.value = false
fetchData()
} catch (error) {
console.error('状态更新失败:', error)
}
}
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该项目吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteProject(row.projectId)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
console.error('删除失败:', error)
}
})
}
const handleSizeChange = (val) => {
page.size = val
fetchData()
}
const handleCurrentChange = (val) => {
page.current = val
fetchData()
}
const getStatusType = (status) => {
const map = {
'DRAFT': 'info',
'IN_PROGRESS': 'primary',
'COMPLETED': 'success',
'CANCELLED': 'danger'
}
return map[status] || 'info'
}
const getStatusText = (status) => {
const map = {
'DRAFT': '草稿',
'IN_PROGRESS': '进行中',
'COMPLETED': '已完成',
'CANCELLED': '已取消'
}
return map[status] || status
}
const formatAmount = (amount) => {
if (!amount) return '¥0.00'
return '¥' + parseFloat(amount).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')
}
onMounted(() => {
fetchData()
})
</script>
<style scoped>
.page-container {
padding: 20px;
@ -22,4 +336,13 @@
justify-content: space-between;
align-items: center;
}
.search-form {
margin-bottom: 20px;
}
.pagination {
margin-top: 20px;
justify-content: flex-end;
}
</style>

View File

@ -7,41 +7,324 @@
<el-button type="primary" @click="handleAdd">新增用户</el-button>
</div>
</template>
<el-table :data="tableData" border>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="realName" label="姓名" />
<el-table-column prop="phone" label="手机号" />
<el-form :inline="true" class="search-form">
<el-form-item label="用户名">
<el-input v-model="searchForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="searchForm.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择" clearable>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border v-loading="loading">
<el-table-column prop="username" label="用户名" width="120" />
<el-table-column prop="realName" label="姓名" width="120" />
<el-table-column prop="phone" label="手机号" width="140" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="status" label="状态">
<el-table-column prop="position" label="职位" width="120" />
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default>
<el-button type="primary" link>编辑</el-button>
<el-button type="danger" link>删除</el-button>
<el-table-column label="操作" width="300" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="warning" link @click="handleResetPassword(scope.row)">重置密码</el-button>
<el-button
:type="scope.row.status === 1 ? 'danger' : 'success'"
link
@click="handleToggleStatus(scope.row)"
>
{{ scope.row.status === 1 ? '禁用' : '启用' }}
</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" :disabled="!!form.userId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="性别">
<el-select v-model="form.gender" placeholder="请选择" style="width: 100%">
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
<el-option label="保密" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位">
<el-input v-model="form.position" placeholder="请输入职位" />
</el-form-item>
</el-col>
</el-row>
<el-form-item v-if="!form.userId" label="密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
<!-- 重置密码对话框 -->
<el-dialog v-model="passwordDialogVisible" title="重置密码" width="400px">
<el-form label-width="100px">
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="passwordForm.newPassword" type="password" placeholder="请输入新密码" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handlePasswordSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserList, createUser, updateUser, deleteUser, resetPassword, updateUserStatus } from '../../api/user'
const tableData = ref([
{ username: 'admin', realName: '管理员', phone: '13800138000', email: 'admin@example.com', status: 1 },
{ username: 'zhangsan', realName: '张三', phone: '13800138001', email: 'zhangsan@example.com', status: 1 }
])
const loading = ref(false)
const tableData = ref([])
const searchForm = reactive({
username: '',
realName: '',
status: null
})
const page = reactive({
current: 1,
size: 10,
total: 0
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formRef = ref(null)
const form = reactive({
userId: null,
username: '',
password: '',
realName: '',
phone: '',
email: '',
gender: 1,
position: ''
})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const passwordDialogVisible = ref(false)
const passwordForm = reactive({
userId: null,
newPassword: ''
})
const fetchData = async () => {
loading.value = true
try {
const res = await getUserList({
current: page.current,
size: page.size,
username: searchForm.username,
realName: searchForm.realName,
status: searchForm.status
})
tableData.value = res.records
page.total = res.total
} catch (error) {
console.error('获取用户列表失败:', error)
} finally {
loading.value = false
}
}
const handleSearch = () => {
page.current = 1
fetchData()
}
const handleReset = () => {
searchForm.username = ''
searchForm.realName = ''
searchForm.status = null
handleSearch()
}
const handleAdd = () => {
console.log('新增用户')
dialogTitle.value = '新增用户'
resetForm()
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogTitle.value = '编辑用户'
resetForm()
Object.assign(form, row)
dialogVisible.value = true
}
const resetForm = () => {
form.userId = null
form.username = ''
form.password = ''
form.realName = ''
form.phone = ''
form.email = ''
form.gender = 1
form.position = ''
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (form.userId) {
await updateUser(form.userId, form)
ElMessage.success('更新成功')
} else {
await createUser(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('保存失败:', error)
}
}
})
}
const handleResetPassword = (row) => {
passwordForm.userId = row.userId
passwordForm.newPassword = ''
passwordDialogVisible.value = true
}
const handlePasswordSubmit = async () => {
if (!passwordForm.newPassword) {
ElMessage.warning('请输入新密码')
return
}
try {
await resetPassword(passwordForm.userId, passwordForm.newPassword)
ElMessage.success('密码重置成功')
passwordDialogVisible.value = false
} catch (error) {
console.error('密码重置失败:', error)
}
}
const handleToggleStatus = (row) => {
const newStatus = row.status === 1 ? 0 : 1
const actionText = newStatus === 1 ? '启用' : '禁用'
ElMessageBox.confirm(`确定要${actionText}该用户吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await updateUserStatus(row.userId, newStatus)
ElMessage.success(`${actionText}成功`)
fetchData()
} catch (error) {
console.error('状态更新失败:', error)
}
})
}
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteUser(row.userId)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
console.error('删除失败:', error)
}
})
}
const handleSizeChange = (val) => {
page.size = val
fetchData()
}
const handleCurrentChange = (val) => {
page.current = val
fetchData()
}
onMounted(() => {
fetchData()
})
</script>
<style scoped>
@ -54,4 +337,13 @@ const handleAdd = () => {
justify-content: space-between;
align-items: center;
}
.search-form {
margin-bottom: 20px;
}
.pagination {
margin-top: 20px;
justify-content: flex-end;
}
</style>