├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── logo.png
│ └── style
│ │ ├── common.less
│ │ └── reset.less
├── components
│ ├── Headerbar.vue
│ └── composables
│ │ └── index.ts
├── main.ts
├── mock
│ ├── data
│ │ ├── about.ts
│ │ └── user.ts
│ ├── index.ts
│ └── service.ts
├── router
│ └── index.ts
├── shims-vue.d.ts
├── source.d.ts
├── store
│ └── index.ts
├── types
│ └── index.d.ts
└── views
│ ├── About.vue
│ ├── Home.vue
│ └── List.vue
└── tsconfig.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | max_line_length = 100
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | '@vue/typescript/recommended',
9 | 'plugin:import/errors',
10 | 'plugin:import/warnings',
11 | 'plugin:import/typescript',
12 | ],
13 | parserOptions: {
14 | ecmaVersion: 2020,
15 | },
16 | rules: {
17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
19 | 'arrow-body-style': ['error', 'always'],
20 | 'max-len': ['error', { code: 160 }],
21 | 'import/no-extraneous-dependencies': ['warn', { devDependencies: false, optionalDependencies: false, peerDependencies: false }],
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # admin
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.21.0",
12 | "axios-mock-adapter": "^1.19.0",
13 | "core-js": "^3.6.5",
14 | "vue": "^3.0.0",
15 | "vue-router": "^4.0.0-0",
16 | "vuex": "^4.0.0-0"
17 | },
18 | "devDependencies": {
19 | "@typescript-eslint/eslint-plugin": "^2.33.0",
20 | "@typescript-eslint/parser": "^2.33.0",
21 | "@vue/cli-plugin-babel": "~4.5.0",
22 | "@vue/cli-plugin-eslint": "~4.5.0",
23 | "@vue/cli-plugin-router": "~4.5.0",
24 | "@vue/cli-plugin-typescript": "~4.5.0",
25 | "@vue/cli-plugin-vuex": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "@vue/compiler-sfc": "^3.0.0",
28 | "@vue/eslint-config-airbnb": "^5.0.2",
29 | "@vue/eslint-config-typescript": "^5.0.2",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-import": "^2.20.2",
32 | "eslint-plugin-vue": "^7.0.0-0",
33 | "less": "^3.0.4",
34 | "less-loader": "^5.0.0",
35 | "mockjs": "^1.1.0",
36 | "typescript": "~3.9.3"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CuteSunLee/vue3_ts_admin/cfd3f2eafa4b7e4c1d448497cb17102c593f0b58/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CuteSunLee/vue3_ts_admin/cfd3f2eafa4b7e4c1d448497cb17102c593f0b58/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/style/common.less:
--------------------------------------------------------------------------------
1 | .content_page {
2 | width: 100%;
3 | height: 100%;
4 | background: #fff;
5 | .content_title {
6 | height: 56px;
7 | line-height: 56px;
8 | background: #f5f8fa;
9 | font-size: 14px;
10 | border-bottom: 1px solid #e4eaee;
11 | border-right: 1px solid #e4eaee;
12 | padding-left: 20px;
13 | font-weight: bold;
14 | }
15 | .content_body {
16 | height: calc(100% - 56px);
17 | background: #fff;
18 | .content_button {
19 | box-sizing: border-box;
20 | padding: 12px 20px;
21 | height: 52px;
22 | background: #fff;
23 | border-bottom: 1px solid #e4eaee;
24 | }
25 | .content_table {
26 | padding: 20px;
27 | height: calc(100% - 52px);
28 | overflow: auto;
29 | input {
30 | font-size: 12px;
31 | color: #666;
32 | }
33 | span {
34 | padding-left: 0px;
35 | padding-right: 10px;
36 | }
37 | table {
38 | border-collapse: collapse;
39 | width: 100%;
40 | table-layout: fixed;
41 | thead {
42 | font-weight: 700;
43 | word-wrap: normal;
44 | text-overflow: ellipsis;
45 | line-height: 30px;
46 | vertical-align: middle;
47 | }
48 | tbody {
49 | text-align: center;
50 | }
51 | tfoot {
52 | th {
53 | height: 0;
54 | }
55 | }
56 | th,
57 | td {
58 | border: 1px solid #e5e5e5;
59 | vertical-align: middle;
60 | box-sizing: border-box;
61 | padding: 0 5px;
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | overflow: hidden;
65 | height: 35px;
66 | }
67 | th {
68 | background-color: #f6f6f6;
69 | }
70 | tr:nth-child(even) {
71 | background: rgba(246, 246, 246, 0.50);
72 | }
73 | }
74 | }
75 | }
76 | }
77 | button {
78 | min-width: 74px;
79 | height: 28px;
80 | line-height: 26px;
81 | padding: 0 12px;
82 | font-size: 12px;
83 | border: 1px solid #d9d9d9;
84 | background-color: #FFF;
85 | transition-property: border-color,background-color,color,opacity;
86 | transition-duration: .2s;
87 | border-radius: 4px;
88 | cursor: pointer;
89 | &:hover {
90 | background-color: #f8f8f8;
91 | }
92 | &:focus {
93 | outline: none;
94 | }
95 | &.primary {
96 | border-color: transparent;
97 | background-color: #3998fc;
98 | color: #fff;
99 | &:hover {
100 | background-color: #3389e3;
101 | }
102 | }
103 | }
104 | input {
105 | min-width: 120px;
106 | height: 28px;
107 | line-height: 26px;
108 | border-radius: 4px;
109 | padding: 0 9px;
110 | vertical-align: middle;
111 | border: 1px solid #d9d9d9;
112 | background-color: #fff;
113 | color: #666;
114 | transition: border-color .2s,color .2s;
115 | font-size: 12px;
116 | &:focus {
117 | outline: none;
118 | border-color: #3998fc;
119 | }
120 | }
--------------------------------------------------------------------------------
/src/assets/style/reset.less:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | body,
3 | html {
4 | width: 100%;
5 | height: 100%;
6 | line-height: 1;
7 | font-size: 12px;
8 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, "sans-serif";
9 | background-color: #f4f8f9;
10 | color: #666;
11 | overflow: hidden;
12 | }
13 |
14 | * {
15 | margin: 0;
16 | padding: 0;
17 | -webkit-tap-highlight-color: rgba(0,0,0,0);
18 | -webkit-touch-callout: none;
19 | box-sizing: border-box;
20 | }
--------------------------------------------------------------------------------
/src/components/Headerbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
33 |
--------------------------------------------------------------------------------
/src/components/composables/index.ts:
--------------------------------------------------------------------------------
1 | import { ref, onMounted } from 'vue';
2 | import {ItemType, FetchType, DeleteType, AddType, EditType} from '../../types/index';
3 |
4 | export const compositionApi = (
5 | fetchApi: FetchType,
6 | deleteApi: DeleteType,
7 | confirmAddApi: AddType,
8 | confirmEditApi: EditType,
9 | itemData: ItemType,
10 | ) => {
11 | const currentIndex = ref(null);
12 | const list = ref([{}]);
13 | const getList = () => {
14 | fetchApi().then((res: any) => {
15 | list.value = res.data.list;
16 | });
17 | };
18 | const addItem = () => {
19 | list.value.unshift(itemData);
20 | currentIndex.value = 0;
21 | };
22 | const editItem = (index: number) => {
23 | currentIndex.value = index;
24 | };
25 | const deleteItem = (index: number, item: ItemType) => {
26 | deleteApi(item).then(() => {
27 | list.value.splice(index, 1);
28 | // getList();
29 | });
30 | };
31 | const cancel = (item: ItemType) => {
32 | currentIndex.value = null;
33 | if (!item.id) {
34 | list.value.splice(0, 1);
35 | }
36 | };
37 | const confirm = (item: ItemType) => {
38 | const api = item.id ? confirmEditApi : confirmAddApi;
39 | api(item).then(() => {
40 | getList();
41 | cancel(item);
42 | });
43 | };
44 | onMounted(() => {
45 | getList();
46 | });
47 | return {
48 | list,
49 | currentIndex,
50 | getList,
51 | addItem,
52 | editItem,
53 | deleteItem,
54 | cancel,
55 | confirm,
56 | };
57 | };
58 |
59 | export default compositionApi;
60 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 | import './assets/style/reset.less';
6 | import './assets/style/common.less';
7 | import mock from './mock/index';
8 |
9 | mock.bootstrap();
10 |
11 | const app = createApp(App);
12 |
13 | app.use(store).use(router).mount('#app');
14 |
--------------------------------------------------------------------------------
/src/mock/data/about.ts:
--------------------------------------------------------------------------------
1 | import Mock from 'mockjs';
2 | import {ItemType} from '../../types/index';
3 | const About: ItemType[] = [];
4 |
5 | for (let i = 0; i < 10; i++) {
6 | About.push(Mock.mock({
7 | id: Mock.Random.guid(),
8 | users: Mock.Random.cname(),
9 | date: Mock.Random.date(),
10 | }));
11 | }
12 | export { About };
13 |
--------------------------------------------------------------------------------
/src/mock/data/user.ts:
--------------------------------------------------------------------------------
1 | import Mock from 'mockjs';
2 | import {ItemType} from '../../types/index';
3 | const Users: ItemType[] = [];
4 |
5 | for (let i = 0; i < 10; i++) {
6 | Users.push(Mock.mock({
7 | id: Mock.Random.guid(),
8 | name: Mock.Random.cname(),
9 | birth: Mock.Random.date(),
10 | sex: Mock.Random.integer(0, 1),
11 | address: Mock.mock('@county(true)'),
12 | 'age|18-30': 1,
13 | }));
14 | }
15 | export { Users };
16 |
--------------------------------------------------------------------------------
/src/mock/index.ts:
--------------------------------------------------------------------------------
1 | import mock from './service';
2 |
3 | export default mock;
4 |
--------------------------------------------------------------------------------
/src/mock/service.ts:
--------------------------------------------------------------------------------
1 | import MockAdapter from 'axios-mock-adapter';
2 | import axios from 'axios';
3 | import { Users } from './data/user';
4 | import { About } from './data/about';
5 |
6 |
7 | export default {
8 | bootstrap() {
9 | const mock = new MockAdapter(axios);
10 | mock.onGet('/users').reply(200, {
11 | list: Users,
12 | });
13 | mock.onGet('/about').reply(200, {
14 | list: About,
15 | });
16 | mock.onPost('/users/delete').reply((config) => {
17 | console.log(config);
18 | return new Promise((resolve) => {
19 | resolve([200, { code: 200, msg: '删除成功' }]);
20 | });
21 | });
22 | mock.onPost('/users/add').reply((config) => {
23 | console.log(config);
24 | return new Promise((resolve) => {
25 | resolve([200, { code: 200, msg: '添加成功' }]);
26 | });
27 | });
28 | mock.onPost('/users/edit').reply((config) => {
29 | console.log(config);
30 | return new Promise((resolve) => {
31 | resolve([200, { code: 200, msg: '编辑成功' }]);
32 | });
33 | });
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
2 | import Home from '../views/Home.vue';
3 |
4 | const routes: Array = [
5 | {
6 | path: '/',
7 | name: 'Home',
8 | component: Home,
9 | },
10 | {
11 | path: '/about',
12 | name: 'About',
13 | // route level code-splitting
14 | // this generates a separate chunk (about.[hash].js) for this route
15 | // which is lazy-loaded when the route is visited.
16 | component: () => { return import(/* webpackChunkName: "about" */ '../views/About.vue'); },
17 | },
18 | {
19 | path: '/list',
20 | name: 'List',
21 | component: () => { return import(/* webpackChunkName: "list" */ '../views/List.vue'); },
22 | },
23 | ];
24 |
25 | const router = createRouter({
26 | history: createWebHashHistory(),
27 | routes,
28 | });
29 |
30 | export default router;
31 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 |
4 | const component: DefineComponent<{}, {}, any>;
5 | export default component;
6 | }
7 |
--------------------------------------------------------------------------------
/src/source.d.ts:
--------------------------------------------------------------------------------
1 | declare const Vue: string;
2 | declare module '*.json';
3 | declare module '*.png';
4 | declare module '*.jpg';
5 | declare module 'api';
6 | declare module 'axios';
7 | declare module 'mockjs';
8 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex';
2 |
3 | export default createStore({
4 | state: {
5 | },
6 | mutations: {
7 | },
8 | actions: {
9 | },
10 | modules: {
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface ItemType {
2 | id?: number | string;
3 | name?: string;
4 | birth?: string;
5 | sex?: number;
6 | address?: string;
7 | users?: string;
8 | date?: string;
9 | [propName: string]: any;
10 | }
11 |
12 | export interface FetchType {
13 | () : Promsise
14 | }
15 |
16 | export interface DeleteType {
17 | (item: ItemType) : Promise
18 | }
19 |
20 | export interface AddType {
21 | (item: ItemType) : Promise
22 | }
23 |
24 | export interface EditType {
25 | (item: ItemType) : Promise
26 | }
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
60 |
61 |
116 |
126 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello, vue3
4 |
5 |
6 |
7 |
14 |
19 |
--------------------------------------------------------------------------------
/src/views/List.vue:
--------------------------------------------------------------------------------
1 |
2 |
77 |
78 |
139 |
149 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------