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