Browse Source

add:lihui 练习vue

todo: 有很多bug(菜单栏)
master
lihui 2 weeks ago
commit
1900916f45
  1. 24
      .gitignore
  2. 3
      .vscode/extensions.json
  3. 5
      README.md
  4. 13
      index.html
  5. 1984
      package-lock.json
  6. 38
      package.json
  7. 1
      public/vite.svg
  8. 5
      src/ shims-vue.d.ts
  9. 24
      src/App.vue
  10. 79
      src/assets/style.css
  11. 1
      src/assets/vue.svg
  12. 15
      src/components/Footer.vue
  13. 61
      src/components/NavHeader.vue
  14. 62
      src/components/SideBar.vue
  15. 12
      src/main.ts
  16. 87
      src/pages/HomeView.vue
  17. 266
      src/pages/ProductView.vue
  18. 155
      src/pages/SettingView.vue
  19. 249
      src/pages/UserView.vue
  20. 35
      src/router/index.ts
  21. 33
      src/utils/http.ts
  22. 3
      src/utils/test.ts
  23. 1
      src/vite-env.d.ts
  24. 15
      tsconfig.app.json
  25. 9
      tsconfig.json
  26. 25
      tsconfig.node.json
  27. 7
      vite.config.ts

24
.gitignore

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
README.md

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

13
index.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

1984
package-lock.json
File diff suppressed because it is too large
View File

38
package.json

@ -0,0 +1,38 @@
{
"name": "elementplus_demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.10.0",
"element-plus": "^2.10.2",
"vue": "^3.5.13",
"vue-route": "^1.1.5",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.8.3",
"vite": "^6.3.5",
"vue-tsc": "^2.2.8"
},
"compilerOptions": {
"types": [
"vue/global-types"
]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}

1
public/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

5
src/ shims-vue.d.ts

@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

24
src/App.vue

@ -0,0 +1,24 @@
<template>
<div id="app">
<NavHeader />
<router-view />
<Footer />
</div>
</template>
<script setup>
import NavHeader from './components/NavHeader.vue'
import Footer from './components/Footer.vue'
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100vh;
display: flex;
flex-direction: column;
}
</style>

79
src/assets/style.css

@ -0,0 +1,79 @@
:root {
font-family: 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;
}
}

1
src/assets/vue.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

15
src/components/Footer.vue

@ -0,0 +1,15 @@
<template>
<el-footer class="text-center text-gray-500">
© 2025 ElementPlus Demo. All rights reserved.
</el-footer>
</template>
<script setup>
</script>
<style scoped>
.el-footer {
line-height: 60px;
border-top: 1px solid #ebeef5;
}
</style>

61
src/components/NavHeader.vue

@ -0,0 +1,61 @@
<template>
<el-header>
<el-row>
<el-col :span="6">
<h1 class="text-2xl font-bold text-white">ElementPlus Demo</h1>
</el-col>
<el-col :span="18">
<el-menu
mode="horizontal"
:default-active="activeMenu"
background-color="#304156"
text-color="#fff"
active-text-color="#409EFF"
@select="handleSelect"
>
<el-menu-item index="1" route="/">首页</el-menu-item>
<el-menu-item index="2" route="/product">商品管理</el-menu-item>
<el-menu-item index="3" route="/user">用户管理</el-menu-item>
<el-sub-menu index="4">
<template #title>系统设置</template>
<el-menu-item index="4-1" route="/setting/permissions">权限设置</el-menu-item>
<el-menu-item index="4-2" route="/setting/interface">界面设置</el-menu-item>
</el-sub-menu>
</el-menu>
</el-col>
</el-row>
</el-header>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const activeMenu = ref('1')
const handleSelect = (key) => {
// index
const pathMap = {
'1': '/',
'2': '/product',
'3': '/user',
'4-1': '/setting/permissions',
'4-2': '/setting/interface'
}
if (pathMap[key]) {
router.push(pathMap[key])
activeMenu.value = key
}
}
</script>
<style scoped>
.el-header {
background-color: #304156;
color: #fff;
line-height: 60px;
padding: 0 20px;
}
</style>

62
src/components/SideBar.vue

@ -0,0 +1,62 @@
<template>
<el-aside width="200px">
<el-menu
:default-active="activeKey"
class="el-menu-vertical"
@open="handleOpen"
@close="handleClose"
background-color="#fff"
text-color="#303133"
active-text-color="#409EFF"
>
<el-sub-menu index="1">
<template #title>
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<template #title>分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组二">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title>选项4</template>
<el-menu-item index="1-4-1">子选项1</el-menu-item>
<el-menu-item index="1-4-2">子选项2</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<span>导航二</span>
</el-menu-item>
<el-menu-item index="3">
<i class="el-icon-document"></i>
<span>导航三</span>
</el-menu-item>
</el-menu>
</el-aside>
</template>
<script setup>
import { ref } from 'vue'
const activeKey = ref('1-1')
const handleOpen = (key, keyPath) => {
console.log(key, keyPath)
}
const handleClose = (key, keyPath) => {
console.log(key, keyPath)
}
</script>
<style scoped>
.el-aside {
background-color: #f9fafc;
border-right: 1px solid #ebeef5;
}
</style>

12
src/main.ts

@ -0,0 +1,12 @@
import {createApp} from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')

87
src/pages/HomeView.vue

@ -0,0 +1,87 @@
<template>
<div class="flex h-screen">
<SideBar />
<div class="flex-1 p-4 overflow-auto">
<el-card class="mb-4">
<template #header>
<div class="flex justify-between items-center">
<span>数据统计</span>
<el-button type="primary" size="small">刷新</el-button>
</div>
</template>
<el-row :gutter="20">
<el-col :span="6">
<el-card class="text-center">
<div class="text-3xl font-bold text-blue-500">¥123,456</div>
<div class="text-gray-500 mt-2">今日销售额</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="text-center">
<div class="text-3xl font-bold text-green-500">2,451</div>
<div class="text-gray-500 mt-2">订单数量</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="text-center">
<div class="text-3xl font-bold text-orange-500">1,284</div>
<div class="text-gray-500 mt-2">用户数</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="text-center">
<div class="text-3xl font-bold text-purple-500">87%</div>
<div class="text-gray-500 mt-2">转化率</div>
</el-card>
</el-col>
</el-row>
</el-card>
<el-card>
<template #header>
<span>最近订单</span>
</template>
<el-table :data="orderData" stripe style="width: 100%">
<el-table-column prop="orderId" label="订单ID" width="150"></el-table-column>
<el-table-column prop="customer" label="客户" width="120"></el-table-column>
<el-table-column prop="product" label="商品" width="180"></el-table-column>
<el-table-column prop="amount" label="金额" width="100">
<template #default="scope">
¥{{ scope.row.amount }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="date" label="日期" width="160"></el-table-column>
</el-table>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import SideBar from '../components/SideBar.vue'
const orderData = ref([
{ orderId: 'ORD-20250618-001', customer: '张三', product: 'iPhone 14 Pro', amount: 8999, status: '已支付', date: '2025-06-18 10:23' },
{ orderId: 'ORD-20250618-002', customer: '李四', product: 'MacBook Air', amount: 11999, status: '已发货', date: '2025-06-18 09:45' },
{ orderId: 'ORD-20250618-003', customer: '王五', product: 'iPad Pro', amount: 6799, status: '已支付', date: '2025-06-18 08:12' },
{ orderId: 'ORD-20250618-004', customer: '赵六', product: 'Apple Watch', amount: 3199, status: '待支付', date: '2025-06-18 07:30' },
{ orderId: 'ORD-20250618-005', customer: '钱七', product: 'AirPods Pro', amount: 1799, status: '已完成', date: '2025-06-18 06:55' }
])
const getStatusType = (status) => {
const statusMap = {
'已支付': 'primary',
'已发货': 'success',
'待支付': 'warning',
'已完成': 'info',
'已取消': 'danger'
}
return statusMap[status] || 'default'
}
</script>

266
src/pages/ProductView.vue

@ -0,0 +1,266 @@
<template>
<div class="flex h-screen">
<SideBar />
<div class="flex-1 p-4 overflow-auto">
<el-card>
<template #header>
<div class="flex justify-between items-center">
<span>商品列表</span>
<el-button type="primary" @click="handleCreateProduct">新增商品</el-button>
</div>
</template>
<el-form :inline="true" :model="searchForm" class="mb-4">
<el-form-item label="商品名称">
<el-input v-model="searchForm.name" placeholder="请输入商品名称" clearable></el-input>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="searchForm.category" placeholder="请选择分类" clearable>
<el-option label="电子产品" value="electronics"></el-option>
<el-option label="服装鞋帽" value="clothing"></el-option>
<el-option label="家居用品" value="household"></el-option>
<el-option label="食品饮料" value="food"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="上架" value="active"></el-option>
<el-option label="下架" value="inactive"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="100"></el-table-column>
<el-table-column prop="name" label="商品名称"></el-table-column>
<el-table-column prop="category" label="分类" width="120">
<template #default="scope">
{{ getCategoryName(scope.row.category) }}
</template>
</el-table-column>
<el-table-column prop="price" label="价格" width="100">
<template #default="scope">
¥{{ scope.row.price }}
</template>
</el-table-column>
<el-table-column prop="stock" label="库存" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
{{ scope.row.status === 'active' ? '上架' : '下架' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="scope">
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="mt-4 flex justify-center"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, reactive, toRefs } from 'vue'
import SideBar from '../components/SideBar.vue'
//
const searchForm = reactive({
name: '',
category: '',
status: ''
})
//
const tableData = ref([
{ id: 1, name: 'iPhone 14 Pro', category: 'electronics', price: 8999, stock: 100, status: 'active' },
{ id: 2, name: 'MacBook Air', category: 'electronics', price: 11999, stock: 50, status: 'active' },
{ id: 3, name: 'iPad Pro', category: 'electronics', price: 6799, stock: 80, status: 'active' },
{ id: 4, name: 'Apple Watch', category: 'electronics', price: 3199, stock: 120, status: 'active' },
{ id: 5, name: 'AirPods Pro', category: 'electronics', price: 1799, stock: 200, status: 'inactive' },
{ id: 6, name: 'T恤', category: 'clothing', price: 99, stock: 300, status: 'active' },
{ id: 7, name: '牛仔裤', category: 'clothing', price: 299, stock: 150, status: 'active' },
{ id: 8, name: '运动鞋', category: 'clothing', price: 599, stock: 90, status: 'active' },
{ id: 9, name: '沙发', category: 'household', price: 3999, stock: 20, status: 'active' },
{ id: 10, name: '餐桌', category: 'household', price: 1999, stock: 30, status: 'inactive' }
])
//
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100)
//
const dialogVisible = ref(false)
const dialogTitle = ref('')
const editForm = reactive({
id: null,
name: '',
category: 'electronics',
price: 0,
stock: 0,
status: 'active'
})
//
const rules = reactive({
name: [
{ required: true, message: '请输入商品名称', trigger: 'blur' }
],
category: [
{ required: true, message: '请选择商品分类', trigger: 'change' }
],
price: [
{ required: true, message: '请输入价格', trigger: 'blur' },
{ type: 'number', message: '价格必须为数字值', trigger: 'blur' }
],
stock: [
{ required: true, message: '请输入库存', trigger: 'blur' },
{ type: 'number', message: '库存必须为数字值', trigger: 'blur' }
]
})
//
const editFormRef = ref(null)
//
const categoryMap = {
'electronics': '电子产品',
'clothing': '服装鞋帽',
'household': '家居用品',
'food': '食品饮料'
}
//
const getCategoryName = (category) => {
return categoryMap[category] || category
}
//
const handleSearch = () => {
console.log('搜索条件:', searchForm)
//
}
const resetSearch = () => {
searchForm.name = ''
searchForm.category = ''
searchForm.status = ''
}
//
const handleEdit = (row) => {
//
Object.assign(editForm, row)
dialogTitle.value = '编辑商品'
dialogVisible.value = true
}
const handleDelete = (row) => {
elMessageBox.confirm(
'确定要删除该商品吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
//
const index = tableData.value.findIndex(item => item.id === row.id)
if (index !== -1) {
tableData.value.splice(index, 1)
elMessage({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
elMessage({
type: 'info',
message: '已取消删除'
})
})
}
//
const handleSizeChange = (newSize) => {
pageSize.value = newSize
console.log('每页条数:', newSize)
}
const handleCurrentChange = (newPage) => {
currentPage.value = newPage
console.log('当前页码:', newPage)
}
//
const handleCreateProduct = () => {
//
Object.keys(editForm).forEach(key => {
if (key === 'id') {
editForm[key] = null
} else if (key === 'status') {
editForm[key] = 'active'
} else if (key === 'category') {
editForm[key] = 'electronics'
} else {
editForm[key] = ''
}
})
dialogTitle.value = '新增商品'
dialogVisible.value = true
}
//
const saveProduct = () => {
editFormRef.value.validate((valid) => {
if (valid) {
//
setTimeout(() => {
dialogVisible.value = false
elMessage({
type: 'success',
message: dialogTitle.value === '新增商品' ? '创建成功!' : '更新成功!'
})
//
if (editForm.id) {
//
const index = tableData.value.findIndex(item => item.id === editForm.id)
if (index !== -1) {
tableData.value.splice(index, 1, { ...editForm })
}
} else {
//
const newId = Math.max(...tableData.value.map(item => item.id)) + 1
tableData.value.unshift({ ...editForm, id: newId })
}
}, 500)
} else {
console.log('验证失败')
return false
}
})
}
</script>

155
src/pages/SettingView.vue

@ -0,0 +1,155 @@
<template>
<div class="flex h-screen">
<SideBar />
<div class="flex-1 p-4 overflow-auto">
<el-card>
<template #header>
<span>系统设置</span>
</template>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="基本设置" name="basic">
<el-form :model="basicSettings" label-width="120px" class="mt-4">
<el-form-item label="网站名称">
<el-input v-model="basicSettings.siteName"></el-input>
</el-form-item>
<el-form-item label="网站URL">
<el-input v-model="basicSettings.siteUrl"></el-input>
</el-form-item>
<el-form-item label="网站描述">
<el-input type="textarea" v-model="basicSettings.siteDescription" rows="4"></el-input>
</el-form-item>
<el-form-item label="联系邮箱">
<el-input v-model="basicSettings.contactEmail"></el-input>
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="basicSettings.contactPhone"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveBasicSettings">保存设置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="界面设置" name="interface">
<el-form :model="interfaceSettings" label-width="120px" class="mt-4">
<el-form-item label="主题颜色">
<el-color-picker v-model="interfaceSettings.themeColor"></el-color-picker>
</el-form-item>
<el-form-item label="侧边栏宽度">
<el-input-number v-model="interfaceSettings.sidebarWidth" :min="180" :max="300" step="10"></el-input-number>
</el-form-item>
<el-form-item label="分页大小">
<el-input-number v-model="interfaceSettings.pageSize" :min="5" :max="100" step="5"></el-input-number>
</el-form-item>
<el-form-item label="显示头部">
<el-switch v-model="interfaceSettings.showHeader"></el-switch>
</el-form-item>
<el-form-item label="显示页脚">
<el-switch v-model="interfaceSettings.showFooter"></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveInterfaceSettings">保存设置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="权限设置" name="permissions">
<el-form :model="permissionSettings" label-width="120px" class="mt-4">
<el-form-item label="用户管理权限">
<el-checkbox-group v-model="permissionSettings.userPermissions">
<el-checkbox label="view">查看</el-checkbox>
<el-checkbox label="create">创建</el-checkbox>
<el-checkbox label="edit">编辑</el-checkbox>
<el-checkbox label="delete">删除</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="商品管理权限">
<el-checkbox-group v-model="permissionSettings.productPermissions">
<el-checkbox label="view">查看</el-checkbox>
<el-checkbox label="create">创建</el-checkbox>
<el-checkbox label="edit">编辑</el-checkbox>
<el-checkbox label="delete">删除</el-checkbox>
<el-checkbox label="publish">发布</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="订单管理权限">
<el-checkbox-group v-model="permissionSettings.orderPermissions">
<el-checkbox label="view">查看</el-checkbox>
<el-checkbox label="process">处理</el-checkbox>
<el-checkbox label="refund">退款</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="savePermissionSettings">保存设置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import SideBar from '../components/SideBar.vue'
const activeTab = ref('basic')
//
const basicSettings = reactive({
siteName: 'ElementPlus Demo',
siteUrl: 'https://example.com',
siteDescription: '这是一个使用Vue3和ElementPlus构建的示例项目',
contactEmail: 'contact@example.com',
contactPhone: '12345678901'
})
//
const interfaceSettings = reactive({
themeColor: '#409EFF',
sidebarWidth: 200,
pageSize: 10,
showHeader: true,
showFooter: true
})
//
const permissionSettings = reactive({
userPermissions: ['view', 'create', 'edit', 'delete'],
productPermissions: ['view', 'create', 'edit', 'delete', 'publish'],
orderPermissions: ['view', 'process', 'refund']
})
//
const saveBasicSettings = () => {
//
setTimeout(() => {
elMessage({
type: 'success',
message: '基本设置保存成功!'
})
}, 500)
}
const saveInterfaceSettings = () => {
//
setTimeout(() => {
elMessage({
type: 'success',
message: '界面设置保存成功!'
})
}, 500)
}
const savePermissionSettings = () => {
//
setTimeout(() => {
elMessage({
type: 'success',
message: '权限设置保存成功!'
})
}, 500)
}
</script>

249
src/pages/UserView.vue

@ -0,0 +1,249 @@
<template>
<div class="flex h-screen">
<SideBar />
<div class="flex-1 p-4 overflow-auto">
<el-card>
<template #header>
<div class="flex justify-between items-center">
<span>用户列表</span>
<el-button type="primary" @click="handleCreateUser">新增用户</el-button>
</div>
</template>
<el-form :inline="true" :model="searchForm" class="mb-4">
<el-form-item label="用户名">
<el-input v-model="searchForm.username" placeholder="请输入用户名" clearable></el-input>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="启用" value="active"></el-option>
<el-option label="禁用" value="inactive"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="100"></el-table-column>
<el-table-column prop="username" label="用户名" width="120"></el-table-column>
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="gender" label="性别" width="80">
<template #default="scope">
<el-tag :type="scope.row.gender === '男' ? 'primary' : 'success'">
{{ scope.row.gender }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" width="80"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-text="启用"
inactive-text="禁用"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="scope">
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="mt-4 flex justify-center"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, reactive, toRefs } from 'vue'
import SideBar from '../components/SideBar.vue'
//
const searchForm = reactive({
username: '',
status: ''
})
//
const tableData = ref([
{ id: 1, username: 'user1', name: '张三', gender: '男', age: 20, email: 'zhangsan@example.com', status: true },
{ id: 2, username: 'user2', name: '李四', gender: '女', age: 24, email: 'lisi@example.com', status: true },
{ id: 3, username: 'user3', name: '王五', gender: '男', age: 30, email: 'wangwu@example.com', status: false },
{ id: 4, username: 'user4', name: '赵六', gender: '女', age: 22, email: 'zhaoliu@example.com', status: true },
{ id: 5, username: 'user5', name: '钱七', gender: '男', age: 28, email: 'qianqi@example.com', status: true },
{ id: 6, username: 'user6', name: '孙八', gender: '女', age: 25, email: 'sunba@example.com', status: false },
{ id: 7, username: 'user7', name: '周九', gender: '男', age: 32, email: 'zhoujiu@example.com', status: true },
{ id: 8, username: 'user8', name: '吴十', gender: '女', age: 27, email: 'wushi@example.com', status: true },
])
//
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100)
//
const dialogVisible = ref(false)
const dialogTitle = ref('')
const editForm = reactive({
id: null,
username: '',
name: '',
gender: '男',
age: 0,
email: '',
status: true
})
//
const rules = reactive({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] }
]
})
//
const editFormRef = ref(null)
//
const handleSearch = () => {
console.log('搜索条件:', searchForm)
//
}
const resetSearch = () => {
searchForm.username = ''
searchForm.status = ''
}
//
const handleEdit = (row) => {
//
Object.assign(editForm, row)
dialogTitle.value = '编辑用户'
dialogVisible.value = true
}
const handleDelete = (row) => {
elMessageBox.confirm(
'确定要删除该用户吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
//
const index = tableData.value.findIndex(item => item.id === row.id)
if (index !== -1) {
tableData.value.splice(index, 1)
elMessage({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
elMessage({
type: 'info',
message: '已取消删除'
})
})
}
const handleStatusChange = (row) => {
const statusText = row.status ? '启用' : '禁用'
elMessage({
type: 'success',
message: `用户 ${row.username}${statusText}`
})
}
//
const handleSizeChange = (newSize) => {
pageSize.value = newSize
console.log('每页条数:', newSize)
}
const handleCurrentChange = (newPage) => {
currentPage.value = newPage
console.log('当前页码:', newPage)
}
//
const handleCreateUser = () => {
//
Object.keys(editForm).forEach(key => {
if (key === 'id') {
editForm[key] = null
} else if (key === 'status') {
editForm[key] = true
} else if (key === 'gender') {
editForm[key] = '男'
} else {
editForm[key] = ''
}
})
dialogTitle.value = '新增用户'
dialogVisible.value = true
}
//
const saveUser = () => {
editFormRef.value.validate((valid) => {
if (valid) {
//
setTimeout(() => {
dialogVisible.value = false
elMessage({
type: 'success',
message: dialogTitle.value === '新增用户' ? '创建成功!' : '更新成功!'
})
//
if (editForm.id) {
//
const index = tableData.value.findIndex(item => item.id === editForm.id)
if (index !== -1) {
tableData.value.splice(index, 1, { ...editForm })
}
} else {
//
const newId = Math.max(...tableData.value.map(item => item.id)) + 1
tableData.value.unshift({ ...editForm, id: newId })
}
}, 500)
} else {
console.log('验证失败')
return false
}
})
}
</script>

35
src/router/index.ts

@ -0,0 +1,35 @@
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from "../pages/HomeView.vue";
import ProductView from "../pages/ProductView.vue";
import UserView from "../pages/UserView.vue";
import SettingView from "../pages/SettingView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/product',
name: 'product',
component: ProductView
},
{
path: '/user',
name: 'user',
component: UserView
},
{
path: '/setting',
name: 'setting',
component: SettingView
}
]
})
export default router

33
src/utils/http.ts

@ -0,0 +1,33 @@
//导入axios
import axios from "axios"
//axios.create创建一个axios实例
//我们给这个实例编写配置,后端所有通过这个实例发送请求,都受这个配置约束。
const $http=axios.create({
baseURL:"http://8080/",
timeout:1000
});
// 添加请求拦截器
$http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
$http.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default $http;

3
src/utils/test.ts

@ -0,0 +1,3 @@
import $http from "./http"
export const getData=$http.get("/posts");

1
src/vite-env.d.ts

@ -0,0 +1 @@
/// <reference types="vite/client" />

15
tsconfig.app.json

@ -0,0 +1,15 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

9
tsconfig.json

@ -0,0 +1,9 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
}

25
tsconfig.node.json

@ -0,0 +1,25 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2025"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

7
vite.config.ts

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