feat: 发布智能体版

This commit is contained in:
augushong
2026-03-26 20:22:34 +08:00
parent 7ee9e102a5
commit 8cc08bcb8c
138 changed files with 7964 additions and 660 deletions

View File

@@ -0,0 +1,57 @@
import { request } from '../utils/request'
/**
* 登录
* @param {Object} data - 登录参数
* @param {string} data.username - 用户名
* @param {string} data.password - 密码
* @param {string} [data.captcha] - 验证码
* @param {number} [data.keep_login] - 是否保持登录
* @returns {Promise<Object>} 登录响应
*/
export function login(data) {
return request({
url: '/admin/login/index',
method: 'POST',
data,
})
}
/**
* 退出登录
* @returns {Promise<Object>}
*/
export function logout() {
return request({
url: '/admin/login/out',
method: 'POST',
})
}
/**
* 获取当前用户信息
* @returns {Promise<Object>}
*/
export function getProfile() {
return request({
url: '/admin/index/editAdmin',
method: 'GET',
data: { get_page_data: 1 },
})
}
/**
* 修改密码
* @param {Object} data - 修改密码参数
* @param {string} data.old_password - 旧密码
* @param {string} data.new_password - 新密码
* @param {string} data.confirm_password - 确认密码
* @returns {Promise<Object>}
*/
export function changePassword(data) {
return request({
url: '/admin/index/changePassword',
method: 'POST',
data,
})
}

View File

@@ -0,0 +1,101 @@
import { request } from '../utils/request'
/**
* 获取权限列表(树形)
* @param {Object} params - 查询参数
* @returns {Promise<Object>}
*/
export function getPermissionTree(params = {}) {
return request({
url: '/admin/auth/index',
method: 'GET',
data: params,
})
}
/**
* 获取权限节点列表
* @param {Object} params - 查询参数
* @returns {Promise<Object>}
*/
export function getPermissionList(params = {}) {
return request({
url: '/admin/auth/index',
method: 'GET',
data: params,
})
}
/**
* 获取权限详情
* @param {number} id - 权限ID
* @returns {Promise<Object>}
*/
export function getPermissionDetail(id) {
return request({
url: '/admin/auth/edit',
method: 'GET',
data: { id },
})
}
/**
* 创建权限
* @param {Object} data - 权限数据
* @returns {Promise<Object>}
*/
export function createPermission(data) {
return request({
url: '/admin/auth/edit',
method: 'POST',
data,
})
}
/**
* 更新权限
* @param {Object} data - 权限数据
* @returns {Promise<Object>}
*/
export function updatePermission(data) {
return request({
url: '/admin/auth/edit',
method: 'POST',
data,
})
}
/**
* 删除权限
* @param {number} id - 权限ID
* @returns {Promise<Object>}
*/
export function deletePermission(id) {
return request({
url: '/admin/auth/delete',
method: 'POST',
data: { id },
})
}
/**
* 更新权限节点(同步后端权限到数据库)
* @returns {Promise<Object>}
*/
export function syncPermissions() {
return request({
url: '/admin/auth/updateAuth',
method: 'POST',
})
}
/**
* 获取用户权限列表
* @returns {Promise<Object>}
*/
export function getUserPermissions() {
return request({
url: '/admin/auth/userAuth',
method: 'GET',
})
}

View File

@@ -0,0 +1,130 @@
import { request } from '../utils/request'
/**
* 获取用户列表
* @param {Object} params - 查询参数
* @param {number} [params.page] - 页码
* @param {number} [params.limit] - 每页数量
* @param {string} [params.username] - 用户名
* @param {string} [params.nickname] - 昵称
* @param {string} [params.phone] - 手机号
* @param {number} [params.status] - 状态
* @returns {Promise<Object>}
*/
export function getUserList(params = {}) {
return request({
url: '/admin/admin/index',
method: 'GET',
data: params,
})
}
/**
* 获取用户详情
* @param {number} id - 用户ID
* @returns {Promise<Object>}
*/
export function getUserDetail(id) {
return request({
url: '/admin/admin/edit',
method: 'GET',
data: { id },
})
}
/**
* 创建用户
* @param {Object} data - 用户数据
* @param {string} data.username - 用户名
* @param {string} data.password - 密码
* @param {string} [data.nickname] - 昵称
* @param {string} [data.phone] - 手机号
* @param {string} [data.email] - 邮箱
* @param {string} [data.head_img] - 头像
* @param {string} [data.remark] - 备注
* @param {number} [data.status] - 状态
* @returns {Promise<Object>}
*/
export function createUser(data) {
return request({
url: '/admin/admin/edit',
method: 'POST',
data,
})
}
/**
* 更新用户
* @param {Object} data - 用户数据
* @param {number} data.id - 用户ID
* @param {string} [data.username] - 用户名
* @param {string} [data.password] - 密码
* @param {string} [data.nickname] - 昵称
* @param {string} [data.phone] - 手机号
* @param {string} [data.email] - 邮箱
* @param {string} [data.head_img] - 头像
* @param {string} [data.remark] - 备注
* @param {number} [data.status] - 状态
* @returns {Promise<Object>}
*/
export function updateUser(data) {
return request({
url: '/admin/admin/edit',
method: 'POST',
data,
})
}
/**
* 删除用户
* @param {number} id - 用户ID
* @returns {Promise<Object>}
*/
export function deleteUser(id) {
return request({
url: '/admin/admin/delete',
method: 'POST',
data: { id },
})
}
/**
* 批量删除用户
* @param {Array<number>} ids - 用户ID列表
* @returns {Promise<Object>}
*/
export function batchDeleteUsers(ids) {
return request({
url: '/admin/admin/delete',
method: 'POST',
data: { ids },
})
}
/**
* 修改用户状态
* @param {number} id - 用户ID
* @param {number} status - 状态
* @returns {Promise<Object>}
*/
export function updateUserStatus(id, status) {
return request({
url: '/admin/admin/edit',
method: 'POST',
data: { id, status },
})
}
/**
* 重置用户密码
* @param {number} id - 用户ID
* @param {string} password - 新密码
* @returns {Promise<Object>}
*/
export function resetUserPassword(id, password) {
return request({
url: '/admin/admin/resetPassword',
method: 'POST',
data: { id, password },
})
}

View File

@@ -25,6 +25,36 @@
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/permission/index",
"style": {
"navigationBarTitleText": "权限管理"
}
},
{
"path": "pages/user/list",
"style": {
"navigationBarTitleText": "用户列表"
}
},
{
"path": "pages/user/detail",
"style": {
"navigationBarTitleText": "用户详情"
}
},
{
"path": "pages/user/edit",
"style": {
"navigationBarTitleText": "编辑用户"
}
},
{
"path": "pages/user/delete",
"style": {
"navigationBarTitleText": "删除用户"
}
}
],
"globalStyle": {

View File

@@ -0,0 +1,247 @@
<template>
<view class="page">
<view class="card">
<up-form :model="form" labelPosition="top">
<up-form-item label="权限名称">
<up-input v-model="form.title" placeholder="请输入权限名称" />
</up-form-item>
<up-form-item label="权限标识">
<up-input v-model="form.name" placeholder="请输入权限标识" />
</up-form-item>
<up-form-item label="权限类型">
<up-picker :show="showTypePicker" :columns="typeOptions" @confirm="onTypeConfirm" @cancel="showTypePicker = false">
<up-input v-model="form.typeLabel" placeholder="请选择权限类型" disabled />
</up-picker>
</up-form-item>
</up-form>
</view>
<view class="btn-row">
<up-button type="primary" text="查询" @click="handleSearch" />
<up-button type="default" text="重置" @click="handleReset" />
</view>
<view class="card">
<view class="card-header">
<text class="card-title">权限列表</text>
<view class="card-actions">
<up-button type="primary" size="mini" text="刷新" @click="fetchPermissions" />
</view>
</view>
<up-empty v-if="!permissions.length && !loading" text="暂无权限数据" mode="data" />
<view v-for="item in permissions" :key="item.id" class="permission-item">
<view class="item-main">
<view class="item-title">{{ item.title }}</view>
<view class="item-meta">
<text class="tag">{{ item.type }}</text>
<text class="status" :class="{ active: item.status === 1 }">
{{ item.status === 1 ? '启用' : '禁用' }}
</text>
</view>
</view>
<view class="item-sub">
<text class="item-name">{{ item.name }}</text>
</view>
<view class="item-actions">
<up-button type="primary" size="mini" text="详情" @click="viewDetail(item.id)" />
<up-button type="warning" size="mini" text="编辑" @click="editPermission(item.id)" />
<up-button type="error" size="mini" text="删除" @click="deletePermission(item.id)" />
</view>
</view>
</view>
<up-loading-page :loading="loading" loading-text="加载中..." />
</view>
</template>
<script>
import { getPermissionList, deletePermission as deletePermissionApi } from '../../api/permission'
export default {
data() {
return {
loading: false,
showTypePicker: false,
form: {
title: '',
name: '',
type: '',
typeLabel: '',
},
typeOptions: [
[
{ label: '全部', value: '' },
{ label: '控制器', value: 'controller' },
{ label: '方法', value: 'action' },
],
],
permissions: [],
}
},
onLoad() {
this.fetchPermissions()
},
methods: {
onTypeConfirm(e) {
const selected = e.value[0]
this.form.type = selected.value
this.form.typeLabel = selected.label
this.showTypePicker = false
},
handleSearch() {
this.fetchPermissions()
},
handleReset() {
this.form = {
title: '',
name: '',
type: '',
typeLabel: '',
}
this.fetchPermissions()
},
async fetchPermissions() {
this.loading = true
try {
const res = await getPermissionList({
title: this.form.title,
name: this.form.name,
type: this.form.type,
})
this.permissions = res.data.list || res.data || []
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
viewDetail(id) {
uni.navigateTo({
url: `/pages/permission/detail?id=${id}`,
})
},
editPermission(id) {
uni.navigateTo({
url: `/pages/permission/edit?id=${id}`,
})
},
async deletePermission(id) {
uni.showModal({
title: '确认删除',
content: '确定要删除该权限吗?',
success: async (res) => {
if (res.confirm) {
try {
await deletePermissionApi(id)
uni.showToast({ title: '删除成功', icon: 'none' })
this.fetchPermissions()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
},
})
},
},
}
</script>
<style>
.page {
padding: 24rpx;
}
.card {
padding: 20rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
}
.card-actions {
display: flex;
gap: 12rpx;
}
.btn-row {
display: flex;
gap: 12rpx;
margin-bottom: 24rpx;
}
.permission-item {
padding: 20rpx;
border: 1rpx solid #f0f0f0;
border-radius: 8rpx;
margin-bottom: 16rpx;
}
.item-main {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
}
.item-meta {
display: flex;
gap: 12rpx;
align-items: center;
}
.tag {
padding: 4rpx 12rpx;
background: #f0f0f0;
border-radius: 4rpx;
font-size: 22rpx;
color: #666;
}
.status {
padding: 4rpx 12rpx;
background: #e0e0e0;
border-radius: 4rpx;
font-size: 22rpx;
color: #999;
}
.status.active {
background: #e6f7e6;
color: #52c41a;
}
.item-sub {
margin-bottom: 12rpx;
}
.item-name {
font-size: 24rpx;
color: #666;
}
.item-actions {
display: flex;
gap: 12rpx;
}
</style>

View File

@@ -0,0 +1,233 @@
<template>
<view class="page">
<up-loading-page :loading="loading" loading-text="加载中..." />
<view v-if="user" class="page-content">
<view class="card warning-card">
<view class="warning-icon"></view>
<view class="warning-title">确认删除用户</view>
<view class="warning-desc">删除后用户数据将无法恢复请谨慎操作</view>
</view>
<view class="card">
<view class="card-title">用户信息</view>
<view class="info-list">
<view class="info-item">
<text class="label">用户名</text>
<text class="value">{{ user.username }}</text>
</view>
<view class="info-item">
<text class="label">昵称</text>
<text class="value">{{ user.nickname || '-' }}</text>
</view>
<view class="info-item">
<text class="label">手机号</text>
<text class="value">{{ user.phone || '-' }}</text>
</view>
<view class="info-item">
<text class="label">状态</text>
<text class="value status" :class="{ active: user.status === 1 }">
{{ user.status === 1 ? '正常' : '禁用' }}
</text>
</view>
</view>
</view>
<view v-if="user.remark" class="card">
<view class="card-title">备注信息</view>
<view class="remark-content">{{ user.remark }}</view>
</view>
<view class="confirm-section">
<up-checkbox
v-model="confirmed"
shape="circle"
label="我确认要删除该用户,且已知晓删除后无法恢复"
/>
</view>
<view class="btn-group">
<up-button type="default" text="取消" @click="handleCancel" />
<up-button
type="error"
:disabled="!confirmed || deleting"
:loading="deleting"
text="确认删除"
@click="handleDelete"
/>
</view>
</view>
</view>
</template>
<script>
import { getUserDetail, deleteUser as deleteUserApi } from '../../api/user'
export default {
data() {
return {
loading: false,
deleting: false,
userId: null,
user: null,
confirmed: false,
}
},
onLoad(options) {
if (options.id) {
this.userId = parseInt(options.id)
this.fetchUserDetail()
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
methods: {
async fetchUserDetail() {
this.loading = true
try {
const res = await getUserDetail(this.userId)
this.user = res.data.row || res.data || null
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} finally {
this.loading = false
}
},
async handleDelete() {
if (!this.confirmed) {
uni.showToast({ title: '请先确认删除操作', icon: 'none' })
return
}
this.deleting = true
try {
await deleteUserApi(this.userId)
uni.showToast({ title: '删除成功', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
} finally {
this.deleting = false
}
},
handleCancel() {
uni.navigateBack()
},
},
}
</script>
<style>
.page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx;
}
.page-content {
padding-bottom: 40rpx;
}
.card {
padding: 24rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.warning-card {
background: linear-gradient(135deg, #fff7e6 0%, #fff1d6 100%);
text-align: center;
}
.warning-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.warning-title {
font-size: 32rpx;
font-weight: 600;
color: #d46b08;
margin-bottom: 12rpx;
}
.warning-desc {
font-size: 26rpx;
color: #d48806;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
margin-bottom: 20rpx;
}
.info-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.info-item:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #666;
}
.value {
font-size: 26rpx;
color: #222;
font-weight: 500;
}
.value.status {
padding: 6rpx 16rpx;
background: #e0e0e0;
border-radius: 16rpx;
font-size: 22rpx;
color: #999;
font-weight: 400;
}
.value.status.active {
background: #e6f7e6;
color: #52c41a;
}
.remark-content {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.confirm-section {
padding: 24rpx;
margin-bottom: 24rpx;
}
.btn-group {
display: flex;
gap: 16rpx;
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<view class="page">
<up-loading-page :loading="loading" loading-text="加载中..." />
<view v-if="user" class="page-content">
<view class="card">
<view class="avatar-section">
<image v-if="user.head_img" :src="user.head_img" class="avatar" mode="aspectFill" />
<view v-else class="avatar-placeholder">{{ user.username?.charAt(0).toUpperCase() }}</view>
<view class="user-info">
<view class="username">{{ user.nickname || user.username }}</view>
<view class="account">{{ user.username }}</view>
</view>
</view>
</view>
<view class="card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-item">
<text class="label">用户 ID</text>
<text class="value">{{ user.id }}</text>
</view>
<view class="info-item">
<text class="label">用户名</text>
<text class="value">{{ user.username }}</text>
</view>
<view class="info-item">
<text class="label">昵称</text>
<text class="value">{{ user.nickname || '-' }}</text>
</view>
<view class="info-item">
<text class="label">手机号</text>
<text class="value">{{ user.phone || '-' }}</text>
</view>
<view class="info-item">
<text class="label">邮箱</text>
<text class="value">{{ user.email || '-' }}</text>
</view>
<view class="info-item">
<text class="label">状态</text>
<text class="value status" :class="{ active: user.status === 1 }">
{{ user.status === 1 ? '正常' : '禁用' }}
</text>
</view>
</view>
</view>
<view v-if="user.remark" class="card">
<view class="card-title">备注信息</view>
<view class="remark-content">{{ user.remark }}</view>
</view>
<view class="card">
<view class="card-title">时间信息</view>
<view class="info-list">
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{ user.create_time || '-' }}</text>
</view>
<view class="info-item">
<text class="label">更新时间</text>
<text class="value">{{ user.update_time || '-' }}</text>
</view>
</view>
</view>
<view class="btn-group">
<up-button type="primary" text="编辑" @click="goEdit" />
<up-button type="error" text="删除" @click="handleDelete" />
</view>
</view>
</view>
</template>
<script>
import { getUserDetail, deleteUser as deleteUserApi } from '../../api/user'
export default {
data() {
return {
loading: false,
userId: null,
user: null,
}
},
onLoad(options) {
if (options.id) {
this.userId = parseInt(options.id)
this.fetchUserDetail()
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
methods: {
async fetchUserDetail() {
this.loading = true
try {
const res = await getUserDetail(this.userId)
this.user = res.data.row || res.data || null
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
goEdit() {
uni.navigateTo({
url: `/pages/user/edit?id=${this.userId}`,
})
},
handleDelete() {
uni.showModal({
title: '确认删除',
content: '确定要删除该用户吗?',
success: async (res) => {
if (res.confirm) {
try {
await deleteUserApi(this.userId)
uni.showToast({ title: '删除成功', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
},
})
},
},
}
</script>
<style>
.page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx;
}
.page-content {
padding-bottom: 40rpx;
}
.card {
padding: 24rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.avatar-section {
display: flex;
align-items: center;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
.avatar-placeholder {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border-radius: 50%;
font-size: 48rpx;
font-weight: 600;
}
.user-info {
flex: 1;
margin-left: 24rpx;
}
.username {
font-size: 32rpx;
font-weight: 600;
color: #222;
margin-bottom: 8rpx;
}
.account {
font-size: 26rpx;
color: #999;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
margin-bottom: 20rpx;
}
.info-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.info-item:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #666;
}
.value {
font-size: 26rpx;
color: #222;
font-weight: 500;
}
.value.status {
padding: 6rpx 16rpx;
background: #e0e0e0;
border-radius: 16rpx;
font-size: 22rpx;
color: #999;
font-weight: 400;
}
.value.status.active {
background: #e6f7e6;
color: #52c41a;
}
.remark-content {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.btn-group {
display: flex;
gap: 16rpx;
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<view class="page">
<up-loading-page :loading="loading" loading-text="加载中..." />
<view class="card">
<up-form :model="form" labelPosition="top">
<up-form-item
label="用户名"
:required="!isEdit"
:rules="[{ required: !isEdit, message: '请输入用户名', trigger: 'blur' }]"
>
<up-input
v-model="form.username"
placeholder="请输入用户名"
:disabled="isEdit"
/>
</up-form-item>
<up-form-item
v-if="!isEdit"
label="密码"
required
:rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
>
<up-input
v-model="form.password"
type="password"
placeholder="请输入密码"
/>
</up-form-item>
<up-form-item
v-if="!isEdit"
label="确认密码"
required
:rules="[{ required: true, message: '请输入确认密码', trigger: 'blur' }]"
>
<up-input
v-model="form.confirmPassword"
type="password"
placeholder="请输入确认密码"
/>
</up-form-item>
<up-form-item
v-if="isEdit"
label="新密码"
>
<up-input
v-model="form.password"
type="password"
placeholder="留空则不修改密码"
/>
</up-form-item>
<up-form-item
v-if="isEdit"
label="确认新密码"
>
<up-input
v-model="form.confirmPassword"
type="password"
placeholder="留空则不修改密码"
/>
</up-form-item>
<up-form-item label="昵称">
<up-input
v-model="form.nickname"
placeholder="请输入昵称"
/>
</up-form-item>
<up-form-item label="手机号">
<up-input
v-model="form.phone"
type="number"
placeholder="请输入手机号"
/>
</up-form-item>
<up-form-item label="邮箱">
<up-input
v-model="form.email"
placeholder="请输入邮箱"
/>
</up-form-item>
<up-form-item label="头像">
<up-input
v-model="form.head_img"
placeholder="请输入头像 URL"
/>
</up-form-item>
<up-form-item label="状态">
<up-radio-group v-model="form.status">
<up-radio :name="1" label="正常" />
<up-radio :name="0" label="禁用" />
</up-radio-group>
</up-form-item>
<up-form-item label="备注">
<up-textarea
v-model="form.remark"
placeholder="请输入备注信息"
:maxlength="200"
:autoHeight="true"
/>
</up-form-item>
</up-form>
</view>
<view class="btn-group">
<up-button type="default" text="取消" @click="handleCancel" />
<up-button type="primary" :loading="submitting" text="保存" @click="handleSubmit" />
</view>
</view>
</template>
<script>
import { getUserDetail, createUser, updateUser } from '../../api/user'
export default {
data() {
return {
loading: false,
submitting: false,
userId: null,
isEdit: false,
form: {
username: '',
password: '',
confirmPassword: '',
nickname: '',
phone: '',
email: '',
head_img: '',
status: 1,
remark: '',
},
}
},
onLoad(options) {
if (options.id) {
this.userId = parseInt(options.id)
this.isEdit = true
this.fetchUserDetail()
} else {
this.isEdit = false
}
},
methods: {
async fetchUserDetail() {
this.loading = true
try {
const res = await getUserDetail(this.userId)
const user = res.data.row || res.data || {}
this.form = {
username: user.username || '',
password: '',
confirmPassword: '',
nickname: user.nickname || '',
phone: user.phone || '',
email: user.email || '',
head_img: user.head_img || '',
status: user.status !== undefined ? user.status : 1,
remark: user.remark || '',
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} finally {
this.loading = false
}
},
validateForm() {
if (!this.form.username) {
uni.showToast({ title: '请输入用户名', icon: 'none' })
return false
}
if (!this.isEdit && !this.form.password) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return false
}
if (this.form.password || this.form.confirmPassword) {
if (this.form.password !== this.form.confirmPassword) {
uni.showToast({ title: '两次密码不一致', icon: 'none' })
return false
}
}
return true
},
async handleSubmit() {
if (!this.validateForm()) {
return
}
this.submitting = true
try {
const data = {
username: this.form.username,
nickname: this.form.nickname,
phone: this.form.phone,
email: this.form.email,
head_img: this.form.head_img,
status: this.form.status,
remark: this.form.remark,
}
if (this.form.password) {
data.password = this.form.password
}
if (this.isEdit) {
data.id = this.userId
await updateUser(data)
uni.showToast({ title: '更新成功', icon: 'none' })
} else {
await createUser(data)
uni.showToast({ title: '创建成功', icon: 'none' })
}
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
} finally {
this.submitting = false
}
},
handleCancel() {
uni.navigateBack()
},
},
}
</script>
<style>
.page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx;
}
.card {
padding: 24rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.btn-group {
display: flex;
gap: 16rpx;
}
</style>

View File

@@ -0,0 +1,363 @@
<template>
<view class="page">
<view class="card">
<up-form :model="form" labelPosition="top">
<up-form-item label="用户名">
<up-input v-model="form.username" placeholder="请输入用户名" />
</up-form-item>
<up-form-item label="昵称">
<up-input v-model="form.nickname" placeholder="请输入昵称" />
</up-form-item>
<up-form-item label="手机号">
<up-input v-model="form.phone" placeholder="请输入手机号" />
</up-form-item>
<up-form-item label="状态">
<up-picker :show="showStatusPicker" :columns="statusOptions" @confirm="onStatusConfirm" @cancel="showStatusPicker = false">
<up-input v-model="form.statusLabel" placeholder="请选择状态" disabled />
</up-picker>
</up-form-item>
</up-form>
</view>
<view class="btn-row">
<up-button type="primary" text="查询" @click="handleSearch" />
<up-button type="default" text="重置" @click="handleReset" />
</view>
<view class="card">
<view class="card-header">
<text class="card-title">用户列表</text>
<view class="card-actions">
<up-button type="primary" size="mini" text="新增" @click="goCreate" />
<up-button type="default" size="mini" text="刷新" @click="fetchUsers" />
</view>
</view>
<up-empty v-if="!users.length && !loading" text="暂无用户数据" mode="data" />
<view v-for="item in users" :key="item.id" class="user-item">
<view class="item-main">
<view class="item-avatar">
<image v-if="item.head_img" :src="item.head_img" class="avatar" mode="aspectFill" />
<view v-else class="avatar-placeholder">{{ item.username?.charAt(0).toUpperCase() }}</view>
</view>
<view class="item-info">
<view class="item-title">{{ item.nickname || item.username }}</view>
<view class="item-username">{{ item.username }}</view>
</view>
<view class="item-status">
<text class="status" :class="{ active: item.status === 1 }">
{{ item.status === 1 ? '正常' : '禁用' }}
</text>
</view>
</view>
<view class="item-meta">
<text v-if="item.phone" class="meta-item">📱 {{ item.phone }}</text>
<text v-if="item.email" class="meta-item">📧 {{ item.email }}</text>
</view>
<view v-if="item.remark" class="item-remark">
<text class="remark-label">备注</text>
<text class="remark-text">{{ item.remark }}</text>
</view>
<view class="item-actions">
<up-button type="primary" size="mini" text="详情" @click="viewDetail(item.id)" />
<up-button type="warning" size="mini" text="编辑" @click="editUser(item.id)" />
<up-button type="error" size="mini" text="删除" @click="deleteUser(item.id)" />
</view>
</view>
<view v-if="pagination.total > 0" class="pagination">
<up-button
:disabled="pagination.page <= 1"
size="mini"
text="上一页"
@click="prevPage"
/>
<text class="page-info">{{ pagination.page }} / {{ Math.ceil(pagination.total / pagination.limit) }}</text>
<up-button
:disabled="pagination.page >= Math.ceil(pagination.total / pagination.limit)"
size="mini"
text="下一页"
@click="nextPage"
/>
</view>
</view>
<up-loading-page :loading="loading" loading-text="加载中..." />
</view>
</template>
<script>
import { getUserList, deleteUser as deleteUserApi } from '../../api/user'
export default {
data() {
return {
loading: false,
showStatusPicker: false,
form: {
username: '',
nickname: '',
phone: '',
status: '',
statusLabel: '',
},
statusOptions: [
[
{ label: '全部', value: '' },
{ label: '正常', value: 1 },
{ label: '禁用', value: 0 },
],
],
users: [],
pagination: {
page: 1,
limit: 10,
total: 0,
},
}
},
onLoad() {
this.fetchUsers()
},
methods: {
onStatusConfirm(e) {
const selected = e.value[0]
this.form.status = selected.value
this.form.statusLabel = selected.label
this.showStatusPicker = false
},
handleSearch() {
this.pagination.page = 1
this.fetchUsers()
},
handleReset() {
this.form = {
username: '',
nickname: '',
phone: '',
status: '',
statusLabel: '',
}
this.pagination.page = 1
this.fetchUsers()
},
async fetchUsers() {
this.loading = true
try {
const res = await getUserList({
page: this.pagination.page,
limit: this.pagination.limit,
username: this.form.username,
nickname: this.form.nickname,
phone: this.form.phone,
status: this.form.status,
})
this.users = res.data.list || res.data || []
this.pagination.total = res.data.total || 0
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
prevPage() {
if (this.pagination.page > 1) {
this.pagination.page--
this.fetchUsers()
}
},
nextPage() {
const maxPage = Math.ceil(this.pagination.total / this.pagination.limit)
if (this.pagination.page < maxPage) {
this.pagination.page++
this.fetchUsers()
}
},
goCreate() {
uni.navigateTo({
url: '/pages/user/edit',
})
},
viewDetail(id) {
uni.navigateTo({
url: `/pages/user/detail?id=${id}`,
})
},
editUser(id) {
uni.navigateTo({
url: `/pages/user/edit?id=${id}`,
})
},
async deleteUser(id) {
uni.showModal({
title: '确认删除',
content: '确定要删除该用户吗?',
success: async (res) => {
if (res.confirm) {
try {
await deleteUserApi(id)
uni.showToast({ title: '删除成功', icon: 'none' })
this.fetchUsers()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
},
})
},
},
}
</script>
<style>
.page {
padding: 24rpx;
}
.card {
padding: 20rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
}
.card-actions {
display: flex;
gap: 12rpx;
}
.btn-row {
display: flex;
gap: 12rpx;
margin-bottom: 24rpx;
}
.user-item {
padding: 20rpx;
border: 1rpx solid #f0f0f0;
border-radius: 8rpx;
margin-bottom: 16rpx;
}
.item-main {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.item-avatar {
width: 80rpx;
height: 80rpx;
margin-right: 16rpx;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border-radius: 50%;
font-size: 32rpx;
font-weight: 600;
}
.item-info {
flex: 1;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
margin-bottom: 4rpx;
}
.item-username {
font-size: 24rpx;
color: #666;
}
.item-status {
display: flex;
align-items: center;
}
.status {
padding: 6rpx 16rpx;
background: #e0e0e0;
border-radius: 16rpx;
font-size: 22rpx;
color: #999;
}
.status.active {
background: #e6f7e6;
color: #52c41a;
}
.item-meta {
display: flex;
gap: 20rpx;
margin-bottom: 12rpx;
}
.meta-item {
font-size: 24rpx;
color: #666;
}
.item-remark {
margin-bottom: 12rpx;
font-size: 24rpx;
color: #666;
}
.remark-label {
color: #999;
}
.remark-text {
color: #666;
}
.item-actions {
display: flex;
gap: 12rpx;
}
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
margin-top: 24rpx;
}
.page-info {
font-size: 24rpx;
color: #666;
}
</style>

View File

@@ -0,0 +1,131 @@
/**
* API 响应基础类型
*/
export interface ApiResponse<T = any> {
code: number
msg: string
data: T
}
/**
* 分页响应类型
*/
export interface PageResponse<T = any> {
total: number
page: number
limit: number
list: T[]
}
/**
* 分页请求参数类型
*/
export interface PageParams {
page?: number
limit?: number
[key: string]: any
}
/**
* 用户信息类型
*/
export interface User {
id: number
username: string
nickname?: string
phone?: string
email?: string
head_img?: string
remark?: string
status?: number
create_time?: string
update_time?: string
}
/**
* 登录请求参数类型
*/
export interface LoginParams {
username: string
password: string
captcha?: string
keep_login?: number
}
/**
* 登录响应类型
*/
export interface LoginResponse {
token: string
admin?: User
}
/**
* 权限节点类型
*/
export interface Permission {
id: number
pid: number
name: string
title: string
type: string
status: number
sort: number
create_time?: string
update_time?: string
}
/**
* 菜单树节点类型
*/
export interface MenuNode extends Permission {
children?: MenuNode[]
}
/**
* 用户列表查询参数类型
*/
export interface UserListParams extends PageParams {
username?: string
nickname?: string
phone?: string
status?: number
}
/**
* 用户表单数据类型
*/
export interface UserFormData {
id?: number
username: string
password?: string
nickname?: string
phone?: string
email?: string
head_img?: string
remark?: string
status?: number
}
/**
* 权限列表查询参数类型
*/
export interface PermissionListParams extends PageParams {
name?: string
title?: string
type?: string
status?: number
}
/**
* 权限表单数据类型
*/
export interface PermissionFormData {
id?: number
pid: number
name: string
title: string
type: string
status?: number
sort?: number
}

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"types": ["@dcloudio/types"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["dist", "node_modules"]
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.js"]
}