权限管理Vue实现

[复制链接]
发表于 2025-10-21 00:54:36 | 显示全部楼层 |阅读模式
1.网站开发中为什么必要权限控制

掩护敏感信息:确保只有授权用户才华访问特定命据。
增强安全性:防御未经授权的访问和其他安全威胁。
满意合规要求:服从相干法律法规,如处理处罚个人或财务信息的规定。
提拔用户体验:为差别脚色定制化访问和服务,简化界面。
支持多脚色操纵:答应基于用户脚色分配差别的访问级别和功能
防止误操纵:限定关键操纵仅限于指定职员实行,镌汰错误风险。
审计追踪:记任命户活动以便查察和题目排查。
维护数据同等性:防止未经授权的数据修改,保持数据正确性和完备性。
2.下载Pinia

npm install pina
3.设置Pinia
  1. import { createPinia } from 'pinia'
复制代码
  1. const pinia = createPinia()
  2. app.use(pinia)
复制代码
4.新建store文件夹,创建user文件设置权限列表
  1. // src/stores/user.js
  2. import {defineStore} from 'pinia'
  3. export const useUserStore = defineStore('user', {
  4.    state: () => ({
  5.        role: 'guest', // 默认角色为访客
  6.        menus: [
  7.            {name: '主页', path: '/', permission: ['guest', 'user', 'admin']},
  8.            {name: '用户面板', path: '/dashboard', permission: ['user', 'admin']},
  9.            {name: '管理员面板', path: '/admin', permission: ['admin']},
  10.            {name: ' 产品管理', path: '/product', permission: ['admin']}
  11.        ]
  12.    }),
  13.    getters: {
  14.        accessibleMenus: (state) => {
  15.            return state.menus.filter(menu => menu.permission.includes(state.role));
  16.        }
  17.    },
  18.    actions: {
  19.        setRole(role) {
  20.            this.role = role;
  21.        }
  22.    }
  23. })
复制代码
5.创建router.js文件设置路由
  1. // src/router/index.js
  2. import {createRouter, createWebHistory} from 'vue-router'
  3. import Home from '../views/Home.vue'
  4. import Dashboard from '../views/Dashboard.vue'
  5. import AdminPanel from '../views/AdminPanel.vue'
  6. import Product from '../views/ProductManager.vue'
  7. import {useUserStore} from '../store/user'
  8. const routes = [
  9.    {path: '/', component: Home},
  10.    {path: '/dashboard', component: Dashboard, meta: {requiresAuth: true, roles: ['user', 'admin']}},
  11.    {path: '/admin', component: AdminPanel, meta: {requiresAuth: true, roles: ['admin']}},
  12.    {path: '/product', component: Product, meta: {requiresAuth: true, roles: ['admin']}}
  13. ]
  14. const router = createRouter({
  15.    history: createWebHistory(),
  16.    routes
  17. })
  18. router.beforeEach(async (to, from, next) => {
  19.    const userStore = useUserStore()
  20.    if (to.matched.some(record => record.meta.requiresAuth)) {
  21.        if (!to.meta.roles.includes(userStore.role)) {
  22.            next({path: '/'})
  23.        } else {
  24.            next()
  25.        }
  26.    } else {
  27.        next()
  28.    }
  29. })
  30. export default router
复制代码
6.创建vue所需组件


  • 首页组件
  1. <script setup>
  2. import { onMounted, ref } from 'vue'
  3. import axios from "axios";
  4. // 定义产品列表
  5. const products = ref([])
  6. // 获取所有产品
  7. const getProducts = async () => {
  8. try {
  9.    const response = await axios.get('http://localhost:8080/api/product/getAll');
  10.    products.value = response.data;
  11. } catch (error) {
  12.    console.error('获取产品列表时出错:', error);
  13. }
  14. };
  15. onMounted(() => {
  16. getProducts();
  17. })
  18. </script>
  19. <template>
  20. <div class="product-page">
  21.    <!-- 展示产品列表 -->
  22.    <section class="product-list-section">
  23.      <h2>我们的产品</h2>
  24.      <ul class="product-list">
  25.        <li v-for="product in products" :key="product.id" class="product-item">
  26.          <img :src="product.image" alt="产品图片" class="product-image">
  27.          <div class="product-info">
  28.            <h3>{{ product.content }}</h3>
  29.            <p>{{ product.addTime }}</p>
  30.          </div>
  31.        </li>
  32.      </ul>
  33.    </section>
  34. </div>
  35. </template>
  36. <style scoped>
  37. .product-page {
  38. font-family: 'Microsoft YaHei', Arial, sans-serif;
  39. text-align: center;
  40. background-color: #f4f4f4;
  41. }
  42. .product-list-section {
  43. padding: 50px 20px;
  44. }
  45. .product-list {
  46. list-style-type: none;
  47. padding: 0;
  48. display: flex;
  49. flex-wrap: wrap;
  50. justify-content: center;
  51. }
  52. .product-item {
  53. background-color: white;
  54. margin: 10px;
  55. border-radius: 10px;
  56. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  57. width: 300px;
  58. overflow: hidden;
  59. transition: transform 0.3s ease-in-out;
  60. }
  61. .product-item:hover {
  62. transform: scale(1.05);
  63. }
  64. .product-image {
  65. width: 100%;
  66. height: auto;
  67. border-top-left-radius: 10px;
  68. border-top-right-radius: 10px;
  69. }
  70. .product-info {
  71. padding: 15px;
  72. }
  73. .product-info h3 {
  74. margin-top: 0;
  75. font-size: 1.2em;
  76. color: #333;
  77. }
  78. .product-info p {
  79. font-size: 1em;
  80. color: #666;
  81. }
  82. </style>
复制代码

  • 面板组件
  1. <script setup>
  2. import { onMounted, computed } from 'vue'
  3. import { useUserStore } from '../store/user'
  4. const userStore = useUserStore()
  5. // 使用计算属性获取角色信息
  6. const role = computed(() => userStore.role)
  7. onMounted(() => {
  8. // 在组件挂载时可以执行一些初始化操作
  9. })
  10. </script>
  11. <template>
  12. <div class="dashboard">
  13.    <header class="dashboard-header">
  14.      <h1>仪表盘</h1>
  15.      <p>欢迎回来,{{ role === 'admin' ? '管理员' : '用户' }}!</p>
  16.    </header>
  17.    <!-- 管理员特定内容 -->
  18.    <section v-if="role === 'admin'" class="admin-section">
  19.      <div class="card">
  20.        <h2>管理员面板</h2>
  21.        <p>此部分内容仅对管理员开放。</p>
  22.        <button @click="showAdminDetails">查看详情</button>
  23.      </div>
  24.    </section>
  25.    <!-- 用户特定内容 -->
  26.    <section v-else class="user-section">
  27.      <div class="card">
  28.        <h2>用户仪表盘</h2>
  29.        <p>欢迎来到您的个人仪表盘。在这里,您可以管理任务并查看重要信息。</p>
  30.      </div>
  31.    </section>
  32.    <!-- 公共内容 -->
  33.    <section class="common-section">
  34.      <div class="card">
  35.        <h2>公共部分</h2>
  36.        <p>无论您的角色是什么,都可以看到此部分内容。</p>
  37.      </div>
  38.    </section>
  39. </div>
  40. </template>
  41. <style scoped>
  42. .dashboard {
  43. font-family: 'Microsoft YaHei', Arial, sans-serif;
  44. max-width: 1200px;
  45. margin: 0 auto;
  46. padding: 20px;
  47. }
  48. .dashboard-header {
  49. background-color: #4CAF50;
  50. color: white;
  51. padding: 20px;
  52. border-radius: 8px;
  53. text-align: center;
  54. }
  55. .dashboard-header h1 {
  56. font-size: 2em;
  57. margin-bottom: 0.5em;
  58. }
  59. .dashboard-header p {
  60. font-size: 1.2em;
  61. }
  62. .admin-section, .user-section, .common-section {
  63. display: flex;
  64. justify-content: space-around;
  65. flex-wrap: wrap;
  66. margin-top: 20px;
  67. }
  68. .card {
  69. background-color: white;
  70. border-radius: 8px;
  71. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  72. padding: 20px;
  73. width: 30%;
  74. margin-bottom: 20px;
  75. transition: transform 0.2s;
  76. }
  77. .card:hover {
  78. transform: translateY(-5px);
  79. }
  80. .card h2 {
  81. font-size: 1.5em;
  82. margin-bottom: 0.5em;
  83. }
  84. .card p {
  85. font-size: 1em;
  86. }
  87. button {
  88. background-color: #4CAF50;
  89. color: white;
  90. padding: 10px 20px;
  91. border: none;
  92. border-radius: 5px;
  93. cursor: pointer;
  94. font-size: 1em;
  95. margin-top: 10px;
  96. }
  97. button:hover {
  98. background-color: #45a049;
  99. }
  100. </style>
  101. <script>
  102. export default {
  103. methods: {
  104.    showAdminDetails() {
  105.      alert('这是管理员详情部分');
  106.    }
  107. }
  108. }
  109. </script>
复制代码

  • 管理员面板组件
  1. <script setup>
  2. import {onMounted, ref} from 'vue'
  3. import {useUserStore} from '../store/user'
  4. const userStore = useUserStore()
  5. // 示例数据
  6. const users = ref([])
  7. // 模拟获取用户列表的方法
  8. const fetchUsers = () => {
  9. // 这里可以替换为实际的API调用
  10. setTimeout(() => {
  11.    users.value = [
  12.      {id: 1, name: '张伟', role: 'admin'},
  13.      {id: 2, name: '李娜', role: 'user'}
  14.    ]
  15. }, 1000)
  16. }
  17. onMounted(() => {
  18. if (userStore.role === 'admin') {
  19.    fetchUsers()
  20. }
  21. })
  22. </script>
  23. <template>
  24. <div class="admin-panel">
  25.    <header class="panel-header">
  26.      <h1>管理员面板</h1>
  27.      <p>欢迎,管理员!</p>
  28.    </header>
  29.    <!-- 用户列表 -->
  30.    <section v-if="users.length > 0" class="user-list-section">
  31.      <h2>用户列表</h2>
  32.      <ul>
  33.        <li v-for="user in users" :key="user.id" class="user-item">
  34.          <span>{{ user.name }}</span> -
  35.          <span>{{ user.role === 'admin' ? '管理员' : '普通用户' }}</span>
  36.        </li>
  37.      </ul>
  38.    </section>
  39.    <section v-else class="loading-section">
  40.      <p>加载用户中...</p>
  41.    </section>
  42. </div>
  43. </template>
  44. <style scoped>
  45. .admin-panel {
  46. max-width: 800px;
  47. margin: 0 auto;
  48. padding: 20px;
  49. background-color: #f9f9f9;
  50. border-radius: 8px;
  51. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  52. font-family: 'Microsoft YaHei', Arial, sans-serif;
  53. }
  54. .panel-header {
  55. text-align: center;
  56. margin-bottom: 20px;
  57. }
  58. .panel-header h1 {
  59. color: #333;
  60. font-size: 2em;
  61. }
  62. .panel-header p {
  63. color: #666;
  64. font-size: 1.2em;
  65. }
  66. .user-list-section, .loading-section {
  67. text-align: center;
  68. }
  69. .user-list-section ul {
  70. list-style-type: none;
  71. padding: 0;
  72. }
  73. .user-item {
  74. background-color: #fff;
  75. margin-bottom: 10px;
  76. padding: 15px;
  77. border-radius: 4px;
  78. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  79. display: flex;
  80. justify-content: space-between;
  81. align-items: center;
  82. }
  83. .user-item span:first-child {
  84. font-weight: bold;
  85. }
  86. .loading-section p {
  87. font-size: 1.2em;
  88. color: #666;
  89. }
  90. </style>
复制代码
  1. <template>
  2. <div class="product-management">
  3.    <!-- 添加产品的弹窗 -->
  4.    <div v-if="isAddModalVisible" class="modal-overlay" @click.self="closeAddModal">
  5.      <div class="modal-content">
  6.        <span class="close" @click="closeAddModal">&times;</span>
  7.        <h2>添加产品</h2>
  8.        <form @submit.prevent="saveProduct()">
  9.          <div class="form-group">
  10.            <label for="content">内容:</label>
  11.            <input type="text" id="content" v-model="addProduct.content" required />
  12.          </div>
  13.          <div class="form-group">
  14.            <label for="image">图片URL:</label>
  15.            <input type="url" id="image" v-model="addProduct.image" required />
  16.          </div>
  17.          <button type="submit">保存</button>
  18.        </form>
  19.      </div>
  20.    </div>
  21.    <!-- 编辑产品的弹窗 -->
  22.    <div v-if="isEditModalVisible" class="modal-overlay" @click.self="closeEditModal">
  23.      <div class="modal-content">
  24.        <span class="close" @click="closeEditModal">&times;</span>
  25.        <h2>编辑产品</h2>
  26.        <form @submit.prevent="updateProduct()">
  27.          <div class="form-group">
  28.            <label for="editContent">内容:</label>
  29.            <input type="text" id="editContent" v-model="editProductData.content" required />
  30.          </div>
  31.          <div class="form-group">
  32.            <label for="editImage">图片URL:</label>
  33.            <input type="url" id="editImage" v-model="editProductData.image" required />
  34.          </div>
  35.          <button type="submit">更新</button>
  36.        </form>
  37.      </div>
  38.    </div>
  39.    <!-- 产品列表 -->
  40.    <div class="product-list">
  41.      <h2>产品列表</h2>
  42.      <ul>
  43.        <li v-for="(item, index) in products" :key="index" class="product-item">
  44.          <img :src="item.image" alt="Product Image" class="product-image" @click="showImage(item.image)">
  45.          <div class="product-info">
  46.            <span>{{ item.content }}</span>
  47.            <span>{{ item.addTime }}</span>
  48.          </div>
  49.          <div class="actions">
  50.            <button @click="editProduct(item)">编辑</button>
  51.            <button @click="deleteProduct(item)">删除</button>
  52.          </div>
  53.        </li>
  54.      </ul>
  55.    </div>
  56.    <!-- 添加产品的按钮 -->
  57.    <button class="add-product-button" @click="openAddModal">添加产品</button>
  58.    <!-- Lightbox 图片预览 -->
  59.    <vue-easy-lightbox
  60.        :visible="visible"
  61.        :imgs="imgs"
  62.        @hide="handleHide"
  63.    ></vue-easy-lightbox>
  64. </div>
  65. </template>
  66. <script>
  67. import { ref, onMounted } from 'vue';
  68. import axios from 'axios';
  69. import VueEasyLightbox from 'vue-easy-lightbox';
  70. export default {
  71. components: { VueEasyLightbox },
  72. setup() {
  73.    const isAddModalVisible = ref(false);
  74.    const isEditModalVisible = ref(false);
  75.    const addProduct = ref({ content: '', image: '' });
  76.    const editProductData = ref({ content: '', image: '' });
  77.    const products = ref([]);
  78.    const currentProductId = ref(null);
  79.    const visible = ref(false);
  80.    const imgs = ref('');
  81.    // 打开添加产品的模态框
  82.    const openAddModal = () => {
  83.      isAddModalVisible.value = true;
  84.    };
  85.    // 关闭添加产品的模态框
  86.    const closeAddModal = () => {
  87.      isAddModalVisible.value = false;
  88.      resetAddForm();
  89.    };
  90.    // 清空添加表单
  91.    const resetAddForm = () => {
  92.      addProduct.value.content = '';
  93.      addProduct.value.image = '';
  94.    };
  95.    // 打开编辑产品的模态框
  96.    const openEditModal = () => {
  97.      isEditModalVisible.value = true;
  98.    };
  99.    // 关闭编辑产品的模态框
  100.    const closeEditModal = () => {
  101.      isEditModalVisible.value = false;
  102.      resetEditForm();
  103.    };
  104.    // 清空编辑表单
  105.    const resetEditForm = () => {
  106.      editProductData.value.content = '';
  107.      editProductData.value.image = '';
  108.    };
  109.    // 保存产品
  110.    const saveProduct = async () => {
  111.      try {
  112.        await axios.post('http://localhost:8080/api/product/save', addProduct.value);
  113.        await getProducts();
  114.        closeAddModal();
  115.      } catch (error) {
  116.        console.error('保存产品时出错:', error);
  117.      }
  118.    };
  119.    // 更新产品
  120.    const updateProduct = async () => {
  121.      try {
  122.        await axios.put(`http://localhost:8080/api/product/update/${currentProductId.value}`, editProductData.value);
  123.        await getProducts();
  124.        closeEditModal();
  125.      } catch (error) {
  126.        console.error('更新产品时出错:', error);
  127.      }
  128.    };
  129.    // 删除产品
  130.    const deleteProduct = async (item) => {
  131.      if (confirm('确定要删除此产品吗?')) {
  132.        try {
  133.          await axios.delete(`http://localhost:8080/api/product/delete/${item.id}`);
  134.          await getProducts();
  135.        } catch (error) {
  136.          console.error('删除产品时出错:', error);
  137.        }
  138.      }
  139.    };
  140.    // 编辑产品
  141.    const editProduct = (item) => {
  142.      currentProductId.value = item.id;
  143.      editProductData.value = { ...item };
  144.      openEditModal();
  145.    };
  146.    // 获取所有产品
  147.    const getProducts = async () => {
  148.      try {
  149.        const response = await axios.get('http://localhost:8080/api/product/getAll');
  150.        products.value = response.data;
  151.      } catch (error) {
  152.        console.error('获取产品列表时出错:', error);
  153.      }
  154.    };
  155.    // 显示大图
  156.    const showImage = (src) => {
  157.      imgs.value = src;
  158.      visible.value = true;
  159.    };
  160.    // 隐藏大图
  161.    const handleHide = () => {
  162.      visible.value = false;
  163.    };
  164.    onMounted(() => {
  165.      getProducts();
  166.    });
  167.    return {
  168.      isAddModalVisible,
  169.      isEditModalVisible,
  170.      addProduct,
  171.      editProductData,
  172.      products,
  173.      openAddModal,
  174.      closeAddModal,
  175.      openEditModal,
  176.      closeEditModal,
  177.      saveProduct,
  178.      updateProduct,
  179.      deleteProduct,
  180.      editProduct,
  181.      visible,
  182.      imgs,
  183.      showImage,
  184.      handleHide
  185.    };
  186. }
  187. };
  188. </script>
  189. <style scoped>
  190. .product-management {
  191. display: flex;
  192. flex-direction: column;
  193. align-items: center;
  194. font-family: 'Microsoft YaHei', Arial, sans-serif;
  195. }
  196. .add-product-button {
  197. margin-top: 20px;
  198. padding: 10px 20px;
  199. background-color: #4CAF50;
  200. color: white;
  201. border: none;
  202. cursor: pointer;
  203. font-size: 16px;
  204. border-radius: 5px;
  205. transition: background-color 0.3s ease;
  206. }
  207. .add-product-button:hover {
  208. background-color: #45a049;
  209. }
  210. .modal-overlay {
  211. position: fixed;
  212. top: 0;
  213. left: 0;
  214. width: 100%;
  215. height: 100%;
  216. background-color: rgba(0, 0, 0, 0.5);
  217. display: flex;
  218. justify-content: center;
  219. align-items: center;
  220. }
  221. .modal-content {
  222. background-color: white;
  223. padding: 20px;
  224. border-radius: 8px;
  225. width: 400px;
  226. position: relative;
  227. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  228. }
  229. .close {
  230. position: absolute;
  231. top: 10px;
  232. right: 10px;
  233. font-size: 20px;
  234. cursor: pointer;
  235. }
  236. .form-group {
  237. margin-bottom: 15px;
  238. }
  239. .form-group label {
  240. display: block;
  241. margin-bottom: 5px;
  242. font-weight: bold;
  243. }
  244. .form-group input {
  245. width: 100%;
  246. padding: 8px;
  247. box-sizing: border-box;
  248. border: 1px solid #ccc;
  249. border-radius: 4px;
  250. }
  251. button[type="submit"] {
  252. background-color: #4CAF50;
  253. color: white;
  254. padding: 10px 15px;
  255. border: none;
  256. border-radius: 5px;
  257. cursor: pointer;
  258. font-size: 14px;
  259. transition: background-color 0.3s ease;
  260. }
  261. button[type="submit"]:hover {
  262. background-color: #45a049;
  263. }
  264. .product-list {
  265. max-width: 1000px;
  266. width: 100%;
  267. margin-top: 20px;
  268. }
  269. .product-list h2 {
  270. text-align: center;
  271. color: #333;
  272. font-size: 1.8em;
  273. margin-bottom: 20px;
  274. }
  275. .product-list ul {
  276. list-style-type: none;
  277. padding: 0;
  278. }
  279. .product-item {
  280. display: flex;
  281. align-items: center;
  282. margin-bottom: 15px;
  283. padding: 15px;
  284. border: 1px solid #ddd;
  285. border-radius: 8px;
  286. justify-content: space-between;
  287. background-color: #f9f9f9;
  288. transition: background-color 0.3s ease;
  289. }
  290. .product-item:hover {
  291. background-color: #e9e9e9;
  292. }
  293. .product-image {
  294. max-width: 120px;
  295. max-height: 120px;
  296. object-fit: cover;
  297. margin-right: 15px;
  298. cursor: pointer;
  299. }
  300. .product-info {
  301. flex-grow: 1;
  302. }
  303. .actions button {
  304. margin-left: 10px;
  305. padding: 5px 10px;
  306. background-color: #007bff;
  307. color: white;
  308. border: none;
  309. cursor: pointer;
  310. font-size: 14px;
  311. border-radius: 3px;
  312. transition: background-color 0.3s ease;
  313. }
  314. .actions button:hover {
  315. background-color: #0056b3;
  316. }
  317. </style>
复制代码
7.在App.vue中遍历Pinia数据获取权限列表
  1. <!-- src/App.vue -->
  2. <template>
  3. <div id="app">
  4.    <!-- 导航栏 -->
  5.    <nav class="menu-bar">
  6.      <ul>
  7.        <li v-for="menu in accessibleMenus" :key="menu.path" class="menu-item">
  8.          <router-link :to="menu.path">{{ menu.name }}</router-link>
  9.        </li>
  10.      </ul>
  11.    </nav>
  12.    <!-- 路由视图 -->
  13.    <router-view></router-view>
  14. </div>
  15. </template>
  16. <script setup>
  17. import { computed } from 'vue'
  18. import { useUserStore } from './store/user'
  19. // 使用用户状态管理
  20. const userStore = useUserStore()
  21. // 计算属性获取可访问菜单列表
  22. const accessibleMenus = computed(() => userStore.accessibleMenus)
  23. </script>
  24. <style scoped>
  25. /* 应用全局样式 */
  26. #app {
  27. font-family: 'Microsoft YaHei', Arial, sans-serif;
  28. }
  29. .menu-bar {
  30. background-color: #333;
  31. padding: 10px 0;
  32. }
  33. .menu-bar ul {
  34. list-style-type: none;
  35. margin: 0;
  36. padding: 0;
  37. display: flex;
  38. justify-content: center;
  39. }
  40. .menu-item {
  41. margin: 0 15px;
  42. }
  43. .menu-item a {
  44. color: white;
  45. text-decoration: none;
  46. padding: 8px 15px;
  47. border-radius: 5px;
  48. transition: background-color 0.3s ease-in-out;
  49. }
  50. .menu-item a:hover {
  51. background-color: #4CAF50;
  52. }
  53. .menu-item a.router-link-active {
  54. background-color: #4CAF50;
  55. font-weight: bold;
  56. }
  57. </style>
复制代码
8.在main.js里设置当前用户权限(user or admin)
  1. // src/main.jsimport { createApp } from 'vue'import App from './App.vue'import router from './router/router.js'import { createPinia } from 'pinia'
  2. import { useUserStore } from './store/user.js'import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'const app = createApp(App)const pinia = createPinia()
  3. app.use(pinia)
  4. app.use(router)app.use(ElementPlus)const userStore = useUserStore()// 模仿登录逻辑userStore.setRole('admin') // 可以根据现实情况设置差别的脚色app.mount('#app')
复制代码




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表