Initial commit: One Pipe System

完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

View File

@@ -0,0 +1,498 @@
<template>
<div class="page-content">
<!-- 搜索和操作区 -->
<ElRow :gutter="12">
<ElCol :xs="24" :sm="12" :lg="6">
<ElInput v-model="searchQuery" placeholder="企业名称/联系人" clearable />
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElSelect v-model="statusFilter" placeholder="状态筛选" clearable style="width: 100%">
<ElOption label="全部" value="" />
<ElOption label="正常" value="active" />
<ElOption label="禁用" value="disabled" />
<ElOption label="待审核" value="pending" />
</ElSelect>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElSelect v-model="roleFilter" placeholder="角色筛选" clearable style="width: 100%">
<ElOption label="全部" value="" />
<ElOption label="企业管理员" value="admin" />
<ElOption label="企业用户" value="user" />
</ElSelect>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
<ElButton v-ripple type="primary" @click="showDialog('add')">新增企业客户</ElButton>
</ElCol>
</ElRow>
<!-- 企业客户列表 -->
<ArtTable :data="filteredData" index style="margin-top: 20px">
<template #default>
<ElTableColumn label="企业名称" prop="enterpriseName" min-width="180" show-overflow-tooltip />
<ElTableColumn label="统一社会信用代码" prop="creditCode" width="180" />
<ElTableColumn label="联系人" prop="contactPerson" width="120" />
<ElTableColumn label="联系电话" prop="contactPhone" width="130" />
<ElTableColumn label="所属角色" prop="roleName" width="120">
<template #default="scope">
<ElTag>{{ scope.row.roleName }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="卡片数量" prop="cardCount" width="100">
<template #default="scope">
<span style="color: var(--el-color-primary)">{{ scope.row.cardCount }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="设备数量" prop="deviceCount" width="100">
<template #default="scope">
<span style="color: var(--el-color-success)">{{ scope.row.deviceCount }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="账户余额" prop="balance" width="120">
<template #default="scope"> ¥{{ scope.row.balance.toFixed(2) }} </template>
</ElTableColumn>
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag v-if="scope.row.status === 'active'" type="success">正常</ElTag>
<ElTag v-else-if="scope.row.status === 'disabled'" type="danger">禁用</ElTag>
<ElTag v-else-if="scope.row.status === 'pending'" type="warning">待审核</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="createTime" width="180" />
<ElTableColumn fixed="right" label="操作" width="260">
<template #default="scope">
<el-button link :icon="View" @click="viewDetail(scope.row)">详情</el-button>
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
<el-button
link
:type="scope.row.status === 'active' ? 'warning' : 'success'"
@click="toggleStatus(scope.row)"
>
{{ scope.row.status === 'active' ? '禁用' : '启用' }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</ElTableColumn>
</template>
</ArtTable>
<!-- 新增/编辑企业客户对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增企业客户' : '编辑企业客户'"
width="700px"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="140px">
<ElDivider content-position="left">企业信息</ElDivider>
<ElFormItem label="企业名称" prop="enterpriseName">
<ElInput v-model="form.enterpriseName" placeholder="请输入企业名称" />
</ElFormItem>
<ElFormItem label="统一社会信用代码" prop="creditCode">
<ElInput v-model="form.creditCode" placeholder="请输入统一社会信用代码" maxlength="18" />
</ElFormItem>
<ElFormItem label="企业地址" prop="address">
<ElInput v-model="form.address" placeholder="请输入企业地址" />
</ElFormItem>
<ElFormItem label="营业执照" prop="businessLicense">
<ElUpload
:action="uploadUrl"
:limit="1"
list-type="picture-card"
accept="image/*"
>
<el-icon><Plus /></el-icon>
</ElUpload>
<div style="color: var(--el-text-color-secondary); font-size: 12px">支持 JPGPNG 格式</div>
</ElFormItem>
<ElDivider content-position="left">联系人信息</ElDivider>
<ElFormItem label="联系人" prop="contactPerson">
<ElInput v-model="form.contactPerson" placeholder="请输入联系人姓名" />
</ElFormItem>
<ElFormItem label="联系电话" prop="contactPhone">
<ElInput v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" />
</ElFormItem>
<ElFormItem label="联系邮箱" prop="contactEmail">
<ElInput v-model="form.contactEmail" placeholder="请输入联系邮箱" />
</ElFormItem>
<ElDivider content-position="left">账号配置</ElDivider>
<ElFormItem label="登录账号" prop="username">
<ElInput v-model="form.username" placeholder="请输入登录账号" :disabled="dialogType === 'edit'" />
<div style="color: var(--el-text-color-secondary); font-size: 12px">只能登录企业端</div>
</ElFormItem>
<ElFormItem v-if="dialogType === 'add'" label="登录密码" prop="password">
<ElInput v-model="form.password" type="password" placeholder="请输入登录密码" show-password />
</ElFormItem>
<ElFormItem label="分配角色" prop="roleId">
<ElSelect v-model="form.roleId" placeholder="请选择客户角色" style="width: 100%">
<ElOption
v-for="role in availableRoles"
:key="role.id"
:label="role.roleName"
:value="role.id"
/>
</ElSelect>
<div style="color: var(--el-text-color-secondary); font-size: 12px">角色决定企业客户的能力边界</div>
</ElFormItem>
<ElFormItem label="初始余额" prop="initialBalance">
<ElInputNumber v-model="form.initialBalance" :min="0" :precision="2" style="width: 100%" />
<span style="margin-left: 8px"></span>
</ElFormItem>
<ElFormItem label="备注" prop="remark">
<ElInput v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
</ElFormItem>
<ElFormItem label="状态">
<ElSwitch v-model="form.status" active-value="active" inactive-value="disabled" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
<!-- 企业详情对话框 -->
<ElDialog v-model="detailDialogVisible" title="企业客户详情" width="800px" align-center>
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="企业名称">{{ currentDetail.enterpriseName }}</ElDescriptionsItem>
<ElDescriptionsItem label="统一社会信用代码">{{ currentDetail.creditCode }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系人">{{ currentDetail.contactPerson }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系电话">{{ currentDetail.contactPhone }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系邮箱">{{ currentDetail.contactEmail }}</ElDescriptionsItem>
<ElDescriptionsItem label="企业地址" :span="2">{{ currentDetail.address }}</ElDescriptionsItem>
<ElDescriptionsItem label="所属角色">
<ElTag>{{ currentDetail.roleName }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="登录账号">{{ currentDetail.username }}</ElDescriptionsItem>
<ElDescriptionsItem label="卡片数量">
<span style="color: var(--el-color-primary)">{{ currentDetail.cardCount }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="设备数量">
<span style="color: var(--el-color-success)">{{ currentDetail.deviceCount }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="账户余额">¥{{ currentDetail.balance.toFixed(2) }}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag v-if="currentDetail.status === 'active'" type="success">正常</ElTag>
<ElTag v-else-if="currentDetail.status === 'disabled'" type="danger">禁用</ElTag>
<ElTag v-else-if="currentDetail.status === 'pending'" type="warning">待审核</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">{{ currentDetail.createTime }}</ElDescriptionsItem>
<ElDescriptionsItem label="最后登录">{{ currentDetail.lastLoginTime || '未登录' }}</ElDescriptionsItem>
<ElDescriptionsItem label="备注" :span="2">{{ currentDetail.remark || '无' }}</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { View, Plus } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
defineOptions({ name: 'EnterpriseCustomer' })
interface EnterpriseCustomer {
id: string
enterpriseName: string
creditCode: string
address: string
contactPerson: string
contactPhone: string
contactEmail: string
username: string
roleId: string
roleName: string
cardCount: number
deviceCount: number
balance: number
status: 'active' | 'disabled' | 'pending'
createTime: string
lastLoginTime?: string
remark?: string
}
const searchQuery = ref('')
const statusFilter = ref('')
const roleFilter = ref('')
const dialogVisible = ref(false)
const detailDialogVisible = ref(false)
const dialogType = ref<'add' | 'edit'>('add')
const formRef = ref<FormInstance>()
const uploadUrl = ref('/api/upload/image')
const form = reactive({
enterpriseName: '',
creditCode: '',
address: '',
businessLicense: '',
contactPerson: '',
contactPhone: '',
contactEmail: '',
username: '',
password: '',
roleId: '',
initialBalance: 0,
remark: '',
status: 'active'
})
const rules = reactive<FormRules>({
enterpriseName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
creditCode: [
{ required: true, message: '请输入统一社会信用代码', trigger: 'blur' },
{ len: 18, message: '统一社会信用代码为18位', trigger: 'blur' }
],
contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
contactPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
contactEmail: [
{ required: true, message: '请输入联系邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
username: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
password: [
{ required: true, message: '请输入登录密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
],
roleId: [{ required: true, message: '请选择客户角色', trigger: 'change' }]
})
const availableRoles = ref([
{ id: '1', roleName: '企业管理员' },
{ id: '2', roleName: '企业用户' },
{ id: '3', roleName: '企业财务' }
])
const mockData = ref<EnterpriseCustomer[]>([
{
id: '1',
enterpriseName: '深圳市科技有限公司',
creditCode: '91440300MA5DXXXX01',
address: '深圳市南山区科技园',
contactPerson: '张经理',
contactPhone: '13800138000',
contactEmail: 'zhang@company.com',
username: 'shenzhen_tech',
roleId: '1',
roleName: '企业管理员',
cardCount: 500,
deviceCount: 450,
balance: 15000.00,
status: 'active',
createTime: '2026-01-01 10:00:00',
lastLoginTime: '2026-01-09 09:30:00',
remark: '重要客户'
},
{
id: '2',
enterpriseName: '北京智能制造有限公司',
creditCode: '91110000MA5DXXXX02',
address: '北京市海淀区中关村',
contactPerson: '李总监',
contactPhone: '13900139000',
contactEmail: 'li@manufacturing.com',
username: 'beijing_smart',
roleId: '1',
roleName: '企业管理员',
cardCount: 800,
deviceCount: 750,
balance: 28000.00,
status: 'active',
createTime: '2026-01-03 14:00:00',
lastLoginTime: '2026-01-09 08:15:00'
},
{
id: '3',
enterpriseName: '上海物联网科技公司',
creditCode: '91310000MA5DXXXX03',
address: '上海市浦东新区张江高科技园区',
contactPerson: '王主管',
contactPhone: '13700137000',
contactEmail: 'wang@iot-shanghai.com',
username: 'shanghai_iot',
roleId: '2',
roleName: '企业用户',
cardCount: 300,
deviceCount: 280,
balance: 8500.00,
status: 'pending',
createTime: '2026-01-08 16:00:00'
}
])
const currentDetail = ref<EnterpriseCustomer>({
id: '',
enterpriseName: '',
creditCode: '',
address: '',
contactPerson: '',
contactPhone: '',
contactEmail: '',
username: '',
roleId: '',
roleName: '',
cardCount: 0,
deviceCount: 0,
balance: 0,
status: 'active',
createTime: ''
})
const filteredData = computed(() => {
let data = mockData.value
if (searchQuery.value) {
data = data.filter(
(item) =>
item.enterpriseName.includes(searchQuery.value) ||
item.contactPerson.includes(searchQuery.value) ||
item.contactPhone.includes(searchQuery.value)
)
}
if (statusFilter.value) {
data = data.filter((item) => item.status === statusFilter.value)
}
if (roleFilter.value) {
data = data.filter((item) => item.roleId === roleFilter.value)
}
return data
})
const handleSearch = () => {}
const showDialog = (type: 'add' | 'edit', row?: EnterpriseCustomer) => {
dialogType.value = type
dialogVisible.value = true
if (type === 'edit' && row) {
Object.assign(form, {
enterpriseName: row.enterpriseName,
creditCode: row.creditCode,
address: row.address,
contactPerson: row.contactPerson,
contactPhone: row.contactPhone,
contactEmail: row.contactEmail,
username: row.username,
roleId: row.roleId,
initialBalance: row.balance,
remark: row.remark,
status: row.status
})
} else {
Object.assign(form, {
enterpriseName: '',
creditCode: '',
address: '',
businessLicense: '',
contactPerson: '',
contactPhone: '',
contactEmail: '',
username: '',
password: '',
roleId: '',
initialBalance: 0,
remark: '',
status: 'active'
})
}
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
if (dialogType.value === 'add') {
const selectedRole = availableRoles.value.find((r) => r.id === form.roleId)
mockData.value.unshift({
id: Date.now().toString(),
enterpriseName: form.enterpriseName,
creditCode: form.creditCode,
address: form.address,
contactPerson: form.contactPerson,
contactPhone: form.contactPhone,
contactEmail: form.contactEmail,
username: form.username,
roleId: form.roleId,
roleName: selectedRole?.roleName || '',
cardCount: 0,
deviceCount: 0,
balance: form.initialBalance,
status: form.status as any,
createTime: new Date().toLocaleString('zh-CN'),
remark: form.remark
})
ElMessage.success('企业客户创建成功')
} else {
ElMessage.success('企业客户更新成功')
}
dialogVisible.value = false
formEl.resetFields()
}
})
}
const viewDetail = (row: EnterpriseCustomer) => {
currentDetail.value = { ...row }
detailDialogVisible.value = true
}
const toggleStatus = (row: EnterpriseCustomer) => {
const action = row.status === 'active' ? '禁用' : '启用'
ElMessageBox.confirm(`确定${action}该企业客户吗?`, `${action}确认`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
row.status = row.status === 'active' ? 'disabled' : 'active'
ElMessage.success(`${action}成功`)
})
}
const handleDelete = (row: EnterpriseCustomer) => {
ElMessageBox.confirm('删除后无法恢复,确定要删除该企业客户吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(() => {
const index = mockData.value.findIndex((item) => item.id === row.id)
if (index !== -1) mockData.value.splice(index, 1)
ElMessage.success('删除成功')
})
}
</script>
<style lang="scss" scoped>
.page-content {
:deep(.el-upload-list--picture-card .el-upload-list__item) {
width: 148px;
height: 148px;
}
}
</style>