first commit
This commit is contained in:
commit
765cb9c2e0
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
node_modules
|
||||
9086
package-lock.json
generated
Normal file
9086
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "dm-admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.7.9",
|
||||
"nprogress": "^0.2.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.5.0",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^7.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port 8080",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
}
|
||||
}
|
||||
13
public/index.html
Normal file
13
public/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>DM Admin</title>
|
||||
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
9
src/App.vue
Normal file
9
src/App.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
9
src/api/community.js
Normal file
9
src/api/community.js
Normal file
@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getCommunityList(params) {
|
||||
return request({
|
||||
url: '/api/community',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
17
src/api/user.js
Normal file
17
src/api/user.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/api/user/password-login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserList(params) {
|
||||
return request({
|
||||
url: '/api/user/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
158
src/layouts/BasicLayout.vue
Normal file
158
src/layouts/BasicLayout.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<a-layout class="layout">
|
||||
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
|
||||
<div class="logo" />
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
v-model:openKeys="openKeys"
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
>
|
||||
<a-sub-menu key="user">
|
||||
<template #icon>
|
||||
<user-outlined />
|
||||
</template>
|
||||
<template #title>用户管理</template>
|
||||
<a-menu-item key="user-list">
|
||||
<router-link to="/user/list">用户列表</router-link>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<a-sub-menu key="community">
|
||||
<template #icon>
|
||||
<home-outlined />
|
||||
</template>
|
||||
<template #title>小区管理</template>
|
||||
<a-menu-item key="community-list">
|
||||
<router-link to="/community/list">小区列表</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="building-list">
|
||||
<router-link to="/community/building">楼栋列表</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="station-list">
|
||||
<router-link to="/community/station">驿站列表</router-link>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout>
|
||||
<a-layout-header style="background: #fff; padding: 0">
|
||||
<div class="header-right">
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
{{ userInfo.username }}
|
||||
<down-outlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<menu-unfold-outlined
|
||||
v-if="collapsed"
|
||||
class="trigger"
|
||||
@click="() => (collapsed = !collapsed)"
|
||||
/>
|
||||
<menu-fold-outlined
|
||||
v-else
|
||||
class="trigger"
|
||||
@click="() => (collapsed = !collapsed)"
|
||||
/>
|
||||
</a-layout-header>
|
||||
<a-layout-content
|
||||
:style="{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }"
|
||||
>
|
||||
<router-view></router-view>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, defineComponent } from 'vue'
|
||||
import {
|
||||
UserOutlined,
|
||||
HomeOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
DownOutlined,
|
||||
LogoutOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicLayout',
|
||||
components: {
|
||||
UserOutlined,
|
||||
HomeOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
DownOutlined,
|
||||
LogoutOutlined,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const collapsed = ref(false)
|
||||
const selectedKeys = ref(['user-list'])
|
||||
const openKeys = ref(['user', 'community'])
|
||||
|
||||
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}'))
|
||||
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === 'userInfo') {
|
||||
userInfo.value = JSON.parse(e.newValue || '{}')
|
||||
}
|
||||
})
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userInfo')
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
selectedKeys,
|
||||
openKeys,
|
||||
userInfo,
|
||||
handleLogout
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
.logo {
|
||||
height: 32px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
margin: 16px;
|
||||
}
|
||||
.header-right {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
line-height: 64px;
|
||||
}
|
||||
.ant-dropdown-link {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
41
src/main.js
Normal file
41
src/main.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import {
|
||||
Button,
|
||||
Layout,
|
||||
Menu,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
Table,
|
||||
Dropdown,
|
||||
SubMenu,
|
||||
Space,
|
||||
Popconfirm,
|
||||
Divider,
|
||||
Tag,
|
||||
Modal
|
||||
} from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
// 注册需要的组件
|
||||
app.use(Button)
|
||||
app.use(Layout)
|
||||
app.use(Menu)
|
||||
app.use(Card)
|
||||
app.use(Form)
|
||||
app.use(Input)
|
||||
app.use(Table)
|
||||
app.use(Dropdown)
|
||||
app.use(SubMenu)
|
||||
app.use(Space)
|
||||
app.use(Popconfirm)
|
||||
app.use(Divider)
|
||||
app.use(Tag)
|
||||
app.use(Modal)
|
||||
|
||||
app.mount('#app')
|
||||
69
src/router/index.js
Normal file
69
src/router/index.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import BasicLayout from '../layouts/BasicLayout.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/user/list'
|
||||
},
|
||||
{
|
||||
path: '/user/list',
|
||||
name: 'userList',
|
||||
component: () => import('../views/user/UserList.vue'),
|
||||
meta: { title: '用户列表' }
|
||||
},
|
||||
{
|
||||
path: '/community/list',
|
||||
name: 'communityList',
|
||||
component: () => import('../views/community/CommunityList.vue'),
|
||||
meta: { title: '小区列表' }
|
||||
},
|
||||
{
|
||||
path: '/community/building',
|
||||
name: 'buildingList',
|
||||
component: () => import('../views/community/BuildingList.vue'),
|
||||
meta: { title: '楼栋列表' }
|
||||
},
|
||||
{
|
||||
path: '/community/station',
|
||||
name: 'stationList',
|
||||
component: () => import('../views/community/StationList.vue'),
|
||||
meta: { title: '驿站列表' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('../views/login/Login.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
if (to.path === '/login') {
|
||||
if (token) {
|
||||
next('/')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
if (token) {
|
||||
next()
|
||||
} else {
|
||||
next('/login')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
20
src/utils/amap.js
Normal file
20
src/utils/amap.js
Normal file
@ -0,0 +1,20 @@
|
||||
export function initAMap() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.AMap) {
|
||||
resolve(window.AMap);
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = `https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f`;
|
||||
|
||||
script.onerror = reject;
|
||||
script.onload = () => {
|
||||
resolve(window.AMap);
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
34
src/utils/request.js
Normal file
34
src/utils/request.js
Normal file
@ -0,0 +1,34 @@
|
||||
import axios from 'axios'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: 'http://127.0.0.1:8000',
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
config => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
message.error(error.response?.data?.message || '请求失败')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default request
|
||||
45
src/views/community/BuildingList.vue
Normal file
45
src/views/community/BuildingList.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="building-list">
|
||||
<h1>楼栋列表</h1>
|
||||
<a-table :columns="columns" :data-source="data">
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<span>楼栋名称</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
title: '楼栋名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '所属小区',
|
||||
dataIndex: 'community',
|
||||
key: 'community',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
]
|
||||
|
||||
const data = ref([])
|
||||
|
||||
return {
|
||||
data,
|
||||
columns
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
239
src/views/community/CommunityList.vue
Normal file
239
src/views/community/CommunityList.vue
Normal file
@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="community-list">
|
||||
<div class="table-header">
|
||||
<h1>小区列表</h1>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'location'">
|
||||
<a @click="showMap(record)">查看位置</a>
|
||||
</template>
|
||||
<template v-if="column.key === 'create_time'">
|
||||
{{ formatDateTime(record.create_time) }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="mapVisible"
|
||||
title="小区位置"
|
||||
:footer="null"
|
||||
width="800px"
|
||||
@cancel="closeMap"
|
||||
>
|
||||
<div id="map-container" style="height: 500px;"></div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, nextTick } from 'vue'
|
||||
import { message, Tag } from 'ant-design-vue'
|
||||
import { getCommunityList } from '@/api/community'
|
||||
import { initAMap } from '@/utils/amap'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
[Tag.name]: Tag
|
||||
},
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const mapVisible = ref(false)
|
||||
const currentMap = ref(null)
|
||||
const currentMarker = ref(null)
|
||||
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
// 获取状态显示文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'OPENING': '运营中',
|
||||
'UNOPEN': '未启用'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态标签颜色
|
||||
const getStatusColor = (status) => {
|
||||
const colorMap = {
|
||||
'OPENING': 'green',
|
||||
'UNOPEN': 'orange'
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return ''
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '小区名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '位置',
|
||||
key: 'location',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
}
|
||||
]
|
||||
|
||||
// 获取小区列表数据
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
skip: (pagination.value.current - 1) * pagination.value.pageSize,
|
||||
limit: pagination.value.pageSize
|
||||
}
|
||||
|
||||
const res = await getCommunityList(params)
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.data.items
|
||||
pagination.value.total = res.data.total
|
||||
} else {
|
||||
message.error(res.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取小区列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 显示地图
|
||||
const showMap = async (record) => {
|
||||
mapVisible.value = true
|
||||
|
||||
// 等待 DOM 更新
|
||||
await nextTick()
|
||||
|
||||
try {
|
||||
const AMap = await initAMap()
|
||||
|
||||
// 创建地图实例
|
||||
if (!currentMap.value) {
|
||||
currentMap.value = new AMap.Map('map-container', {
|
||||
zoom: 15,
|
||||
viewMode: '3D'
|
||||
})
|
||||
}
|
||||
|
||||
// 设置中心点
|
||||
const position = [record.longitude, record.latitude]
|
||||
currentMap.value.setCenter(position)
|
||||
|
||||
// 清除旧标记
|
||||
if (currentMarker.value) {
|
||||
currentMap.value.remove(currentMarker.value)
|
||||
}
|
||||
|
||||
// 添加新标记
|
||||
currentMarker.value = new AMap.Marker({
|
||||
position,
|
||||
title: record.name
|
||||
})
|
||||
currentMap.value.add(currentMarker.value)
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载地图失败:', error)
|
||||
message.error('加载地图失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭地图
|
||||
const closeMap = () => {
|
||||
mapVisible.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
columns,
|
||||
tableData,
|
||||
pagination,
|
||||
mapVisible,
|
||||
handleTableChange,
|
||||
showMap,
|
||||
closeMap,
|
||||
getStatusText,
|
||||
getStatusColor,
|
||||
formatDateTime
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-table-content) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
45
src/views/community/StationList.vue
Normal file
45
src/views/community/StationList.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="station-list">
|
||||
<h1>驿站列表</h1>
|
||||
<a-table :columns="columns" :data-source="data">
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<span>驿站名称</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
title: '驿站名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '所属小区',
|
||||
dataIndex: 'community',
|
||||
key: 'community',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
]
|
||||
|
||||
const data = ref([])
|
||||
|
||||
return {
|
||||
data,
|
||||
columns
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
107
src/views/login/Login.vue
Normal file
107
src/views/login/Login.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<a-card class="login-card" title="系统登录">
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="login"
|
||||
@finish="onFinish"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-form-item
|
||||
name="phone"
|
||||
:rules="[{ required: true, message: '请输入手机号码!' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.phone" placeholder="手机号码">
|
||||
<template #prefix>
|
||||
<mobile-outlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请输入密码!' }]"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password" placeholder="密码">
|
||||
<template #prefix>
|
||||
<lock-outlined />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
class="login-button"
|
||||
:loading="loading"
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, reactive, ref } from 'vue'
|
||||
import { MobileOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { login } from '@/api/user'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MobileOutlined,
|
||||
LockOutlined
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const formState = reactive({
|
||||
phone: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const onFinish = async values => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await login(values)
|
||||
localStorage.setItem('token', res.data.access_token)
|
||||
localStorage.setItem('userInfo', JSON.stringify(res.data.user))
|
||||
message.success('登录成功')
|
||||
router.push('/')
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
formState,
|
||||
loading,
|
||||
onFinish
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 368px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
43
src/views/settings/Settings.vue
Normal file
43
src/views/settings/Settings.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="settings">
|
||||
<h1>系统设置</h1>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-form-item
|
||||
label="系统名称"
|
||||
name="systemName"
|
||||
>
|
||||
<a-input v-model:value="formState.systemName" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="系统描述"
|
||||
name="description"
|
||||
>
|
||||
<a-textarea v-model:value="formState.description" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, reactive } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const formState = reactive({
|
||||
systemName: '',
|
||||
description: ''
|
||||
})
|
||||
|
||||
return {
|
||||
formState
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
152
src/views/user/UserList.vue
Normal file
152
src/views/user/UserList.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="user-list">
|
||||
<div class="table-header">
|
||||
<h1>用户列表</h1>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
row-key="userid"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'phone'">
|
||||
{{ formatPhone(record.phone) }}
|
||||
</template>
|
||||
<template v-if="column.key === 'points'">
|
||||
{{ record.points || 0 }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { getUserList } from '@/api/user'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userid',
|
||||
key: 'userid',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '用户编号',
|
||||
dataIndex: 'user_code',
|
||||
key: 'user_code',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
dataIndex: 'points',
|
||||
key: 'points',
|
||||
width: 80,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time',
|
||||
width: 180,
|
||||
}
|
||||
]
|
||||
|
||||
// 格式化手机号,中间4位显示*
|
||||
const formatPhone = (phone) => {
|
||||
if (!phone) return ''
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
|
||||
// 获取用户列表数据
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
skip: (pagination.value.current - 1) * pagination.value.pageSize,
|
||||
limit: pagination.value.pageSize
|
||||
}
|
||||
|
||||
const res = await getUserList(params)
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.data.items
|
||||
pagination.value.total = res.data.total
|
||||
} else {
|
||||
message.error(res.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理(分页、排序等)
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
columns,
|
||||
tableData,
|
||||
pagination,
|
||||
handleTableChange,
|
||||
formatPhone
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-table-content) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
21
vue.config.js
Normal file
21
vue.config.js
Normal file
@ -0,0 +1,21 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
|
||||
module.exports = defineConfig({
|
||||
css: {
|
||||
loaderOptions: {
|
||||
less: {
|
||||
lessOptions: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user