fisrt commit

main
luth1ng 7 months ago
parent c8486b5c4f
commit ad6dd82ef1

23
front/.gitignore vendored

@ -0,0 +1,23 @@
.DS_Store
node_modules
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock

@ -0,0 +1,70 @@
# 飞龙快递系统
## 系统概述
- 基于vue3框架开发
- 用例图如下:
![alt text](image.png)
![alt text](image-1.png)
## 代码书写说明
`以下是 Vue 项目 src 目录的详细说明`
```
src/
├── assets/ # 静态资源
│ ├── css/ # 样式文件
│ │ └── base.css # 基础样式文件
│ └── (各种图片) # 图片资源文件夹
├── components/ # 可复用的 Vue 组件
│ ├── Carousel.vue # 轮播组件
│ ├── Header.vue # 页面头部组件
│ ├── Footer.vue # 页脚底部组件
│ └── Login.vue # 登录注册组件
├── hooks/ # 自定义 Hooks
│ └── useEmitter.js # 事件发射器 Hook
├── icons/ # 图标资源
│ └── svg/ # SVG 图标文件夹
│ └── (各种图标) # 各类 SVG 图标
├── router/ # 路由配置
│ └── index.js # 主路由配置文件
├── store/ # Vuex 状态管理
│ └── index.js # Vuex Store 入口文件
├── views/ # 页面视图组件
│ ├── back/ # 后台管理页面
| | ├── index.vue # 后台管中心
| | ├── order.vue # 订单管理中心
| | ├── personal.vue # 人员管理中心
| | ├── problem.vue # 问题管理中心
| | └── backlogin.vue # 后台登录
│ ├── courier/ # 快递员页面
│ │ ├── index.vue # 快递员中心
│ │ └── order.vue # 订单管理中心
│ ├── fore/ # 前台用户页面
│ │ ├── check.vue #查件
│ │ ├── delivery.vue # 寄件
│ │ ├── service.vue # 快递服务
│ │ ├── profile.vue # 我的资料
| | ├── address.vue # 地址簿
│ │ ├── complaint.vue # 快递投诉
│ | └── feedback.vue # 我有建议
│ └── home.vue # 主页面组件
├── App.vue # 主应用组件
├── main.js # 应用入口文件
└── request/ # 请求相关文件夹
└── style.css # 请求样式文件
```
### 目录及文件描述
- `assets/:` 存放静态资源,如 CSS 文件、图片等。
- `components/:` 存放可复用的 Vue 组件,组织项目中的 UI 元素。
- `hooks/:` 自定义 Hook包含复用的逻辑。
- `icons/:` 存放 SVG 图标文件。
- `router/:` 路由配置文件,管理应用的路由和导航。
- `store/:` Vuex 状态管理相关文件,维护全局状态。
- `views/:` 页面视图组件,组织应用的主要页面内容。
- `App.vue:` Vue 应用的根组件。
- `main.js:` 应用的入口文件,初始化 Vue 实例和配置。
- `request/:` 存放与网络请求相关的文件。

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/hear.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>快递系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,24 @@
{
"name": "practical_project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.7",
"element-china-area-data": "^6.1.0",
"element-plus": "^2.8.7",
"mitt": "^3.0.1",
"pinia": "^2.2.5",
"vue": "^3.5.10",
"vue-router": "4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"vite": "^5.4.8"
}
}

@ -0,0 +1,10 @@
<template>
<router-view />
</template>
<script setup>
</script>
<style>
@import url("./assets/css/base.css");
</style>

@ -0,0 +1,103 @@
import request from '@/utils/request'
// 运单查询相关接口
export const trackingAPI = {
// 查询运单信息
getPackageInfo: (trackingNumber: string) =>
request.get('/api/tracking/${trackingNumber}'),
// 获取已寄出的包裹列表
getSentPackages: () =>
request.get('/api/tracking/sent'),
// 获取已收到的包裹列表
getReceivedPackages: () =>
request.get('/api/tracking/received')
}
// 投诉相关接口
export const complaintAPI = {
// 提交投诉
submitComplaint: (data: {
orderNumber: string,
reason: string,
description: string,
contact: string
}) => request.post('/api/complaints', data)
}
// 寄件相关接口
export const deliveryAPI = {
// 提交寄件信息
submitDelivery: (data: {
sender: {
name: string,
phone: string,
province: string[],
address: string,
company?: string
},
receiver: {
name: string,
phone: string,
province: string[],
address: string,
company?: string
},
sendType: number
}) => request.post('/api/delivery', data),
// 获取地址簿
getAddressBook: () =>
request.get('/api/address-book')
}
// 反馈相关接口
export const feedbackAPI = {
// 提交反馈
submitFeedback: (data: {
title: string,
content: string,
contact?: string
}) => request.post('/api/feedback', data)
}
// 用户资料相关接口
export const profileAPI = {
// 获取用户信息
getUserProfile: () =>
request.get('/api/user/profile'),
// 更新用户信息
updateProfile: (data: {
name: string,
email: string,
phone: string,
gender: string,
birthday: string,
address: string
}) => request.put('/api/user/profile', data),
// 更新头像
updateAvatar: (formData: FormData) =>
request.post('/api/user/avatar', formData),
// 地址管理相关
getAddresses: () =>
request.get('/api/user/addresses'),
addAddress: (data: {
name: string,
address: string,
phone: string
}) => request.post('/api/user/addresses', data),
updateAddress: (id: number, data: {
name: string,
address: string,
phone: string
}) => request.put(`/api/user/addresses/${id}`, data),
deleteAddress: (id: number) =>
request.delete(`/api/user/addresses/${id}`)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

@ -0,0 +1,44 @@
.content {
width: 100%;
height: 100%;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
/* 水平布局 居中*/
.horizontalView {
position: relative;
flex-direction: row;
display: flex;
align-items: center;
}
/* 垂直布局居中 */
.verticalView {
position: relative;
flex-direction: column;
display: flex;
align-items: center;
}
/* 居中 */
.center {
position: absolute;
top: 50%;
left: 50%;
font-size: 28px;
transform: translate(-50%, -50%);
}
.w100 {
width: 100%;
}
.h100 {
height: 100%;
}
.icon-svg {
width: 1.4rem;
height: 1.4rem;
fill: currentColor;
overflow: hidden;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,246 @@
<template>
<div class="layout-container">
<!-- 左侧导航栏 -->
<div class="sidebar">
<div class="logo">
<h2>快递系统</h2>
</div>
<el-menu
default-active="/home"
class="sidebar-menu"
router
background-color="#304156"
text-color="#fff"
>
<el-menu-item index="/home">
<el-icon><HomeFilled /></el-icon>
<span>首页</span>
</el-menu-item>
<el-menu-item index="/delivery">
<el-icon><Van /></el-icon>
<span>寄件</span>
</el-menu-item>
<el-menu-item index="/check">
<el-icon><Search /></el-icon>
<span>查件</span>
</el-menu-item>
<el-menu-item index="/service">
<el-icon><Service /></el-icon>
<span>服务查询</span>
</el-menu-item>
<el-sub-menu index="/problem">
<template #title>
<el-icon><Warning /></el-icon>
<span>问题反馈</span>
</template>
<el-menu-item index="/feedback">我有建议</el-menu-item>
<el-menu-item index="/complaint">快递投诉</el-menu-item>
</el-sub-menu>
<el-sub-menu index="/profile">
<template #title>
<el-icon><User /></el-icon>
<span>个人中心</span>
</template>
<el-menu-item index="/profile">我的资料/地址簿</el-menu-item>
</el-sub-menu>
</el-menu>
</div>
<!-- 顶部标题栏 -->
<div class="header">
<div class="header-left">
<el-icon class="toggle-icon" @click="toggleSidebar">
<Fold v-if="!isCollapse" />
<Expand v-else />
</el-icon>
<span class="current-route">{{ currentRoute }}</span>
</div>
<div class="header-right">
<el-dropdown>
<span class="user-info">
<el-avatar :size="30" :src="avatarUrl" />
<span class="username">{{ username }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="goToProfile"></el-dropdown-item>
<el-dropdown-item @click="logout">退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<style scoped>
.layout-container {
height: 100vh;
width: 100%;
}
/* 侧边栏样式 */
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 220px;
background-color: #304156;
transition: width 0.3s;
z-index: 1000;
overflow-y: auto;
overflow-x: hidden;
}
.sidebar.collapsed {
width: 64px;
}
/* 顶部标题栏样式 */
.header {
position: fixed;
top: 0;
right: 0;
width: calc(100% - 220px); /* 减去侧边栏宽度 */
height: 60px;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
z-index: 999;
transition: width 0.3s;
}
.sidebar.collapsed + .header {
width: calc(100% - 64px); /* 侧边栏折叠时的宽度 */
}
.header-left {
display: flex;
align-items: center;
gap: 15px;
}
.toggle-icon {
font-size: 20px;
cursor: pointer;
color: #606266;
}
.current-route {
font-size: 16px;
color: #303133;
font-weight: 500;
}
.header-right {
display: flex;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.username {
font-size: 14px;
color: #606266;
}
/* Logo样式 */
.logo {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.logo h2 {
margin: 0;
font-size: 18px;
white-space: nowrap;
}
/* 菜单样式 */
.sidebar-menu {
border-right: none;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.sidebar {
width: 64px;
}
.header {
width: calc(100% - 64px);
}
.username {
display: none;
}
.current-route {
font-size: 14px;
}
}
/* Element Plus 样式覆盖 */
:deep(.el-menu-item.is-active) {
background-color: #263445 !important;
}
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
background-color: #263445 !important;
}
:deep(.el-menu) {
border-right: none;
}
</style>
<script setup>
import { ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { Fold, Expand } from '@element-plus/icons-vue';
const router = useRouter();
const route = useRoute();
const isCollapse = ref(false);
const avatarUrl = ref('path/to/avatar.jpg');
const username = ref('用户名');
//
const currentRoute = computed(() => {
return route.meta.title || route.name || '首页';
});
//
const toggleSidebar = () => {
isCollapse.value = !isCollapse.value;
const sidebar = document.querySelector('.sidebar');
if (sidebar) {
sidebar.classList.toggle('collapsed');
}
};
//
const goToProfile = () => {
router.push('/profile');
};
// 退
const logout = () => {
router.push({ name: 'login' });
};
</script>

@ -0,0 +1,493 @@
<template>
<div class="body">
<div class="main">
<div class="switch" id="switch-cnt">
<div class="switch__circle"></div>
<div class="switch__circle switch__circle--t"></div>
<div class="switch__container" id="switch-c1">
<h2 class="switch__title title">欢迎!</h2>
<p class="switch__description description">已有帐号现在登陆</p>
<button class="switch__button button switch-btn" @click="change"></button>
<button class="switch__button button switch-btn" @click="toBack"></button>
</div>
<div class="switch__container is-hidden" id="switch-c2">
<h2 class="switch__title title">您好</h2>
<p class="switch__description description">没有账号现在注册</p>
<button class="switch__button button switch-btn" @click="change"></button>
</div>
</div>
<!-- signup-->
<div class="container a-container" id="a-container">
<el-form class="form" id="a-form" method="" action="">
<h2 class="form_title title">注册</h2>
<el-input class="form__input" type="text" placeholder="姓名" v-model="registerForm.name" />
<br>
<el-input class="form__input" type="text" placeholder="邮箱" v-model="registerForm.email" />
<br>
<el-input class="form__input" type="password" placeholder="密码" v-model="registerForm.password" />
<button class="form__button button submit" @click="register"></button>
</el-form>
</div>
<!-- signin-->
<div class="container b-container" id="b-container">
<el-form :model="foreuser" :rules="rules" ref="ruleFormRef" class="form" id="b-form" method=""
action="#">
<h2 class="form_title title">登陆</h2>
<el-form-item prop="mobile">
<el-input class="form__input" type="text" placeholder="电话号码" v-model="foreuser.mobile" />
</el-form-item>
<br>
<el-form-item prop="password">
<el-input class="form__input" type="password" placeholder="密码" v-model="foreuser.password"
show-password />
</el-form-item>
<el-button link class="form__link" @click="rescue">?</el-button>
<el-form-item>
<el-button class="form__button button submit" @click="login"></el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import useEmitter from '../hooks/useEmitter.js'
//
const router = useRouter();
//
const foreuser = reactive({
mobile: '',
password: '',
});
//
const registerForm = reactive({
name: '',
email: '',
password: '',
});
//
const rules = reactive({
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('手机号不能为空'));
} else if (!/^((13[0-9]|15[0-9]|16[0-9]|17[3-8]|18[0-9]|19[0-9]|14[5-7])\d{8})$/.test(value)) {
callback(new Error('请输入正确的手机号格式'));
} else {
callback();
}
}, trigger: 'blur'
}
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('邮箱不能为空'));
} else if (!/^[0-9a-zA-Z_.-]+@[0-9a-zA-Z_.-]+\.[a-zA-Z]{1,2}$/.test(value)) {
callback(new Error('请输入正确的邮箱格式'));
} else {
callback();
}
}, trigger: 'blur'
}
]
});
//
const toBack = () => {
router.push({ name: 'backLogin' });
};
const rescue = () => {
router.push({ name: 'rescue' });
};
//
const users = [
{ mobile: '18960955706', password: '123456' },
];
//
const login = () => {
const user = users.find(user => user.mobile === foreuser.mobile && user.password === foreuser.password);
if (user) {
ElMessage({
message: '登陆成功!',
type: 'success'
});
router.push({ name: 'home' });
} else {
ElMessage({
message: '用户名或密码错误!',
type: 'error'
});
}
};
const register = () => {
//
if (registerForm.name && registerForm.email && registerForm.password) {
ElMessage({
message: '注册成功!',
type: 'success'
});
//
router.push({ name: 'Header' });
} else {
ElMessage({
message: '请填写完整信息!',
type: 'error'
});
}
};
//
const sidebarOpen = ref(true)
const emitter = useEmitter()
const change = () => {
const switchC1 = document.querySelector("#switch-c1");
const switchC2 = document.querySelector("#switch-c2");
const switchCircle = document.querySelectorAll(".switch__circle");
const switchCtn = document.querySelector("#switch-cnt");
switchCtn.classList.add("is-gx");
setTimeout(() => {
switchCtn.classList.remove("is-gx");
}, 1500);
switchCtn.classList.toggle("is-txr");
switchCircle[0].classList.toggle("is-txr");
switchCircle[1].classList.toggle("is-txr");
//
switchC1.classList.toggle("is-hidden");
switchC2.classList.toggle("is-hidden");
// 使
sidebarOpen.value = !sidebarOpen.value;
emitter.emit('change', sidebarOpen.value);
};
onMounted(() => {
emitter.on('change', (isOpen) => {
const aContainer = document.querySelector("#a-container");
const bContainer = document.querySelector("#b-container");
//
aContainer.classList.toggle("is-txl");
bContainer.classList.toggle("is-txl");
bContainer.classList.toggle("is-z200");
});
});
</script>
<style scoped>
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
/* Generic */
.body {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Montserrat', sans-serif;
font-size: 12px;
background-color: #ecf0f3;
color: #a0a5a8;
}
/**/
.main {
position: relative;
width: 1000px;
min-width: 1000px;
min-height: 600px;
height: 600px;
padding: 25px;
background-color: #ecf0f3;
box-shadow: 10px 10px 10px #d1d9e6, -10px -10px 10px #f9f9f9;
border-radius: 12px;
overflow: hidden;
}
@media (max-width: 1200px) {
.main {
transform: scale(0.7);
}
}
@media (max-width: 1000px) {
.main {
transform: scale(0.6);
}
}
@media (max-width: 800px) {
.main {
transform: scale(0.5);
}
}
@media (max-width: 600px) {
.main {
transform: scale(0.4);
}
}
.container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
width: 600px;
height: 100%;
padding: 25px;
background-color: #ecf0f3;
transition: 1.25s;
}
.form {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 70%;
height: 100%;
}
.form__icon {
object-fit: contain;
width: 30px;
margin: 0 5px;
opacity: .5;
transition: .15s;
}
.form__icon:hover {
opacity: 1;
transition: .15s;
cursor: pointer;
}
.form__span {
margin-top: 30px;
margin-bottom: 12px;
}
.form__link {
color: #181818;
font-size: 15px;
margin-top: 25px;
border-bottom: 1px solid #a0a5a8;
line-height: 2;
}
.title {
font-size: 34px;
font-weight: 700;
line-height: 3;
color: #181818;
}
.description {
font-size: 14px;
letter-spacing: .25px;
text-align: center;
line-height: 1.6;
}
.button {
width: 180px;
height: 50px;
border-radius: 25px;
margin-top: 50px;
font-weight: 700;
font-size: 14px;
letter-spacing: 1.15px;
background-color: #4B70E2;
color: #f9f9f9;
box-shadow: 8px 8px 16px #d1d9e6, -8px -8px 16px #f9f9f9;
border: none;
outline: none;
}
/**/
.a-container {
z-index: 100;
left: calc(100% - 600px);
}
.b-container {
left: calc(100% - 600px);
z-index: 0;
}
.switch {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 400px;
padding: 50px;
z-index: 200;
transition: 1.25s;
background-color: #ecf0f3;
overflow: hidden;
box-shadow: 4px 4px 10px #d1d9e6, -4px -4px 10px #f9f9f9;
}
.switch__circle {
position: absolute;
width: 500px;
height: 500px;
border-radius: 50%;
background-color: #ecf0f3;
box-shadow: inset 8px 8px 12px #d1d9e6, inset -8px -8px 12px #f9f9f9;
bottom: -60%;
left: -60%;
transition: 1.25s;
}
.switch__circle--t {
top: -30%;
left: 60%;
width: 300px;
height: 300px;
}
.switch__container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: absolute;
width: 400px;
padding: 50px 55px;
transition: 1.25s;
}
.switch__button {
cursor: pointer;
}
.switch__button:hover {
box-shadow: 6px 6px 10px #d1d9e6, -6px -6px 10px #f9f9f9;
transform: scale(0.985);
transition: .25s;
}
.switch__button:active,
.switch__button:focus {
box-shadow: 2px 2px 6px #d1d9e6, -2px -2px 6px #f9f9f9;
transform: scale(0.97);
transition: .25s;
}
/**/
.is-txr {
left: calc(100% - 400px);
transition: 1.25s;
transform-origin: left;
}
.is-txl {
left: 0;
transition: 1.25s;
transform-origin: right;
}
.is-z200 {
z-index: 200;
transition: 1.25s;
}
.is-hidden {
visibility: hidden;
opacity: 0;
position: absolute;
transition: 1.25s;
}
.is-gx {
animation: is-gx 1.25s;
}
@keyframes is-gx {
0%,
10%,
100% {
width: 400px;
}
30%,
50% {
width: 500px;
}
}
.form__input :deep(.el-input__inner) {
width: 350px;
height: 40px;
margin: 4px 0;
padding-left: 25px;
font-size: 13px;
letter-spacing: 0.15px;
border: none;
outline: none;
font-family: "Montserrat", sans-serif;
background-color: #ecf0f3;
transition: 0.25s ease;
border-radius: 8px;
box-shadow: inset 3px 3px 3px #d1d9e6, inset -3px -3px 3px #f9f9f9;
cursor: default !important;
}
.form__input :deep(.el-input__wrapper) {
display: inline-flex;
/* flex-grow: 1; */
/* align-items: center; */
/* justify-content: center; */
/* padding: 1px 11px; */
background-color: transparent;
background-image: none;
cursor: default;
/*border-radius: var(--el-input-border-radius,var(--el-border-radius-base));*/
transition: var(--el-transition-box-shadow);
border: none;
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
}
.el-alert {
margin: 20px 0 0;
}
.el-alert:first-child {
margin: 0;
}
</style>

@ -0,0 +1,31 @@
<template>
<div class="not-found">
<h1>404 - 页面未找到</h1>
<p>您访问的页面不存在</p>
<el-button @click="goBack"></el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goBack = () => {
router.go(-1)
}
</script>
<style scoped>
.not-found {
text-align: center;
margin-top: 50px;
}
h1 {
color: #ff4d4f;
}
p {
font-size: 18px;
color: #666;
}
</style>

@ -0,0 +1,125 @@
<template>
<div class="forgot-password">
<el-card class="card">
<template #header>
<h2>忘记密码</h2>
</template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<!-- 手机号输入 -->
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<!-- 验证码输入 -->
<el-form-item label="验证码" prop="captcha">
<el-row>
<el-col :span="16">
<el-input v-model="form.captcha" placeholder="请输入验证码" />
</el-col>
<el-col :span="8">
<el-button :loading="loading" @click="getCaptcha" class="captcha-btn">获取验证码</el-button>
</el-col>
</el-row>
</el-form-item>
<!-- 新密码输入 -->
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="form.newPassword" placeholder="请输入新密码" />
</el-form-item>
<!-- 确认密码输入 -->
<el-form-item label="确认密码" prop="confirmPassword">
<el-input type="password" v-model="form.confirmPassword" placeholder="请确认密码" />
</el-form-item>
<!-- 提交按钮 -->
<el-form-item>
<el-button type="primary" @click="resetPassword"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
const form = reactive({
mobile: '',
captcha: '',
newPassword: '',
confirmPassword: '',
});
const loading = ref(false);
const rules = reactive({
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^[1][3-9][0-9]{9}$/, message: '请输入有效的手机号', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 16, message: '密码长度应在6-16字符之间', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: (rule, value, callback) => {
if (value !== form.newPassword) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
}, trigger: 'blur' }
]
});
//
const getCaptcha = async () => {
if (!form.mobile) {
ElMessage.error('请输入手机号');
return;
}
loading.value = true;
setTimeout(() => {
loading.value = false;
ElMessage.success('验证码已发送');
}, 2000); //
};
//
const resetPassword = () => {
//
if (!form.mobile || !form.captcha || !form.newPassword || !form.confirmPassword) {
ElMessage.error('请填写完整的表单信息');
return;
}
//
ElMessage.success('密码重置成功!');
// API
};
</script>
<style scoped>
.forgot-password {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.card {
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.captcha-btn {
width: 100%;
background-color: #409EFF;
border-color: #409EFF;
}
</style>

@ -0,0 +1,108 @@
<template>
<div class="app-container">
<div class="header">
<h1 class="title">快递后台管理中心</h1>
</div>
<div class="menu">
<el-menu :default-active="activeIndex" :router="true" class="el-menu-demo" mode="horizontal" :ellipsis="false"
@select="handleSelect">
<el-menu-item index="1" :route="{ name: 'personal' }">用户管理</el-menu-item>
<el-menu-item index="2" :route="{ name: 'problem' }">问题中心</el-menu-item>
<el-menu-item index="3" :route="{ name: 'addkuaidi' }">快件录入</el-menu-item>
<el-menu-item index="4" :route="{ name: 'shopp' }">商品/货物管理</el-menu-item>
<el-menu-item index="5" :route="{ name: 'notice' }">公告发布</el-menu-item>
<el-menu-item index="6" :route="{ name: 'login' }" class="menu-item-right">退出</el-menu-item>
</el-menu>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
//
const activeIndex = ref('1');
//
const route = useRoute();
//
watchEffect(() => {
switch (route.name) {
case 'personal':
activeIndex.value = '1';
break;
case 'problem':
activeIndex.value = '2';
break;
case 'addkuaidi':
activeIndex.value = '3';
break;
case 'shopp':
activeIndex.value = '4';
break;
case 'notice':
activeIndex.value = '5';
break;
case 'login':
activeIndex.value = '6';
break;
default:
activeIndex.value = '1';
break;
}
});
</script>
<style scoped>
.app-container {
display: relative;
flex-direction: column;
height: 100vh;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
background-color: #a4bacb;
color: aliceblue;
padding: 10px 20px;
}
.menu {
margin-top: 50px;
}
.title {
font-size: 15px;
text-align: center;
}
.content {
margin-top: 100px;
/* 根据 header 和 menu 的高度调整 */
flex: 1;
overflow-y: auto;
}
@media (max-width: 600px) {
.header h1 {
font-size: 1.5rem;
}
.el-menu--horizontal {
font-size: 0.9rem;
}
}
.menu-item-right {
margin-left: auto;
}
</style>

@ -0,0 +1,9 @@
// src/hooks/useEmitter.js
import { getCurrentInstance } from 'vue'
export default function useEmitter() {
const internalInstance = getCurrentInstance()
const emitter = internalInstance.appContext.config.globalProperties.emitter
return emitter
}

@ -0,0 +1,28 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as Icons from '@element-plus/icons-vue'
import mitt from 'mitt';
import Header from './components/Header.vue';
import backIndex from './components/backIndex.vue';
import router from './router'
import App from './App.vue'
const app = createApp(App)
const emitter = mitt()
Object.keys(Icons).forEach((key) => {
app.component(key, Icons[key]);
});
app.use(ElementPlus)
app.use(router)
app.config.globalProperties.emitter = emitter;
app.component('Header', Header);
app.component('backIndex', backIndex);
app.mount('#app')

@ -0,0 +1,8 @@
import axios from "axios";
import { ElEmpty } from "element-plus";
const request = axios.create({
baseURL: 'http://localhost:8081'
})
export default request

@ -0,0 +1,114 @@
import { createRouter, createWebHashHistory } from "vue-router"
const routes= [
// 登录相关
{
path: "/",
name: 'login',
component: () => import("../components/Login.vue")
},
{
path: "/backLogin",
name: 'backLogin',
component: () => import("../views/back/backLogin.vue")
},
{
path: '/rescue',
name: 'rescue',
component: () => import("../components/Rescue.vue")
},
{
path: '/backIndex',
name: 'backIndex',
component: () => import('../components/backIndex.vue')
},
// 页面组件
{
path: "/header",
name: 'header',
component: () => import("../components/Header.vue")
},
// 前台用户相关页面
{
path: '/home',
name: 'home',
component: () => import("../views/Home.vue")
},
{
path: '/delivery',
name: 'delivery',
component: () => import('../views/fore/delivery.vue')
},
{
path: '/check',
name: 'check',
component: () => import('../views/fore/check.vue')
},
{
path: '/service',
name: 'service',
component: () => import('../views/fore/service.vue')
},
{
path: '/profile',
name: 'profile',
component: () => import('../views/fore/profile.vue')
},
{
path: '/complaint',
name: 'complaint',
component: () => import('../views/fore/complaint.vue')
},
{
path: '/feedback',
name: 'feedback',
component: () => import('../views/fore/feedback.vue')
},
// 后台管理页面
{
path: '/shopp',
name: 'shopp',
component: () => import('../views/back/shopp.vue')
},
{
path: '/addkuaidi',
name: 'addkuaidi',
component: () => import('../views/back/addkuaidi.vue')
},
{
path: '/personal',
name: 'personal',
component: () => import('../views/back/personal.vue')
},
{
path: '/problem',
name: 'problem',
component: () => import('../views/back/problem.vue')
},
{
path: '/notice',
name: 'notice',
component: () => import('../views/back/notice.vue')
},
// 快递员页面
{
path: '/indexDeliveryman',
name: 'indexDeliveryman',
component: () => import('../views/courier/index.vue')
},
// 404 页面
{
path: '/:pathMatch(.*)*',
component: () => import('../components/NotFound.vue')
}
]
const router = createRouter({
history: createWebHashHistory(), routes
})
export default router

@ -0,0 +1,18 @@
import {
defineStore
}
from "pinia";
export const userStore = defineStore('storeId', {
state: () => {
return {
loginState: false // 登录状态, 已经登录true没有登录false
}
},
getters: {},
actions: {
setLoginState(state) {
this.loginState = state
}
}
})

@ -0,0 +1,79 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

@ -0,0 +1,63 @@
// 包裹信息接口
export interface Package {
trackingNumber: string
packageName: string
sender: string
senderaddress: string
receiver: string
receiveraddress: string
status: string
latestLocation: string
}
// 投诉表单接口
export interface ComplaintForm {
orderNumber: string
reason: string
description: string
contact: string
}
// 寄件信息接口
export interface DeliveryForm {
sender: {
name: string
phone: string
province: string[]
address: string
company?: string
}
receiver: {
name: string
phone: string
province: string[]
address: string
company?: string
}
sendType: number
}
// 反馈表单接口
export interface FeedbackForm {
title: string
content: string
contact?: string
}
// 用户信息接口
export interface UserProfile {
name: string
email: string
phone: string
gender: string
birthday: string
address: string
}
// 地址信息接口
export interface Address {
id: number
name: string
address: string
phone: string
}

@ -0,0 +1,53 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 创建axios实例
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API || '',
timeout: 5000
})
// 请求拦截器
request.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
response => {
const res = response.data
// 这里可以根据后端的响应结构进行适当的处理
if (res.code !== 200) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
error => {
console.log('err' + error)
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default request

@ -0,0 +1,201 @@
<template>
<div class="home-container">
<Header />
<div class="content-container">
<router-view v-if="$route.path !== '/home'"></router-view>
<!-- 首页内容仅在路径为 /home 时显示 -->
<div v-else class="home-content">
<!-- 轮播图区域 -->
<div class="carousel-section">
<el-carousel :interval="4000" type="card" height="300px">
<el-carousel-item v-for="item in carouselImages" :key="item.id">
<el-image :src="item.url" fit="cover" class="carousel-image" />
</el-carousel-item>
</el-carousel>
</div>
<!-- 快捷功能区域 -->
<div class="quick-access">
<div class="quick-item" @click="$router.push('/delivery')">
<el-icon><Van /></el-icon>
<span>我要寄件</span>
</div>
<div class="quick-item" @click="$router.push('/check')">
<el-icon><Search /></el-icon>
<span>运单查询</span>
</div>
<div class="quick-item" @click="$router.push('/service')">
<el-icon><Service /></el-icon>
<span>服务指南</span>
</div>
</div>
<!-- 公告区域 -->
<div class="notice-section">
<h3><el-icon><Bell /></el-icon> </h3>
<el-scrollbar height="150px">
<div v-for="notice in notices" :key="notice.id" class="notice-item">
<span class="notice-title">{{ notice.title }}</span>
<span class="notice-date">{{ notice.date }}</span>
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import Header from '../components/Header.vue';
import { ref } from 'vue';
import { Van, Search, Service, Bell } from '@element-plus/icons-vue';
//
const carouselImages = ref([
{ id: 1, url: 'src/assets/sf1.jpg' },
{ id: 2, url: 'src/assets/sf2.jpg' },
{ id: 3, url: 'src/assets/sf3.png' },
{ id: 4, url: 'src/assets/sf4.jpg' },
{ id: 5, url: 'src/assets/sf5.jpg' },
{ id: 6, url: 'src/assets/sf6.jpg' },
{ id: 7, url: 'src/assets/sf7.jpg' },
{ id: 8, url: 'src/assets/sf8.jpg' },
]);
//
const notices = ref([
{ id: 1, title: '关于春节期间快递服务安排的通知', date: '2024-02-01' },
{ id: 2, title: '服务升级公告:新增智能快递柜服务', date: '2024-01-28' },
{ id: 3, title: '关于恶劣天气影响部分地区配送的通知', date: '2024-01-25' },
{ id: 4, title: '快递服务费用调整通知', date: '2024-01-20' },
]);
</script>
<style scoped>
.home-container {
width: 100%;
height: 100vh;
display: flex;
overflow: hidden;
position: fixed;
top: 0;
left: 0;
}
.content-container {
flex: 1;
margin-left: 220px;
height: 100vh;
overflow: hidden;
background-color: #f0f2f5;
position: relative;
}
.home-content {
position: fixed;
top: 60px;
right: 0;
width: calc(100% - 220px);
height: calc(100vh - 60px);
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
/* 轮播图样式 */
.carousel-section {
margin: 0 auto;
max-width: 1200px;
margin-bottom: 30px;
}
.carousel-image {
width: 100%;
height: 100%;
border-radius: 8px;
}
/* 快捷功能区域样式 */
.quick-access {
display: flex;
justify-content: space-around;
margin: 30px auto;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
max-width: 1200px;
}
.quick-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 20px;
border-radius: 8px;
transition: all 0.3s;
}
.quick-item:hover {
background: #f5f7fa;
transform: translateY(-5px);
}
.quick-item .el-icon {
font-size: 40px;
color: #409EFF;
margin-bottom: 10px;
}
/* 公告区域样式 */
.notice-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
max-width: 1200px;
margin: 0 auto;
}
.notice-section h3 {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 15px;
color: #303133;
}
.notice-item {
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #ebeef5;
cursor: pointer;
}
.notice-item:hover {
background: #f5f7fa;
}
.notice-date {
color: #909399;
font-size: 14px;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.content-container {
margin-left: 64px;
}
.home-content {
width: calc(100% - 64px);
}
.quick-access {
flex-direction: column;
gap: 15px;
}
}
</style>

@ -0,0 +1,131 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
//
const form = reactive({
trackingNumber: '',
company: '',
recipientName: '',
phoneNumber: '',
adminName: '',
});
//
const rules = {
trackingNumber: [{ required: true, message: '请输入快递单号', trigger: 'blur' }],
company: [{ required: true, message: '请选择快递公司', trigger: 'change' }],
recipientName: [{ required: true, message: '请输入收件人姓名', trigger: 'blur' }],
phoneNumber: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
adminName: [{ required: true, message: '请输入管理员姓名', trigger: 'blur' }],
};
//
const formRef = ref(null);
//
const submitForm = () => {
if (formRef) {
console.log('表单提交成功', form);
alert('快递信息提交成功!');
} else {
alert('请填写完整信息!');
}
// });
};
</script>
<template>
<el-container>
<!-- 顶部导航 -->
<el-header>
<backIndex />
</el-header>
<!-- 主内容区域 -->
<el-container>
<el-main>
<div style="
padding: 20px;
max-width: 600px;
margin: 30px auto;
background: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
">
<h2>录入快件</h2>
<!-- 表单部分 -->
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="单号" prop="trackingNumber">
<el-input v-model="form.trackingNumber" placeholder="请输入快递单号" />
</el-form-item>
<el-form-item label="快递公司" prop="company">
<el-select v-model="form.company" placeholder="请选择快递公司">
<el-option label="顺丰速运" value="顺丰速运" />
<el-option label="中通快递" value="中通快递" />
<el-option label="圆通速递" value="圆通速递" />
<el-option label="韵达快递" value="韵达快递" />
<el-option label="京东物流" value="京东物流" />
<el-option label="菜鸟裹裹" value="菜鸟裹裹" />
</el-select>
</el-form-item>
<el-form-item label="收件人姓名" prop="recipientName">
<el-input v-model="form.recipientName" placeholder="请输入收件人姓名" />
</el-form-item>
<el-form-item label="手机号" prop="phoneNumber">
<el-input v-model="form.phoneNumber" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="管理员姓名" prop="adminName">
<el-input v-model="form.adminName" placeholder="请输入管理员姓名" />
</el-form-item>
</el-form>
<!-- 提交按钮 -->
<div style="text-align: center; margin-top: 20px;">
<el-button type="primary" :disabled="!form.trackingNumber ||
!form.company ||
!form.recipientName ||
!form.phoneNumber ||
!form.adminName
" @click="submitForm">
立即提交
</el-button>
</div>
</div>
</el-main>
</el-container>
</el-container>
</template>
<style scoped>
h2 {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
}
.el-form-item {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
div[style*='max-width: 600px;'] {
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
div[style*='max-width: 600px;'] {
max-width: 90%;
padding: 15px;
}
h2 {
font-size: 20px;
}
}
</style>

@ -0,0 +1,234 @@
<script setup>
import { ref, reactive } from 'vue';
import { User, Lock } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { useRouter } from 'vue-router';
const foreuser = reactive({ username: '', password: '', mobile: '' });
const router = useRouter();
const userType = ref('');
const rules = reactive({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
});
//
const adims = [
{ mobile: 'admin', password: '123456' },
];
//
const users = [
{ mobile: 'user', password: '123456' },
];
//
const login = () => {
if (!userType.value) {
ElMessage.error('请选择用户类型');
return;
}
//
if (!foreuser.username || !foreuser.password) {
ElMessage.error('请输入完整的账号和密码');
return;
}
//
if (userType.value === '0') { //
const admin = adims.find(item => item.mobile === foreuser.username && item.password === foreuser.password);
if (admin) {
ElMessage.success('登陆成功!');
router.push({ name: 'notice' }); //
} else {
ElMessage.error('用户名或密码错误!');
}
} else if (userType.value === '1') { //
const user = users.find(item => item.mobile === foreuser.username && item.password === foreuser.password);
if (user) {
ElMessage.success('登陆成功!');
router.push({ name: 'indexDeliveryman' }); //
} else {
ElMessage.error('用户名或密码错误!');
}
}
};
const goBack = () => {
router.go(-1)
}
</script>
<template>
<div class="login-container">
<el-card class="login-card">
<template #header>
<h2 class="card-title">后台登陆</h2>
</template>
<el-form :model="foreuser" :rules="rules" ref="ruleFormRef" action="#">
<el-row :gutter="10" class="form-row">
<el-col :span="4" :offset="1">
<span class="label">账号<span v-if="!foreuser.username" class="required">*</span></span>
</el-col>
<el-col :span="15" :offset="0">
<el-form-item prop="username">
<el-input placeholder="账号" class="input" v-model="foreuser.username" :prefix-icon="User" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10" class="form-row">
<el-col :span="4" :offset="1">
<span class="label">密码<span v-if="!foreuser.password" class="required">*</span></span>
</el-col>
<el-col :span="15" :offset="0">
<el-form-item prop="password">
<el-input placeholder="密码" class="input" v-model="foreuser.password" :prefix-icon="Lock" show-password />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10" class="form-row">
<el-col :span="5" :offset="0">
<span class="label">用户类型</span>
</el-col>
<el-col :span="15" :offset="0">
<el-form-item>
<el-select v-model="userType" placeholder="点击选择" class="select">
<el-option label="管理员" value="0" />
<el-option label="快递员" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<a href="#" class="link">忘记密码</a>
</el-form-item>
<el-form-item>
<el-button class="btn primary-btn" type="primary" @click="login"></el-button>
<el-button class="btn secondary-btn" @click="goBack"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.login-card {
background-color: #fff;
padding: 40px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 400px;
max-width: 100%;
}
.card-title {
font-size: 24px;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 20px;
}
.form-row {
margin-bottom: 10px; /* 增加每个输入框之间的间距 */
}
.input {
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.input input {
border: none;
outline: none;
box-shadow: none;
}
.select {
width: 100%;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.select select {
border: none;
outline: none;
box-shadow: none;
}
.link {
color: #409eff;
text-decoration: none;
cursor: pointer;
float: right;
margin-left: 220px;
}
.link:hover {
text-decoration: underline;
}
.btn {
margin-right: 10px;
padding: 10px 20px;
font-size: 16px;
border-radius: 5px;
transition: all 0.3s ease;
}
.primary-btn {
background-color: #409eff;
color: #fff;
margin-left: 100px;
}
.primary-btn:hover {
background-color: #66b1ff;
}
.secondary-btn {
background-color: #fff;
color: #409eff;
border: 1px solid #409eff;
}
.secondary-btn:hover {
background-color: #e4e7ed;
color: #303133;
}
.label {
display: block;
margin-bottom: 5px;
font-size: 14px;
color: #606266;
}
.required {
color: red;
margin-left: 2px;
}
</style>

@ -0,0 +1,311 @@
<template>
<el-container>
<!-- 顶部 Header -->
<el-header>
<backIndex />
</el-header>
<!-- 主体内容 -->
<el-container class="ain">
<!-- 右侧主要内容 -->
<el-main class="el-main">
<div class="filter-bar"
style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; justify-content: center; align-items: center; gap: 10px; width: 100%;">
<!-- 筛选区域 -->
<el-input placeholder="请输入标题" style="width: 300px;" />
<el-date-picker placeholder="请选择日期" type="date" style="width: 200px;" />
<el-button type="primary">查询</el-button>
<el-button>重置</el-button>
</div>
<!-- 发布按钮 -->
<el-button type="success" @click="openPublishDialog"></el-button>
</div>
<!-- 表格区域 -->
<!-- 表格区域 -->
<div style="display: flex; justify-content: center; width: 100%; padding: 20px 0;">
<el-table :data="tableData" style="width: 75%; background-color: #fff; border-radius: 5px;">
<el-table-column prop="title" label="公告标题" width="400" />
<el-table-column prop="department" label="发布部门" width="200" />
<el-table-column prop="date" label="预计发布时间" width="200" />
<el-table-column prop="status" label="状态" width="150">
<template #default="scope">
<el-tag type="success" v-if="scope.row.status === '已发布'"></el-tag>
<el-tag type="info" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="400">
<template #default="scope">
<el-button v-if="scope.row.status === ''" type="success" size="small"
@click="publish(scope.row)">发布</el-button>
<el-button v-else type="warning" size="small"
@click="unpublish(scope.row)">撤回</el-button>
<el-button type="primary" size="small" @click="openEditDialog(scope.row)"></el-button>
<el-button type="danger" size="small" @click="remove(scope.row)"></el-button>
<el-button type="info" size="small" @click="viewDetails(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div style="display: flex; justify-content: center; margin-top: 20px;">
<el-pagination background layout="prev, pager, next" :total="30" />
</div>
</el-main>
</el-container>
<!-- 页脚 -->
<el-footer>
<Footer />
</el-footer>
<!-- 发布公告的弹窗 -->
<el-dialog title="发布公告" v-model="dialogVisible" width="600px" align-center>
<el-form :model="formData" label-width="120px">
<el-form-item label="公告标题">
<el-input v-model="formData.title" placeholder="请输入公告标题" />
</el-form-item>
<el-form-item label="公告内容">
<el-input type="textarea" v-model="formData.content" placeholder="请输入公告内容" />
</el-form-item>
<el-form-item label="预计发布时间">
<el-date-picker v-model="formData.date" type="date" placeholder="请选择日期" />
</el-form-item>
<el-form-item label="发布部门">
<el-input v-model="formData.department" placeholder="请输入发布部门" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="savePublish"></el-button>
</div>
</el-dialog>
<!-- 编辑公告的弹窗 -->
<el-dialog title="编辑公告" v-model="editDialogVisible" width="600px" align-center>
<el-form :model="editFormData" label-width="120px">
<el-form-item label="公告标题">
<el-input v-model="editFormData.title" placeholder="请输入公告标题" />
</el-form-item>
<el-form-item label="公告内容">
<el-input type="textarea" v-model="editFormData.content" placeholder="请输入公告内容" />
</el-form-item>
<el-form-item label="预计发布时间">
<el-date-picker v-model="editFormData.date" type="date" placeholder="请选择日期" />
</el-form-item>
<el-form-item label="发布部门">
<el-input v-model="editFormData.department" placeholder="请输入发布部门" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveEdit"></el-button>
</div>
</el-dialog>
<!-- 查看公告详情弹窗 -->
<el-dialog title="公告详情" v-model="detailsDialogVisible" width="600px" align-center>
<el-row v-if="viewFormData" class="details-content">
<el-row class="detail-item"><strong>标题</strong>{{ viewFormData.title }}</el-row>
<el-row class="detail-item"><strong>内容</strong>
<el-row class="detail-item content-item">{{ viewFormData.content }}</el-row>
</el-row>
<el-row class="detail-item"><strong>日期</strong>{{ viewFormData.date }}</el-row>
<el-row class="detail-item"><strong>部门</strong>{{ viewFormData.department }}</el-row>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="detailsDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
//
const tableData = reactive([
{ id: '1', title: '双十一关于快递员公告', department: '运输部', date: '2021-10-27', content: '新华社利马11月14日电记者 张远、郝云甫当地时间11月14日下午国家主席习近平乘专机抵达利马应秘鲁共和国总统博鲁阿尔特邀请出席亚太经合组织第三十一次领导人非正式会议并对秘鲁进行国事访问。' + '习近平乘专机抵达利马卡亚俄空军基地时,秘鲁部长会议主席阿德里安森等高级官员热情迎接。当地学生向习近平献花,用中文对习近平到访表示热烈欢迎。礼兵分列红毯两侧致敬。', status: '未发布' },
{ id: '2', title: '关于优化用户对寄件功能的完善', department: '寄件部', date: '2021-10-27', content: '这是测试内容2', status: '未发布' },
{ id: '3', title: '因天气原因导致部分地区无法或延迟送达的地区', department: '快递部', date: '2021-10-26', content: '这是测试内容3', status: '未发布' },
{ id: '4', title: '双十一关于快递员公告', department: '运输部', date: '2021-10-27', content: '新华社利马11月14日电记者 张远、郝云甫当地时间11月14日下午国家主席习近平乘专机抵达利马应秘鲁共和国总统博鲁阿尔特邀请出席亚太经合组织第三十一次领导人非正式会议并对秘鲁进行国事访问。' + '习近平乘专机抵达利马卡亚俄空军基地时,秘鲁部长会议主席阿德里安森等高级官员热情迎接。当地学生向习近平献花,用中文对习近平到访表示热烈欢迎。礼兵分列红毯两侧致敬。', status: '未发布' },
{ id: '5', title: '关于优化用户对寄件功能的完善', department: '寄件部', date: '2021-10-27', content: '这是测试内容2', status: '未发布' },
{ id: '6', title: '因天气原因导致部分地区无法或延迟送达的地区', department: '快递部', date: '2021-10-26', content: '这是测试内容3', status: '未发布' },
{ id: '7', title: '双十一关于快递员公告', department: '运输部', date: '2021-10-27', content: '新华社利马11月14日电记者 张远、郝云甫当地时间11月14日下午国家主席习近平乘专机抵达利马应秘鲁共和国总统博鲁阿尔特邀请出席亚太经合组织第三十一次领导人非正式会议并对秘鲁进行国事访问。' + '习近平乘专机抵达利马卡亚俄空军基地时,秘鲁部长会议主席阿德里安森等高级官员热情迎接。当地学生向习近平献花,用中文对习近平到访表示热烈欢迎。礼兵分列红毯两侧致敬。', status: '未发布' },
{ id: '8', title: '关于优化用户对寄件功能的完善', department: '寄件部', date: '2021-10-27', content: '这是测试内容2', status: '未发布' },
{ id: '9', title: '因天气原因导致部分地区无法或延迟送达的地区', department: '快递部', date: '2021-10-26', content: '这是测试内容3', status: '未发布' },
{ id: '10', title: '双十一关于快递员公告', department: '运输部', date: '2021-10-27', content: '新华社利马11月14日电记者 张远、郝云甫当地时间11月14日下午国家主席习近平乘专机抵达利马应秘鲁共和国总统博鲁阿尔特邀请出席亚太经合组织第三十一次领导人非正式会议并对秘鲁进行国事访问。' + '习近平乘专机抵达利马卡亚俄空军基地时,秘鲁部长会议主席阿德里安森等高级官员热情迎接。当地学生向习近平献花,用中文对习近平到访表示热烈欢迎。礼兵分列红毯两侧致敬。', status: '未发布' },
{ id: '11', title: '关于优化用户对寄件功能的完善', department: '寄件部', date: '2021-10-27', content: '这是测试内容2', status: '未发布' },
{ id: '12', title: '因天气原因导致部分地区无法或延迟送达的地区', department: '快递部', date: '2021-10-26', content: '这是测试内容3', status: '未发布' },
]);
// ID
let nextId = 4;
//
const dialogVisible = ref(false);
const formData = reactive({
id: '',
title: '',
content: '',
date: new Date(),
department: '',
});
const publish = (row) => {
row.status = '已发布';
// console.log(` "${row.id}" `);
};
const unpublish = (row) => {
row.status = '未发布';
// console.log(` "${row.id}" `);
};
//
const editDialogVisible = ref(false);
const editFormData = reactive({
id: '',
title: '',
content: '',
date: new Date(),
department: '',
});
//
const detailsDialogVisible = ref(false);
const viewFormData = reactive({
id: '',
title: '',
content: '',
date: '',
department: '',
});
//
const openPublishDialog = () => {
dialogVisible.value = true;
};
//
const openEditDialog = (row: any) => {
Object.assign(editFormData, row);
editFormData.date = new Date(row.date);
editDialogVisible.value = true;
};
//
const viewDetails = (row: any) => {
//
Object.assign(viewFormData, row);
detailsDialogVisible.value = true;
};
//
const savePublish = () => {
if (formData.title && formData.content && formData.date && formData.department) {
const newId = nextId++;
tableData.push({
...formData,
date: formData.date.getFullYear() + '-' + (formData.date.getMonth() + 1).toString().padStart(2, '0') + '-' + formData.date.getDate().toString().padStart(2, '0'),
status: '未发布',
});
//
Object.assign(formData, {
title: '',
content: '',
date: '',
department: '',
});
}
// console.log(':', formData.id);
dialogVisible.value = false;
};
//
const saveEdit = () => {
const formattedDate = editFormData.date.getFullYear() + '-' + (editFormData.date.getMonth() + 1).toString().padStart(2, '0') + '-' + editFormData.date.getDate().toString().padStart(2, '0');
const index = tableData.findIndex(item => item.id === editFormData.id);
if (index !== -1) {
const updatedRow = {
...tableData[index],
title: editFormData.title,
content: editFormData.content,
department: editFormData.department,
date: formattedDate,
};
tableData[index] = updatedRow; //
}
editDialogVisible.value = false;
};
//
const remove = (row: any) => {
tableData.splice(tableData.indexOf(row), 1);
// console.log(':', row);
};
</script>
<style scoped>
.el-main {
padding: 20px;
background-color: #ecf0f1;
}
.ain {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.el-footer {
color: #fff;
text-align: center;
}
.details-content {
display: flex;
flex-direction: column;
padding-right: 15px;
}
.detail-item {
margin-bottom: 10px;
line-height: 1.5;
}
.details-content strong {
margin-right: 10px;
}
.content-item {
display: flex;
flex-direction: column;
}
.dialog-footer {
display: flex;
justify-content: center;
/* 水平居中 */
gap: 20px;
/* 按钮之间的间距 */
}
.content-box {
max-height: 200px;
overflow-y: auto;
padding: 5px;
border: 1px solid #ddd;
margin-top: 5px;
}
</style>

@ -0,0 +1,527 @@
<template>
<el-container>
<el-header>
<backIndex />
</el-header>
<el-container>
<el-main class="emain">
<div class="management-container">
<!-- 用户管理 -->
<div class="section">
<h3>用户管理</h3>
</div>
<div>
<!-- 搜索框和添加用户按钮的容器 -->
<div class="toolbar">
<!-- 用户搜索框 -->
<el-input v-model="userSearch" placeholder="输入员工id或用户id" class="user-search" prefix-icon="el-icon-search">
</el-input>
<!-- 添加用户按钮 -->
<el-button @click="handleAddUser" type="primary" class="add-user-button">
添加用户
</el-button>
</div>
<!-- 用户表格 -->
<el-table :data="paginatedUsers" stripe style="width: 90%; margin: 20px auto;" border>
<el-table-column prop="id" label="用户ID" align="center"></el-table-column>
<el-table-column prop="email" label="邮箱" align="center"></el-table-column>
<el-table-column prop="password" label="密码" align="center"></el-table-column>
<el-table-column prop="phone" label="电话号" align="center"></el-table-column>
<el-table-column prop="lastLogin" label="最后登录时间" align="center"></el-table-column>
<el-table-column prop="registerTime" label="注册时间" align="center"></el-table-column>
<el-table-column label="操作" width="220" align="center">
<template v-slot="scope">
<div class="button-group">
<el-button @click="handleEditUser(scope.row)" size="mini" type="primary" plain>编辑</el-button>
<el-button @click="handleDeleteUser(scope.row)" size="mini" type="danger" plain>删除</el-button>
<el-button @click="handleViewUser(scope.row)" size="mini" type="info" plain>查看详情</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<el-pagination :current-page="userCurrentPage" :page-size="userPageSize" :total="userData.length"
@current-change="handleUserPageChange" layout="prev, pager, next, jumper" class="pagination" />
</div>
<!-- 快递员管理 -->
<div class="section">
<h3>快递员管理</h3>
<!-- 添加快递员按钮 -->
<div class="action-buttons">
<el-button @click="handleAddCourier" type="primary">添加快递员</el-button>
</div>
<el-table :data="paginatedCouriers" stripe style="width: 90%; margin: 0 auto;" border>
<el-table-column prop="id" label="员工号" align="center"></el-table-column>
<el-table-column prop="name" label="姓名" align="center"></el-table-column>
<el-table-column prop="status" label="在职状态" align="center"></el-table-column>
<el-table-column prop="phone" label="电话" align="center"></el-table-column>
<el-table-column prop="entryTime" label="入职时间" align="center"></el-table-column>
<el-table-column label="操作" width="220" align="center">
<template v-slot="scope">
<div class="button-group">
<el-button @click="handleEditCourier(scope.row)" size="mini" type="primary" plain>编辑</el-button>
<el-button @click="handleDeleteCourier(scope.row)" size="mini" type="danger" plain>删除</el-button>
<el-button @click="handleViewCourier(scope.row)" size="mini" type="info" plain>查看详情</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<el-pagination :current-page="courierCurrentPage" :page-size="courierPageSize" :total="courierData.length"
@current-change="handleCourierPageChange" layout="prev, pager, next, jumper" class="pagination" />
</div>
</div>
</el-main>
</el-container>
<el-footer>
<Footer />
</el-footer>
<!-- 编辑用户对话框 -->
<el-dialog title="编辑用户" v-model="editUserDialogVisible">
<el-form :model="currentUser">
<el-form-item label="用户ID">
<el-input v-model="currentUser.id" readonly />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="currentUser.email" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="currentUser.password" />
</el-form-item>
<el-form-item label="电话号">
<el-input v-model="currentUser.phone" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="editUserDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveUserEdit"></el-button>
</div>
</el-dialog>
<!-- 添加用户对话框 -->
<el-dialog title="添加用户" v-model="addUserDialogVisible">
<el-form :model="newUser" label-width="100px">
<el-form-item label="用户ID">
<el-input v-model="newUser.id" placeholder="请输入用户ID" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="newUser.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="newUser.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="电话号">
<el-input v-model="newUser.phone" placeholder="请输入电话号码" />
</el-form-item>
<el-form-item label="注册时间">
<el-date-picker v-model="newUser.registerTime" type="date" placeholder="选择注册时间" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addUserDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveNewUser"></el-button>
</div>
</el-dialog>
<!-- 编辑快递员对话框 -->
<el-dialog title="编辑快递员" v-model="editCourierDialogVisible">
<el-form :model="currentCourier">
<el-form-item label="员工号">
<el-input v-model="currentCourier.id" readonly />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="currentCourier.name" />
</el-form-item>
<el-form-item label="在职状态">
<el-input v-model="currentCourier.status" />
</el-form-item>
<el-form-item label="电话">
<el-input v-model="currentCourier.phone" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="editCourierDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveCourierEdit"></el-button>
</div>
</el-dialog>
<!-- 添加快递员对话框 -->
<el-dialog title="添加快递员" v-model="addCourierDialogVisible">
<el-form :model="newCourier" label-width="100px">
<el-form-item label="员工号">
<el-input v-model="newCourier.id" placeholder="请输入员工号" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="newCourier.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="在职状态">
<el-input v-model="newCourier.status" placeholder="请输入在职状态(如:在职、离职)" />
</el-form-item>
<el-form-item label="电话">
<el-input v-model="newCourier.phone" placeholder="请输入电话号码" />
</el-form-item>
<el-form-item label="入职时间">
<el-date-picker v-model="newCourier.entryTime" type="date" placeholder="选择入职时间" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addCourierDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveNewCourier"></el-button>
</div>
</el-dialog>
<!-- 查看快递员详情对话框 -->
<el-dialog title="快递员详情" v-model="viewCourierDialogVisible">
<el-form :model="currentCourier" label-width="100px">
<el-form-item label="员工号">
<el-input v-model="currentCourier.id" readonly />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="currentCourier.name" readonly />
</el-form-item>
<el-form-item label="在职状态">
<el-input v-model="currentCourier.status" readonly />
</el-form-item>
<el-form-item label="电话">
<el-input v-model="currentCourier.phone" readonly />
</el-form-item>
<el-form-item label="入职时间">
<el-input v-model="currentCourier.entryTime" readonly />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="viewCourierDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
<!-- 查看详情对话框 -->
<!-- 查看用户详情对话框 -->
<el-dialog title="查看用户详情" v-model="viewUserDialogVisible">
<el-form :model="currentUser" label-width="100px" readonly>
<el-form-item label="用户ID">
<el-input v-model="currentUser.id" readonly />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="currentUser.email" readonly />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="currentUser.password" readonly />
</el-form-item>
<el-form-item label="电话号">
<el-input v-model="currentUser.phone" readonly />
</el-form-item>
<el-form-item label="最后登录时间">
<el-input v-model="currentUser.lastLogin" readonly />
</el-form-item>
<el-form-item label="注册时间">
<el-input v-model="currentUser.registerTime" readonly />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="viewUserDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
//
const viewUserDialogVisible = ref(false)
const handleViewUser = (user) => {
currentUser.value = { ...user } //
viewUserDialogVisible.value = true //
}
//
const viewCourierDialogVisible = ref(false) //
const handleViewCourier = (courier: any) => {
currentCourier.value = { ...courier } //
viewCourierDialogVisible.value = true //
}
//
const addUserDialogVisible = ref(false) //
const newUser = ref({
id: '', // ID
email: '', //
password: '', //
phone: '', //
registerTime: '' //
})
const handleAddUser = () => {
newUser.value = { id: '', email: '', password: '', phone: '', registerTime: '' } //
addUserDialogVisible.value = true //
}
const handleSaveNewUser = () => {
if (!newUser.value.id || !newUser.value.email || !newUser.value.phone) {
ElMessage.warning('请完整填写信息')
return
}
//
ElMessage.success('用户添加成功')
addUserDialogVisible.value = false //
}
//
const userData = ref([
{ id: '001', email: 'user1@example.com', password: 'password123', phone: '13800000001', lastLogin: '2024-11-01 10:30:00', registerTime: '2023-01-15 14:20:00' },
{ id: '002', email: 'user2@example.com', password: 'password123', phone: '13800000002', lastLogin: '2024-11-02 12:00:00', registerTime: '2023-02-10 09:15:00' },
// ...
]);
const courierData = ref([
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
// ...
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
{ id: '001', name: '小刘', status: '在职', phone: '13800000001', entryTime: '2022-03-01' },
{ id: '002', name: '小王', status: '离职', phone: '13800000002', entryTime: '2021-06-15' },
]);
// /
const currentUser = ref<any>({})
const currentCourier = ref<any>({})
//
const editUserDialogVisible = ref(false)
const editCourierDialogVisible = ref(false)
//
const userPageSize = 5
const courierPageSize = 5
const userCurrentPage = ref(1)
const courierCurrentPage = ref(1)
const userSearch = ref('')
//
const addCourierDialogVisible = ref(false) //
const newCourier = ref({
id: '', //
name: '', //
status: '', //
phone: '', //
entryTime: '' //
})
const handleAddCourier = () => {
newCourier.value = { id: '', name: '', status: '', phone: '', entryTime: '' } //
addCourierDialogVisible.value = true //
}
const handleSaveNewCourier = () => {
if (!newCourier.value.id || !newCourier.value.name || !newCourier.value.phone) {
ElMessage.warning('请完整填写信息')
return
}
//
courierData.value.push({ ...newCourier.value })
ElMessage.success('快递员添加成功')
addCourierDialogVisible.value = false //
}
//
const paginatedUsers = computed(() => {
const start = (userCurrentPage.value - 1) * userPageSize
return userData.value.slice(start, start + userPageSize)
})
const paginatedCouriers = computed(() => {
const start = (courierCurrentPage.value - 1) * courierPageSize
return courierData.value.slice(start, start + courierPageSize)
})
//
const handleEditUser = (user: any) => {
currentUser.value = { ...user }
editUserDialogVisible.value = true
}
//
const handleEditCourier = (courier: any) => {
currentCourier.value = { ...courier }
editCourierDialogVisible.value = true
}
//
const handleDeleteUser = (user: any) => {
ElMessageBox.confirm(
'确定删除该用户吗?',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
//
const index = userData.value.findIndex(u => u.id === user.id)
if (index !== -1) userData.value.splice(index, 1)
ElMessage.success('删除成功')
}).catch(() => {
ElMessage.info('删除已取消')
})
}
//
const handleDeleteCourier = (courier: any) => {
ElMessageBox.confirm(
'确定删除该快递员吗?',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
//
const index = courierData.value.findIndex(c => c.id === courier.id)
if (index !== -1) courierData.value.splice(index, 1)
ElMessage.success('删除成功')
}).catch(() => {
ElMessage.info('删除已取消')
})
}
//
const handleSaveUserEdit = () => {
const index = userData.value.findIndex(u => u.id === currentUser.value.id)
if (index !== -1) {
userData.value[index] = { ...currentUser.value }
ElMessage.success('用户信息已更新')
}
editUserDialogVisible.value = false
}
//
const handleSaveCourierEdit = () => {
const index = courierData.value.findIndex(c => c.id === currentCourier.value.id)
if (index !== -1) {
courierData.value[index] = { ...currentCourier.value }
ElMessage.success('快递员信息已更新')
}
editCourierDialogVisible.value = false
}
//
const handleUserPageChange = (page: number) => userCurrentPage.value = page
const handleCourierPageChange = (page: number) => courierCurrentPage.value = page
</script>
<style scoped>
.toolbar {
display: flex;
justify-content: flex-end;
/* 将内容对齐到右边 */
align-items: center;
width: 90%;
margin: 20px auto;
}
.user-search {
flex: 0 0 200px;
/* 搜索框的固定宽度 */
margin-right: 10px;
/* 可以根据需要调整这个值 */
}
/* 添加用户按钮 */
.add-user-button {
flex-shrink: 0;
}
.emain {
margin-top: 50px;
}
.management-container {
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.management-container h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.section {
margin-top: 30px;
}
.section h3 {
font-size: 20px;
color: #666;
margin-bottom: 15px;
text-align: center;
}
.el-table {
margin-top: 20px;
}
.button-group {
display: flex;
justify-content: space-evenly;
}
.el-button {
min-width: 60px;
font-size: 12px;
padding: 4px 8px;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: center;
}
.el-table-column {
text-align: center;
}
.el-table__header-wrapper th {
text-align: center;
}
.action-buttons {
text-align: right;
margin-top: 20px;
margin-right: 100px;
margin-bottom: 20px;
}
</style>

@ -0,0 +1,268 @@
<template>
<el-container>
<el-header>
<backIndex />
</el-header>
<el-container>
<el-main class="emain">
<div class="problem-container">
<!-- 用户问题表格 -->
<div class="section">
<h3>用户问题</h3>
<el-table :data="paginatedUserProblems" stripe style="width: 90%; margin: 0 auto;" border>
<el-table-column prop="id" label="ID" align="center"></el-table-column>
<el-table-column prop="username" label="用户名" align="center"></el-table-column>
<el-table-column prop="title" label="问题标题" align="center" header-align="center"></el-table-column>
<el-table-column prop="status" label="问题状态" align="center">
<template v-slot="scope">
<el-tag :type="scope.row.status === '已解决' ? 'success' : 'warning'" disable-transitions>
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="merchantStatus" label="商家回复" width="120" align="center">
<template v-slot="scope">
<el-tag :type="scope.row.merchantStatus === '已回复' ? 'success' : 'danger'" disable-transitions>
{{ scope.row.merchantStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center">
<template v-slot="scope">
<div class="button-group">
<el-button @click="handleResolve(scope.row)" size="mini" type="success" plain>解决</el-button>
<el-button @click="handleDelete(scope.row)" size="mini" type="danger" plain>删除</el-button>
<el-button @click="handleContact(scope.row)" size="mini" type="primary" plain>联系客户</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<el-pagination :current-page="userCurrentPage" :page-size="userPageSize" :total="userProblemData.length"
@current-change="handleUserPageChange" layout="prev, pager, next, jumper" class="pagination" />
</div>
<!-- 快递员问题表格 -->
<div class="section">
<h3>快递员问题</h3>
<el-table :data="paginatedCourierProblems" stripe style="width: 90%; margin: 0 auto;" border>
<el-table-column prop="id" label="ID" align="center"></el-table-column>
<el-table-column prop="courierName" label="快递员姓名" align="center"></el-table-column>
<el-table-column prop="title" label="问题标题" align="center" header-align="center"></el-table-column>
<el-table-column prop="status" label="问题状态" align="center">
<template v-slot="scope">
<el-tag :type="scope.row.status === '已解决' ? 'success' : 'warning'" disable-transitions>
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="companyReply" label="公司回复" width="120" align="center">
<template v-slot="scope">
<el-tag :type="scope.row.companyReply === '已回复' ? 'success' : 'danger'" disable-transitions>
{{ scope.row.companyReply }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center">
<template v-slot="scope">
<div class="button-group">
<el-button @click="handleResolve(scope.row)" size="mini" type="success" plain>解决</el-button>
<el-button @click="handleDelete(scope.row)" size="mini" type="danger" plain>删除</el-button>
<el-button @click="handleContactCourier(scope.row)" size="mini" type="primary"
plain>联系快递员</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<el-pagination :current-page="courierCurrentPage" :page-size="courierPageSize"
:total="courierProblemData.length" @current-change="handleCourierPageChange"
layout="prev, pager, next, jumper" class="pagination" />
</div>
</div>
</el-main>
</el-container>
</el-container>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
const userProblemData = ref([
{ id: 1, username: '张三', title: '快递丢失', status: '未解决', merchantStatus: '未回复' },
{ id: 2, username: '李四', title: '配送延迟', status: '已解决', merchantStatus: '已回复' },
{ id: 3, username: '王五', title: '包裹损坏', status: '未解决', merchantStatus: '未回复' },
{ id: 4, username: '赵六', title: '地址错误', status: '未解决', merchantStatus: '未回复' },
{ id: 5, username: '孙七', title: '包装破损', status: '已解决', merchantStatus: '已回复' },
{ id: 6, username: '周八', title: '收件人地址错误', status: '未解决', merchantStatus: '未回复' },
{ id: 7, username: '吴九', title: '物品破损', status: '未解决', merchantStatus: '未回复' },
{ id: 8, username: '郑十', title: '配送信息不符', status: '未解决', merchantStatus: '未回复' },
{ id: 9, username: '钱十一', title: '签收问题', status: '已解决', merchantStatus: '已回复' },
{ id: 10, username: '孙十二', title: '快递丢失', status: '未解决', merchantStatus: '未回复' },
{ id: 11, username: '李十三', title: '包裹迟迟未到', status: '未解决', merchantStatus: '未回复' },
{ id: 12, username: '王十四', title: '派送时效不符合', status: '已解决', merchantStatus: '已回复' },
{ id: 13, username: '赵十五', title: '包装破损', status: '未解决', merchantStatus: '未回复' },
{ id: 14, username: '孙十六', title: '快递迟到', status: '已解决', merchantStatus: '已回复' },
{ id: 15, username: '周十七', title: '配送路线问题', status: '未解决', merchantStatus: '未回复' },
{ id: 16, username: '吴十八', title: '物品丢失', status: '未解决', merchantStatus: '未回复' },
{ id: 17, username: '郑十九', title: '快递员态度差', status: '已解决', merchantStatus: '已回复' },
{ id: 18, username: '钱二十', title: '快递员迟到', status: '未解决', merchantStatus: '未回复' },
{ id: 19, username: '孙二十一', title: '商品损坏', status: '已解决', merchantStatus: '已回复' },
{ id: 20, username: '李二十二', title: '配送不及时', status: '未解决', merchantStatus: '未回复' },
{ id: 21, username: '王二十三', title: '包裹未送达', status: '未解决', merchantStatus: '未回复' },
{ id: 22, username: '赵二十四', title: '地址不清晰', status: '已解决', merchantStatus: '已回复' },
{ id: 23, username: '孙二十五', title: '收件地址错误', status: '未解决', merchantStatus: '未回复' },
{ id: 24, username: '周二十六', title: '派送延误', status: '已解决', merchantStatus: '已回复' },
{ id: 25, username: '吴二十七', title: '包裹破损', status: '未解决', merchantStatus: '未回复' },
{ id: 26, username: '郑二十八', title: '配送信息错误', status: '已解决', merchantStatus: '已回复' },
{ id: 27, username: '钱二十九', title: '包裹无法签收', status: '未解决', merchantStatus: '未回复' },
{ id: 28, username: '孙三十', title: '派送时效问题', status: '已解决', merchantStatus: '已回复' },
{ id: 29, username: '李三十一', title: '快递包裹丢失', status: '未解决', merchantStatus: '未回复' },
{ id: 30, username: '王三十二', title: '物流追踪问题', status: '已解决', merchantStatus: '已回复' }
])
const courierProblemData = ref([
{ id: 1, courierName: '小刘', title: '车辆故障', status: '未解决', companyReply: '未回复' },
{ id: 2, courierName: '小王', title: '路线不清', status: '已解决', companyReply: '已回复' },
{ id: 3, courierName: '小李', title: '客户未取件', status: '未解决', companyReply: '未回复' },
{ id: 4, courierName: '小张', title: '包裹丢失', status: '未解决', companyReply: '未回复' },
{ id: 5, courierName: '小赵', title: '快递迟到', status: '已解决', companyReply: '已回复' },
{ id: 3, courierName: '小李', title: '客户未取件', status: '未解决', companyReply: '未回复' },
{ id: 4, courierName: '小张', title: '包裹丢失', status: '未解决', companyReply: '未回复' },
{ id: 5, courierName: '小赵', title: '快递迟到', status: '已解决', companyReply: '已回复' },
{ id: 6, courierName: '小陈', title: '包裹损坏', status: '未解决', companyReply: '未回复' },
{ id: 7, courierName: '小黄', title: '快递错发', status: '已解决', companyReply: '已回复' },
{ id: 8, courierName: '小李', title: '包裹丢失', status: '未解决', companyReply: '未回复' },
{ id: 9, courierName: '小刘', title: '客户未取件', status: '已解决', companyReply: '已回复' },
{ id: 10, courierName: '小王', title: '快递迟到', status: '未解决', companyReply: '未回复' },
{ id: 11, courierName: '小张', title: '车辆故障', status: '已解决', companyReply: '已回复' },
{ id: 12, courierName: '小赵', title: '路线不清', status: '未解决', companyReply: '未回复' },
{ id: 13, courierName: '小陈', title: '包裹丢失', status: '已解决', companyReply: '已回复' },
{ id: 14, courierName: '小黄', title: '客户未取件', status: '未解决', companyReply: '未回复' },
{ id: 15, courierName: '小李', title: '快递错发', status: '已解决', companyReply: '已回复' },
{ id: 16, courierName: '小刘', title: '车辆故障', status: '未解决', companyReply: '未回复' },
{ id: 17, courierName: '小王', title: '快递迟到', status: '已解决', companyReply: '已回复' },
{ id: 18, courierName: '小张', title: '客户未取件', status: '未解决', companyReply: '未回复' },
{ id: 19, courierName: '小赵', title: '包裹丢失', status: '已解决', companyReply: '已回复' },
{ id: 20, courierName: '小陈', title: '快递错发', status: '未解决', companyReply: '未回复' },
{ id: 21, courierName: '小黄', title: '包裹损坏', status: '已解决', companyReply: '已回复' },
{ id: 22, courierName: '小李', title: '路线不清', status: '未解决', companyReply: '未回复' },
{ id: 23, courierName: '小刘', title: '快递迟到', status: '已解决', companyReply: '已回复' },
{ id: 24, courierName: '小王', title: '车辆故障', status: '未解决', companyReply: '未回复' },
{ id: 25, courierName: '小张', title: '快递错发', status: '已解决', companyReply: '已回复' },
//
])
const userPageSize = 10 //
const courierPageSize = 10 //
const userCurrentPage = ref(1) //
const courierCurrentPage = ref(1) //
//
const paginatedUserProblems = computed(() => {
const start = (userCurrentPage.value - 1) * userPageSize
return userProblemData.value.slice(start, start + userPageSize)
})
//
const paginatedCourierProblems = computed(() => {
const start = (courierCurrentPage.value - 1) * courierPageSize
return courierProblemData.value.slice(start, start + courierPageSize)
})
//
const handleUserPageChange = (page: number) => {
userCurrentPage.value = page
}
//
const handleCourierPageChange = (page: number) => {
courierCurrentPage.value = page
}
const handleResolve = (row: any) => {
console.log('解决问题', row)
row.status = '已解决'
}
const handleDelete = (row: any) => {
console.log('删除问题', row)
}
const handleContact = (row: any) => {
console.log('联系客户', row)
}
const handleContactCourier = (row: any) => {
console.log('联系快递员', row)
}
</script>
<style scoped>
.emain {
margin-top: 50px;
}
.problem-container {
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.problem-container h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.section {
margin-top: 40px;
}
.section h3 {
font-size: 20px;
color: #666;
margin-bottom: 15px;
text-align: center;
}
.el-table {
margin-top: 20px;
}
.button-group {
display: flex;
justify-content: space-evenly;
}
.el-button {
min-width: 60px;
font-size: 12px;
padding: 4px 8px;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: center;
/* 居中分页 */
}
.el-table-column {
text-align: center;
}
.el-table__header-wrapper th {
text-align: center;
}
</style>

@ -0,0 +1,199 @@
<template>
<el-container class="container">
<el-header>
<backIndex />
</el-header>
<el-container class="main-container">
<el-main class="content">
<div class="content-header"></div>
<el-row :gutter="70">
<el-col :xs="24" :sm="12">
<el-input v-model="searchQuery" placeholder="搜索商品名称以进行批量操作" suffix-icon="el-icon-search"
style="width: 130%;" />
</el-col>
<el-col :xs="24" :sm="12" class="batch-actions" style="margin-top: 10px;">
<el-button @click="handleBatchDelete" type="danger" size="small">批量删除</el-button>
<el-button @click="handleBatchStatusChange" type="warning" size="small">批量上架/下架</el-button>
</el-col>
</el-row>
<el-table :data="currentPageGoodsData" style="width: 100%; table-layout: auto">
<el-table-column prop="name" label="商品名称" width="150">
<template v-slot="scope">
<el-checkbox v-model="scope.row.selected" />
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column prop="id" label="商品ID" width="120" />
<el-table-column prop="category" label="所属分类" width="150" />
<el-table-column prop="price" label="商品价格" width="100" />
<el-table-column prop="audience" label="适用人群" width="120" />
<el-table-column prop="status" label="状态" width="120">
<template v-slot="scope">
<el-tag :type="scope.row.status === '已上架' ? 'success' : 'info'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="250">
<template v-slot="scope">
<el-button @click="editItem(scope.row)" type="primary" size="small">编辑</el-button>
<el-button @click="deleteItem(scope.row)" type="danger" size="small">删除</el-button>
<el-button @click="toggleStatus(scope.row)" :type="scope.row.status === '已上架' ? 'warning' : 'success'"
size="small">
{{ scope.row.status === '已上架' ? '下架' : '上架' }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination class="pagination" :current-page="currentPage" :page-size="pageSize"
:total="filteredGoodsData.length" @current-change="handlePageChange"
layout="total, prev, pager, next, jumper" />
</el-main>
</el-container>
<el-footer>
<Footer />
</el-footer>
<!-- 弹窗 -->
<!-- 弹窗 -->
<el-dialog :visible.sync="deleteDialogVisible" title="确认删除">
<p>您确定要删除商品 "{{ itemToDelete ? itemToDelete.name : '' }}" </p>
<span slot="footer" class="dialog-footer">
<el-button @click="cancelDelete"></el-button>
<el-button type="danger" @click="confirmDelete"></el-button>
</span>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
const searchQuery = ref('');
const currentPage = ref(1);
const pageSize = ref(11);
const itemToDeleteName = computed(() => itemToDelete.value?.name || '');
const deleteDialogVisible = ref(false);
const editForm = ref<any>({});
const itemToDelete = ref<any>(null); //
const deleteItem = (item: any) => {
itemToDelete.value = item; //
deleteDialogVisible.value = true; //
};
const cancelDelete = () => {
itemToDelete.value = null; //
deleteDialogVisible.value = false; //
};
const confirmDelete = () => {
if (itemToDelete.value) {
goodsData.value = goodsData.value.filter(item => item.id !== itemToDelete.value.id);
ElMessage.success(`商品 "${itemToDelete.value.name}" 删除成功`);
itemToDelete.value = null; //
}
deleteDialogVisible.value = false; //
};
const goodsData = ref([
{ name: '商品1', id: '001', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '002', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '003', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '004', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '005', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '006', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '007', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '008', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '009', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '010', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '011', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '012', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
{ name: '商品1', id: '013', category: '电子产品', price: '100', audience: '成年人', status: '已下架', selected: false },
{ name: '商品2', id: '014', category: '家居用品', price: '50', audience: '家庭', status: '已上架', selected: false },
]);
const filteredGoodsData = computed(() => {
if (!searchQuery.value) return goodsData.value;
return goodsData.value.filter(item => item.name.includes(searchQuery.value));
});
const currentPageGoodsData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return filteredGoodsData.value.slice(start, end);
});
const handlePageChange = (page: number) => {
currentPage.value = page;
};
const handleBatchDelete = () => {
const selectedItems = goodsData.value.filter(item => item.selected);
if (selectedItems.length === 0) {
return ElMessage.warning('请先选择要删除的商品');
}
deleteDialogVisible.value = true;
};
const confirmBatchDelete = () => {
goodsData.value = goodsData.value.filter(item => !item.selected);
deleteDialogVisible.value = false;
ElMessage.success('删除成功');
};
const handleBatchStatusChange = () => {
const selectedItems = goodsData.value.filter(item => item.selected);
if (selectedItems.length === 0) {
return ElMessage.warning('请先选择要更改状态的商品');
}
selectedItems.forEach(item => {
item.status = item.status === '已上架' ? '已下架' : '已上架';
});
ElMessage.success('状态修改成功');
};
const editItem = (item: any) => {
editForm.value = { ...item };
};
const toggleStatus = (item: any) => {
item.status = item.status === '已上架' ? '已下架' : '已上架';
};
</script>
<style scoped>
.main-container {
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 50px;
height: 100vh;
box-sizing: border-box;
}
.pagination {
margin: 20px auto;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 1200px;
margin: 0 auto;
overflow-x: auto;
}
</style>

@ -0,0 +1,226 @@
<template>
<el-container style="height: 100vh;">
<!-- 顶部标题 -->
<el-header style="background-color: #A8B9C9; color: #F2F8FE; text-align: center; font-size: 24px; padding: 20px;">
快递员中心
</el-header>
<el-container>
<!-- 左侧菜单栏 -->
<el-aside width="250px" style="background-color: #f4f6f9;">
<el-menu :default-active="currentTab" class="el-menu-vertical-demo" @select="handleTabChange">
<el-menu-item index="toDeliver">待配送订单</el-menu-item>
<el-menu-item index="history">历史订单</el-menu-item>
</el-menu>
</el-aside>
<!-- 主内容区域 -->
<el-main style="padding: 20px;">
<!-- 待配送订单 -->
<el-row v-if="currentTab === 'toDeliver'" class="order-card">
<h3>待配送订单</h3>
<div class="button-container">
<el-button type="primary" @click="openAddOrderDialog" class="btn">新增订单</el-button>
</div>
<el-table :data="toDeliverOrders" border stripe>
<el-table-column prop="id" label="订单编号" width="180" />
<el-table-column prop="receiver" label="收件人" width="180" />
<el-table-column prop="address" label="地址" />
<el-table-column prop="status" label="状态" width="120" />
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button v-if="row.status === ''" type="primary" size="small"
@click="updateStatus(row, '配送中')">开始配送</el-button>
<el-button v-if="row.status === ''" type="success" size="small"
@click="updateStatus(row, '已完成')">完成配送</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
<!-- 历史订单 -->
<el-row v-if="currentTab === 'history'" class="order-card">
<h3>历史订单</h3>
<el-table :data="historyOrders" border stripe>
<el-table-column prop="id" label="订单编号" width="180" />
<el-table-column prop="receiver" label="收件人" width="180" />
<el-table-column prop="address" label="地址" />
<el-table-column prop="status" label="状态" width="120" />
<el-table-column prop="deliveryTime" label="配送时间" width="180" />
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button type="danger" size="small" @click="deleteHistoryOrder(row)"></el-button>
</template>
</el-table-column>
</el-table>
</el-row>
</el-main>
<!-- 新增订单对话框 -->
<el-dialog title="新增订单" v-model="addOrderDialogVisible" width="400px" align-center>
<el-form :model="newOrder" ref="newOrderForm" label-width="100px">
<el-form-item label="订单编号">
<el-input v-model="newOrder.id" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="收件人">
<el-input v-model="newOrder.receiver" placeholder="请输入收件人" />
</el-form-item>
<el-form-item label="地址">
<el-input v-model="newOrder.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="newOrder.status" placeholder="请选择状态">
<el-option label="待取件" value="待取件" />
<el-option label="配送中" value="配送中" />
<el-option label="已完成" value="已完成" />
</el-select>
</el-form-item>
</el-form>
<el-row slot="footer" class="dialog-footer">
<el-button @click="addOrderDialogVisible = false">取消</el-button>
<el-button type="primary" @click="addOrder"></el-button>
</el-row>
</el-dialog>
</el-container>
</el-container>
</template>
<script setup>
import {
ref
} from 'vue';
// State
const currentTab = ref('toDeliver');
const addOrderDialogVisible = ref(false); //
const newOrder = ref({
id: '',
receiver: '',
address: '',
status: '待取件'
});
const toDeliverOrders = ref([{
id: '001',
receiver: '李四',
address: '北京市朝阳区',
status: '待取件'
},
{
id: '002',
receiver: '王五',
address: '北京市海淀区',
status: '待取件'
},
{
id: '003',
receiver: '赵六',
address: '北京市昌平区',
status: '配送中'
}
]);
const historyOrders = ref([{
id: '004',
receiver: '孙七',
address: '北京市东城区',
status: '已完成',
deliveryTime: '2024-10-10 14:00'
},
{
id: '005',
receiver: '周八',
address: '北京市西城区',
status: '已完成',
deliveryTime: '2024-10-12 15:30'
}
]);
// Methods
const updateStatus = (order, newStatus) => {
const orderIndex = toDeliverOrders.value.findIndex(o => o.id === order.id);
if (orderIndex !== -1) {
toDeliverOrders.value[orderIndex].status = newStatus;
if (newStatus === '已完成') {
historyOrders.value.push({
...toDeliverOrders.value[orderIndex],
deliveryTime: new Date().toLocaleString()
});
toDeliverOrders.value.splice(orderIndex, 1);
}
}
};
const deleteHistoryOrder = (order) => {
const index = historyOrders.value.findIndex(o => o.id === order.id);
if (index !== -1) {
historyOrders.value.splice(index, 1);
}
};
const handleTabChange = (index) => {
currentTab.value = index;
};
const openAddOrderDialog = () => {
addOrderDialogVisible.value = true;
};
const addOrder = () => {
if (newOrder.value.id && newOrder.value.receiver && newOrder.value.address) {
toDeliverOrders.value.push({
...newOrder.value
});
newOrder.value = {
id: '',
receiver: '',
address: '',
status: '待取件'
}; //
addOrderDialogVisible.value = false; //
} else {
alert('请填写完整订单信息');
}
};
</script>
<style scoped>
.btn {
margin-left: 1200px;
}
.order-card {
margin-top: 20px;
}
.el-table th {
background-color: #f2f3f5;
text-align: center;
}
.el-table td {
text-align: center;
}
.el-menu-vertical-demo {
margin-top: 20px;
}
.el-menu-item {
font-size: 16px;
}
.el-button {
margin-top: 5px;
}
.dialog-footer {
text-align: right;
}
.button-container {
text-align: right;
margin-bottom: 10px;
}
</style>

@ -0,0 +1,354 @@
<template>
<el-container>
<!-- 导航栏 -->
<el-header>
<Header />
</el-header>
<el-main>
<div class="container">
<!-- 标题输入框和按钮 -->
<div class="top-section">
<h1 class="title">运单查询</h1>
<div class="input-group">
<el-input
v-model="form.trackingNumber"
placeholder="请输入快递单号"
clearable
style="width: 300px; margin-right: 20px;"
@input="validateTrackingNumber"
@keyup.enter="searchPackage"
></el-input>
<el-button @click="searchPackage" type="primary" :loading="loading">查询</el-button>
</div>
</div>
<!-- 选项卡 -->
<el-tabs v-model="activeTab" type="card" style="margin-top: 20px;" @tab-click="handleTabClick">
<el-tab-pane label="我寄的" name="sent">
<el-table v-if="sentPackages.length" :data="sentPackages" style="margin-top: 20px;">
<el-table-column label="快递单号" prop="trackingNumber"></el-table-column>
<el-table-column label="包裹名称" prop="packageName"></el-table-column>
<el-table-column label="寄件人" prop="sender"></el-table-column>
<el-table-column label="寄件人地址" prop="senderaddress"></el-table-column>
<el-table-column label="收件人" prop="receiver"></el-table-column>
<el-table-column label="收件人地址" prop="receiveraddress"></el-table-column>
<el-table-column label="状态" prop="status">
<template #default="scope">
<span :class="getStatusClass(scope.row.status)">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column label="最新位置" prop="latestLocation"></el-table-column>
</el-table>
<p v-else></p>
</el-tab-pane>
<el-tab-pane label="我收的" name="received">
<el-table v-if="receivedPackages.length" :data="receivedPackages" style="margin-top: 20px;">
<el-table-column label="快递单号" prop="trackingNumber"></el-table-column>
<el-table-column label="包裹名称" prop="packageName"></el-table-column>
<el-table-column label="寄件人<E4BBB6><E4BABA><EFBFBD>址" prop="senderaddress"></el-table-column>
<el-table-column label="寄件人" prop="sender"></el-table-column>
<el-table-column label="收件人地址" prop="receiveraddress"></el-table-column>
<el-table-column label="收件人" prop="receiver"></el-table-column>
<el-table-column label="状态" prop="status">
<template #default="scope">
<span :class="getStatusClass(scope.row.status)">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column label="最新位置" prop="latestLocation"></el-table-column>
</el-table>
<p v-else></p>
</el-tab-pane>
</el-tabs>
</div>
</el-main>
<el-footer>
<Footer />
</el-footer>
</el-container>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { trackingAPI } from '@/api'
import type { Package } from '@/types/api'
//
interface Package {
trackingNumber: string;
packageName: string;
sender: string;
senderaddress: string;
receiver: string;
receiveraddress: string;
status: string;
latestLocation: string;
}
const form = ref({
trackingNumber: ''
})
const activeTab = ref('sent')
const sentPackages = ref<Package[]>([])
const receivedPackages = ref<Package[]>([])
const loading = ref(false)
const error = ref('')
//
const validateTrackingNumber = () => {
if (form.value.trackingNumber && !/^\d+$/.test(form.value.trackingNumber)) {
error.value = '快递单号只能包含数字'
} else {
error.value = ''
}
}
// ""
const initSentPackages = async () => {
try {
const response = await trackingAPI.getSentPackages()
sentPackages.value = response.data
} catch (error) {
ElMessage.error('获取寄出包裹列表失败')
}
}
//
const handleTabClick = async (tab: any) => {
try {
if (tab.paneName === 'sent') {
const response = await trackingAPI.getSentPackages()
sentPackages.value = response.data
activeTab.value = 'sent'
} else if (tab.paneName === 'received') {
const response = await trackingAPI.getReceivedPackages()
receivedPackages.value = response.data
activeTab.value = 'received'
}
} catch (error) {
ElMessage.error('获取包裹列表失败')
}
}
//
const searchPackage = async () => {
if (!form.value.trackingNumber) {
ElMessage.error('请输入快递单号')
return
}
if (error.value) {
ElMessageBox.alert(error.value, '错误', {
confirmButtonText: '确定',
type: 'error',
})
return
}
loading.value = true
try {
const response = await trackingAPI.getPackageInfo(form.value.trackingNumber)
const result = response.data
if (result) {
ElMessage.success('查询成功')
if (['123', '456', '789'].includes(result.trackingNumber)) {
activeTab.value = 'sent'
sentPackages.value = [result]
receivedPackages.value = []
} else if (['011', '012', '013'].includes(result.trackingNumber)) {
activeTab.value = 'received'
receivedPackages.value = [result]
sentPackages.value = []
}
} else {
ElMessageBox.alert('未找到该快递单号的信息,请确认单号是否正确。', '错误', {
confirmButtonText: '确定',
type: 'error',
})
}
} catch (error) {
ElMessage.error('查询失败')
} finally {
loading.value = false
}
}
//
const getStatusClass = (status: string) => {
switch (status) {
case '已签收':
return 'status-success';
case '正在运输':
return 'status-warning';
default:
return 'status-fail';
}
}
//
onMounted(() => {
initSentPackages();
})
</script>
<style scoped>
/* 将容器设置为 flex 布局,居中对齐 */
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start; /* 从顶部开始排列 */
padding: 20px;
/* background: #f5f5f5; */
}
/* 标题样式 */
.title {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
text-align: center;
margin-top: 0px; /* 为标题留出导航栏的空间 */
}
/* 顶部区域样式 */
.top-section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
max-width: 600px;
}
/* 输入框和按钮容器 */
.input-group {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.input-group .el-input {
margin-right: 20px;
width: 300px;
}
.el-button {
padding: 10px 20px;
font-size: 16px;
}
/* 表格样式 */
.el-table {
width: 100%;
max-width: 1000px; /* 增加表格的最大宽度 */
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
overflow: hidden;
}
.el-table th,
.el-table td {
text-align: center;
padding: 12px 0;
}
.el-table th {
background: #f0f0f0;
font-weight: bold;
}
.el-table tr:nth-child(even) {
background: #f9f9f9;
}
.el-table tr:hover {
background: #e0e0e0;
}
/* 状态颜色样式 */
.status-success {
color: green;
}
.status-fail {
color: rgb(255, 0, 0);
}
.status-warning {
color: orange;
}
/* 选项卡样式 */
.el-tabs {
width: 100%;
max-width: 1000px; /* 增加选项卡的最大宽度 */
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
overflow: hidden;
}
.el-tabs__header {
margin: 0;
padding: 0 20px;
background: #f0f0f0;
border-bottom: 1px solid #ddd;
}
.el-tabs__nav-wrap::after {
display: none;
}
.el-tabs__item {
padding: 0 20px;
height: 40px;
line-height: 40px;
font-weight: bold;
color: #333;
transition: background 0.3s, color 0.3s, border-bottom 0.3s;
border-bottom: 2px solid transparent; /* 默认情况下没有下划线 */
}
.el-tabs__item.is-active {
background: #fff;
color: #409eff;
border-bottom: 2px solid red !important; /* 激活状态下有红色下划线 */
}
.el-tabs__item:hover {
background: #e0e0e0;
color: #409eff;
border-bottom: 2px solid #f60d0d !important; /* 悬停时有下划线 */
}
/* Element UI 容器样式 */
.el-header {
padding: 0;
height: 100;
}
.el-main {
margin-top: 80px;
padding: 20px 0;
overflow: hidden;
height: calc(100vh - 200px); /* 调整高度以适应屏幕 */
}
.el-footer {
margin-top: 0px;
padding: 20px 0;
/* background: #333; */
color: #fff;
text-align: center;
}
</style>

@ -0,0 +1,132 @@
<template>
<el-container>
<el-header>
<Header />
</el-header>
<el-container>
<el-main>
<div class="complaint-container">
<h2>快递投诉</h2>
<el-form
:model="complaintForm"
:rules="rules"
ref="complaintFormRef"
label-width="100px"
class="complaint-form"
>
<el-form-item label="订单号" prop="orderNumber">
<el-input v-model="complaintForm.orderNumber" placeholder="请输入订单号" />
</el-form-item>
<el-form-item label="投诉原因" prop="reason">
<el-select v-model="complaintForm.reason" placeholder="请选择投诉原因">
<el-option label="快递未按时送达" value="未按时送达" />
<el-option label="快递损坏或丢失" value="损坏或丢失" />
<el-option label="服务态度差" value="服务态度差" />
<el-option label="其他问题" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="详细描述" prop="description">
<el-input
type="textarea"
v-model="complaintForm.description"
placeholder="请详细描述您的问题"
rows="4"
/>
</el-form-item>
<el-form-item label="联系方式" prop="contact">
<el-input v-model="complaintForm.contact" placeholder="请输入您的联系方式" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitComplaint"></el-button>
<el-button @click="resetForm"></el-button>
</el-form-item>
</el-form>
</div>
</el-main>
</el-container>
<el-footer>
<Footer />
</el-footer>
</el-container>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { complaintAPI } from '@/api';
import type { ComplaintForm } from '@/types/api';
const complaintForm = ref<ComplaintForm>({
orderNumber: '',
reason: '',
description: '',
contact: '',
});
const rules = {
orderNumber: [
{ required: true, message: '订单号不能为空', trigger: 'blur' },
],
reason: [
{ required: true, message: '请选择投诉原因', trigger: 'change' },
],
description: [
{ required: true, message: '请详细描述问题', trigger: 'blur' },
{ min: 10, message: '描述至少需要10个字', trigger: 'blur' },
],
contact: [
{ required: true, message: '联系方式不能为空', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入有效的手机号码',
trigger: 'blur',
},
],
};
const complaintFormRef = ref();
const submitComplaint = async () => {
complaintFormRef.value?.validate(async (valid) => {
if (valid) {
try {
await complaintAPI.submitComplaint(complaintForm.value);
ElMessage.success('投诉已提交,我们将尽快处理您的问题!');
resetForm();
} catch (error) {
ElMessage.error('提交失败,请稍后重试');
}
} else {
ElMessage.error('请检查表单内容是否填写完整!');
}
});
};
const resetForm = () => {
complaintFormRef.value?.resetFields();
};
</script>
<style scoped>
.complaint-container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
.complaint-form {
width: 100%;
}
</style>

@ -0,0 +1,280 @@
<template>
<el-header>
<Header />
</el-header>
<div class="delivery-container">
<el-row class="send-container">
<el-row class="panel send-panel send-panel1">
<el-row class="send-container">
<el-button class="send-btn"></el-button>
<el-row>
<el-button @click="openAddressBook">簿</el-button>
<el-button>智能填写</el-button>
</el-row>
</el-row>
<el-form ref="senderFormRef" label-width="auto" class="form" :model="form.sender" :rules="rules.sender">
<el-form-item label="姓名" prop="senderName">
<el-input v-model="form.sender.senderName" placeholder="请填写发件人姓名"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="senderPhone">
<el-input v-model="form.sender.senderPhone" placeholder="请填写发件人联系电话"></el-input>
</el-form-item>
<el-form-item label="省市区" prop="senderProvince">
<el-cascader
:options="options"
v-model="form.sender.senderProvince"
:props="{ value: 'value', label: 'label', children: 'children' }"
placeholder="请选择省市区"
/>
</el-form-item>
<el-form-item label="详细地址" prop="senderAddress">
<el-input v-model="form.sender.senderAddress" placeholder="请填写详细地址"></el-input>
</el-form-item>
<el-form-item label="公司名称" prop="senderCompany" class="no-asterisk">
<el-input v-model="form.sender.senderCompany" placeholder="请填写公司名称"></el-input>
</el-form-item>
</el-form>
</el-row>
<el-image src="src/assets/arrow.png"></el-image>
<el-row class="panel send-panel send-panel1">
<el-row class="send-container">
<el-button class="send-btn receiver-btn"></el-button>
<el-row>
<el-button @click="openAddressBook">簿</el-button>
<el-button>智能填写</el-button>
</el-row>
</el-row>
<el-form ref="receiverFormRef" label-width="auto" class="form" :model="form.receiver" :rules="rules.receiver">
<el-form-item label="姓名" prop="receiverName">
<el-input v-model="form.receiver.receiverName" placeholder="请填写收件人姓名"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="receiverPhone">
<el-input v-model="form.receiver.receiverPhone" placeholder="请填写收件人联系电话"></el-input>
</el-form-item>
<el-form-item label="省市区" prop="receiverProvince">
<el-cascader
:options="options"
v-model="form.receiver.receiverProvince"
:props="{ value: 'value', label: 'label', children: 'children' }"
placeholder="请选择省市区"
/>
</el-form-item>
<el-form-item label="详细地址" prop="receiverAddress">
<el-input v-model="form.receiver.receiverAddress" placeholder="请填写详细地址"></el-input>
</el-form-item>
<el-form-item label="公司名称" prop="receiverCompany" class="no-asterisk">
<el-input v-model="form.receiver.receiverCompany" placeholder="请填写公司名称"></el-input>
</el-form-item>
</el-form>
</el-row>
<el-row class="panel send-panel">
<el-text class="end-label">寄件方式</el-text>
<el-radio-group v-model="sendType">
<el-radio :value="0" size="large">预约上门取件</el-radio>
<el-radio :value="1" size="large">自行联系快递员</el-radio>
</el-radio-group>
</el-row>
<el-row class="panel send-panel">
<el-button class="confirm-btn" @click="submitForm"></el-button>
</el-row>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router'; //
import addressList from '../../assets/addressList.js';
import { ElMessage } from 'element-plus';
import { deliveryAPI } from '@/api';
import type { DeliveryForm } from '@/types/api';
const formatCityData = (arr) => {
return arr.map(item => ({
value: item.name,
label: item.name,
children: item.sub && item.sub.length > 0 ? formatCityData(item.sub) : []
}));
};
const options = ref(formatCityData(addressList));
const sendType = ref(0);
//
const router = useRouter();
const form = ref<DeliveryForm>({
sender: {
name: '',
phone: '',
province: [],
address: '',
company: ''
},
receiver: {
name: '',
phone: '',
province: [],
address: '',
company: ''
},
sendType: 0
});
const rules = ref({
receiver: {
receiverName: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
receiverPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
],
receiverProvince: [
{ required: true, message: '请输入省市区', trigger: 'blur' }
],
receiverAddress: [
{ required: true, message: '请输入详细地址', trigger: 'blur' }
],
receiverCompany: [
{ required: false, message: '请输入公司名称', trigger: 'blur' }
],
},
sender: {
senderName: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
senderPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
],
senderProvince: [
{ required: true, message: '请输入省市区', trigger: 'blur' }
],
senderAddress: [
{ required: true, message: '请输入详细地址', trigger: 'blur' }
],
senderCompany: [
{ required: false, message: '请输入公司名称', trigger: 'blur' }
],
}
});
const senderFormRef = ref(null);
const receiverFormRef = ref(null);
// 簿
const openAddressBook = async () => {
try {
const response = await deliveryAPI.getAddressBook();
// 簿...
router.push({ name: 'address' });
} catch (error) {
ElMessage.error('获取地址簿失败');
}
};
//
const submitForm = async () => {
if (senderFormRef.value && receiverFormRef.value) {
const validSender = await senderFormRef.value.validate();
const validReceiver = await receiverFormRef.value.validate();
if (validSender && validReceiver) {
try {
await deliveryAPI.submitDelivery(form.value);
ElMessage.success('下单成功!');
} catch (error) {
ElMessage.error('下单失败,请稍后重试');
}
}
}
};
</script>
<style>
.delivery-container {
padding: 20px;
margin: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
.send-container {
display: flex;
width: 70%;
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
align-items: center;
margin-left: auto;
margin-right: auto; /* 添加这一行 */
}
/* 卡片容器样式 */
.panel {
background-color: #f9f9f9; /* 淡雅的背景颜色 */
border-radius: 8px; /* 圆角边框 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
padding: 30px;
margin-top: 30px;
margin-bottom: 20px;
width: calc(50% - 20px); /* 设置宽度为容器宽度的一半减去间距 */
}
/* 调整第二个卡片的样式 */
.send-panel1 {
width: calc(50% - 20px); /* 设置宽度为容器宽度的一半减去间距 */
align-items: center;
}
/* 按钮样式 */
.send-btn {
background-color: #333; /* 深灰色背景 */
color: white;
border-radius: 50%;
padding: 10px;
margin-right: 10px; /* 添加右边距 */
}
.receiver-btn {
background-color: #d9534f; /* 红色背景 */
}
/* 表单样式 */
.form {
margin-top: 10px;
width: 70%; /* 表单宽度调整为100% */
}
/* 确认按钮样式 */
.confirm-btn {
background-color: #428bca; /* 蓝色背景 */
color: white;
padding: 10px 20px; /* 添加内边距 */
border: none; /* 去除边框 */
border-radius: 5px; /* 圆角边框 */
margin-top: 5px;
margin-bottom: 5px;
cursor: pointer; /* 鼠标悬停时显示指针 */
}
/* 标签样式 */
.el-form-item__label {
color: #333; /* 标签文字颜色 */
font-weight: 500; /* 字体加粗 */
}
.end-label {
font-weight: 600;
align-self: flex-start;
margin-right: 40px;
margin-top: 10px;
}
.el-form-item__label::before {
content: '*';
color: red;
margin-right: 4px;
}
.el-form-item.no-asterisk .el-form-item__label::before {
content: none;
}
</style>

@ -0,0 +1,129 @@
<template>
<el-container>
<el-header>
<Header />
</el-header>
<el-container>
<el-main>
<div class="feedback-container">
<h2>我有建议</h2>
<p class="feedback-description">
如果您对我们的服务或功能有任何建议请告诉我们我们会认真听取并改进
</p>
<el-form
:model="feedbackForm"
:rules="rules"
ref="feedbackFormRef"
label-width="100px"
class="feedback-form"
>
<el-form-item label="标题" prop="title">
<el-input
v-model="feedbackForm.title"
placeholder="请输入您的建议标题"
/>
</el-form-item>
<el-form-item label="建议内容" prop="content">
<el-input
type="textarea"
v-model="feedbackForm.content"
placeholder="请输入详细建议内容"
rows="5"
/>
</el-form-item>
<el-form-item label="联系方式" prop="contact">
<el-input
v-model="feedbackForm.contact"
placeholder="请输入您的联系方式(可选)"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitFeedback">
提交建议
</el-button>
<el-button @click="resetForm"></el-button>
</el-form-item>
</el-form>
</div>
</el-main>
</el-container>
<el-footer>
<Footer />
</el-footer>
</el-container>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { feedbackAPI } from '@/api';
import type { FeedbackForm } from '@/types/api';
const feedbackForm = ref<FeedbackForm>({
title: '',
content: '',
contact: '',
});
const rules = {
title: [
{ required: true, message: '标题不能为空', trigger: 'blur' },
{ min: 3, message: '标题至少需要3个字符', trigger: 'blur' },
],
content: [
{ required: true, message: '建议内容不能为空', trigger: 'blur' },
{ min: 10, message: '内容至少需要10个字符', trigger: 'blur' },
],
};
const feedbackFormRef = ref();
const submitFeedback = async () => {
feedbackFormRef.value?.validate(async (valid) => {
if (valid) {
try {
await feedbackAPI.submitFeedback(feedbackForm.value);
ElMessage.success('感谢您的建议!我们会认真参考并改进。');
resetForm();
} catch (error) {
ElMessage.error('提交失败,请稍后重试');
}
} else {
ElMessage.error('请填写完整的建议内容!');
}
});
};
const resetForm = () => {
feedbackFormRef.value?.resetFields();
};
</script>
<style scoped>
.feedback-container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
}
h2 {
text-align: center;
margin-bottom: 10px;
}
.feedback-description {
text-align: center;
margin-bottom: 20px;
color: #666;
}
.feedback-form {
width: 100%;
}
</style>

@ -0,0 +1,272 @@
<template>
<el-header>
<Header />
</el-header>
<div class="profile-list-container">
<div class="profile-content">
<!-- 用户资料表单 -->
<el-card class="profile-card" shadow="hover">
<div class="profile-header">
<!-- 头像 -->
<el-avatar
style="width: 80px; height: 80px;"
:src="avatarUrl"
class="avatar"
@click="changeAvatar"
/>
<div class="profile-info">
<el-form ref="profileForm" :model="profileInfo" label-width="80px">
<el-form-item label="用户名">
<el-input v-model="profileInfo.name" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="profileInfo.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<el-button type="danger" @click="logout">退</el-button>
<!-- 用户详细信息表单 -->
<div class="profile-details">
<el-form ref="profileDetailsForm" :model="profileInfo" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="手机号">
<el-input v-model="profileInfo.phone" placeholder="请输入手机号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别">
<el-select v-model="profileInfo.gender" placeholder="请选择性别">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="出生日期">
<el-date-picker v-model="profileInfo.birthday" type="date" placeholder="选择日期"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="地址">
<el-input v-model="profileInfo.address" placeholder="请输入地址"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
<!-- 地址管理表单 -->
<el-card class="address-card" shadow="hover">
<h3>地址管理</h3>
<el-button type="primary" @click="addAddress"></el-button>
<!-- 地址表单 -->
<el-form :model="addresses" label-width="80px">
<div v-for="(address, index) in addresses" :key="address.id" class="address-item">
<el-form-item label="收货人" :prop="'name' + index">
<el-input v-model="address.name" placeholder="请输入收货人姓名"></el-input>
</el-form-item>
<el-form-item label="地址" :prop="'address' + index">
<el-input v-model="address.address" placeholder="请输入收货地址"></el-input>
</el-form-item>
<el-form-item label="电话" :prop="'phone' + index">
<el-input v-model="address.phone" placeholder="请输入联系电话"></el-input>
</el-form-item>
<el-form-item>
<el-button type="text" @click="editAddress(index)"></el-button>
<el-button type="text" @click="deleteAddress(address.id)"></el-button>
<el-button type="text" @click="copyAddress(address)"></el-button>
</el-form-item>
</div>
</el-form>
</el-card>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { profileAPI } from '@/api'
import type { UserProfile, Address } from '@/types/api'
const profileInfo = ref<UserProfile>({
name: '',
email: '',
phone: '',
gender: '',
birthday: '',
address: '',
})
const addresses = ref<Address[]>([])
//
const getUserInfo = async () => {
try {
const response = await profileAPI.getUserProfile()
profileInfo.value = response.data
} catch (error) {
ElMessage.error('获取用户信息失败')
}
}
//
const changeAvatar = async (event: Event) => {
const file = (event.target as HTMLInputElement).files?.[0]
if (file) {
const formData = new FormData()
formData.append('avatar', file)
try {
await profileAPI.updateAvatar(formData)
ElMessage.success('头像更新成功')
} catch (error) {
ElMessage.error('头像更新失败')
}
}
}
//
const getAddresses = async () => {
try {
const response = await profileAPI.getAddresses()
addresses.value = response.data
} catch (error) {
ElMessage.error('获取地址列表失败')
}
}
//
const addAddress = async () => {
const newAddress = { name: '', address: '', phone: '' }
try {
const response = await profileAPI.addAddress(newAddress)
addresses.value.push(response.data)
ElMessage.success('地址添加成功')
} catch (error) {
ElMessage.error('地址添加失败')
}
}
//
const editAddress = async (index: number) => {
try {
const address = addresses.value[index]
await profileAPI.updateAddress(address.id, address)
ElMessage.success('地址更新成功')
} catch (error) {
ElMessage.error('地址更新失败')
}
}
//
const deleteAddress = async (id: number) => {
try {
await profileAPI.deleteAddress(id)
addresses.value = addresses.value.filter(addr => addr.id !== id)
ElMessage.success('地址删除成功')
} catch (error) {
ElMessage.error('地址删除失败')
}
}
onMounted(() => {
getUserInfo()
getAddresses()
})
// ... ...
</script>
<style scoped>
.profile-list-container {
position: fixed;
top: 60px;
right: 0;
width: calc(100% - 220px);
height: calc(100vh - 60px);
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
.profile-content {
max-width: 1000px;
margin: 20px auto;
}
.profile-card {
margin-bottom: 20px;
background-color: #fff;
border-radius: 8px;
}
.profile-header {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.avatar {
margin-right: 20px;
cursor: pointer;
}
.profile-info {
flex-grow: 1;
}
.profile-details {
margin-top: 20px;
}
.address-card {
background-color: #fff;
border-radius: 8px;
margin-top: 20px;
}
.address-item {
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.address-item:last-child {
border-bottom: none;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.profile-list-container {
width: calc(100% - 64px);
}
.profile-content {
padding: 10px;
}
.el-form-item {
margin-bottom: 15px;
}
}
/* Element Plus 样式覆盖 */
:deep(.el-card__body) {
padding: 20px;
}
:deep(.el-button--text) {
padding: 0 10px;
}
:deep(.el-form-item__label) {
font-weight: 500;
}
:deep(.el-input__inner) {
border-radius: 4px;
}
</style>

@ -0,0 +1,215 @@
<template>
<el-header>
<Header />
</el-header>
<div class="service-list-container">
<div class="service-content">
<ul class="service-list">
<li>
<el-image src="src/assets/time.png"></el-image>
<div>
<p>运费时效</p>
<p>查预估费用和预计送达时间</p>
</div>
</li>
<li>
<el-image src="src/assets/serviceSpot.png"></el-image>
<div>
<p>服务网点</p>
<p>查各地网点丰巢柜合作点</p>
</div>
</li>
<li>
<el-image src="src/assets/require.png"></el-image>
<div>
<p>收寄标准</p>
<p>查物品能不能寄</p>
</div>
</li>
<li>
<el-image src="src/assets/serviceRange.png"></el-image>
<div>
<p>服务范围</p>
<p>查地址能不能收派</p>
</div>
</li>
</ul>
<ul class="service-list">
<li>
<el-icon><DataLine /></el-icon>
<div>
<p>汇率查询</p>
</div>
</li>
<li>
<el-icon><Document /></el-icon>
<div>
<p>常用表格</p>
</div>
</li>
<li>
<el-icon><SetUp /></el-icon>
<div>
<p>清关服务</p>
</div>
</li>
<li>
<el-icon><DataBoard /></el-icon>
<div>
<p>无着件公示</p>
</div>
</li>
<li>
<el-icon><HomeFilled /></el-icon>
<div>
<p>特殊入仓地址查询</p>
</div>
</li>
<li>
<el-icon><Download /></el-icon>
<div>
<p>电子回单下载</p>
</div>
</li>
</ul>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
DataLine,
Document,
SetUp,
DataBoard,
HomeFilled,
Download
} from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus'
import { serviceAPI } from '@/api'
//
const checkDeliveryTime = async (from: string, to: string) => {
try {
const response = await serviceAPI.getDeliveryTime(from, to)
// ...
} catch (error) {
ElMessage.error('查询失败')
}
}
//
const checkServicePoints = async (location: string) => {
try {
const response = await serviceAPI.getServicePoints(location)
// ...
} catch (error) {
ElMessage.error('查询失败')
}
}
//
const checkDeliveryStandards = async () => {
try {
const response = await serviceAPI.getDeliveryStandards()
// ...
} catch (error) {
ElMessage.error('查询失败')
}
}
//
const checkServiceRange = async (address: string) => {
try {
const response = await serviceAPI.getServiceRange(address)
// ...
} catch (error) {
ElMessage.error('查询失败')
}
}
</script>
<style scoped>
.service-list-container {
position: fixed;
top: 60px; /* header 高度 */
right: 0;
width: calc(100% - 220px); /* 减去侧边栏宽度 */
height: calc(100vh - 60px);
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
.service-content {
max-width: 1200px;
margin: 20px auto;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
padding: 20px;
}
.service-list {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin-bottom: 20px;
}
.service-list li {
flex: 0 0 200px;
border: 1px solid #eaeaea;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s;
background-color: #f9f9f9;
}
.service-list li:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.service-list img {
width: 50px;
height: 50px;
margin-bottom: 10px;
}
.service-list .el-icon {
font-size: 30px;
margin: 10px 0;
color: #409EFF;
}
.service-list p {
margin: 5px 0;
}
.service-list p:first-child {
font-weight: bold;
font-size: 16px;
}
.service-list p:last-child {
color: #666;
font-size: 14px;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.service-list-container {
width: calc(100% - 64px); /* 折叠时的侧边栏宽度 */
}
.service-list li {
flex: 0 0 150px;
}
}
</style>

@ -0,0 +1,12 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
}
}
})
Loading…
Cancel
Save