Browse Source

测试commit5

master
guoapeng 5 months ago
parent
commit
a092c6c625
  1. 67
      .eslintrc
  2. BIN
      .husky/commit-msg
  3. 1
      .husky/pre-commit
  4. 23
      README.md
  5. BIN
      commitlint.config.js
  6. 39
      package.json
  7. 3768
      pnpm-lock.yaml
  8. BIN
      public/favicon.ico
  9. 5
      src/apis/system.ts
  10. 10
      src/components/common/FTTable/expand.ts
  11. 199
      src/components/common/FTTable/index.vue
  12. 10
      src/env.d.ts
  13. 4
      src/libs/constant.ts
  14. 154
      src/libs/http.ts
  15. 20
      src/libs/token.ts
  16. 28
      src/main.ts
  17. 37
      src/router/index.ts
  18. 30
      src/router/routes.ts
  19. 12
      src/stores/useMainStore.ts
  20. 15
      src/views/login/index.vue
  21. 2
      src/views/main/index.vue
  22. 4
      src/views/page/index.vue
  23. 30
      tsconfig.json
  24. 2
      vite.config.ts

67
.eslintrc

@ -1,67 +0,0 @@
{
"root": true,
"env": {
"browser": true,
"node": true,
"vue/setup-compiler-macros": true,
"es2021": true
},
"globals": {
"defineEmits": "readonly",
"defineProps": "readonly",
"defineExpose": "readonly"
},
"parser": "vue-eslint-parser",
"extends": [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier",
"./.eslintrc-auto-import.json"
],
"parserOptions": {
"ecmaVersion": 12,
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["vue", "@typescript-eslint", "prettier"],
"rules": {
"no-explicit-any": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-var": "error",
"prettier/prettier": "error",
"vue/multi-word-component-names": 0,
"camelcase": 2,
"consistent-this": [2, "that"],
"eqeqeq": 2,
"no-await-in-loop": 0,
"consistent-return": 0,
"no-plusplus": 0,
"no-unused-expressions": 0,
"operator-linebreak": 0,
"comma-dangle": [
2,
{
"arrays": "ignore",
"objects": "ignore",
"imports": "never",
"exports": "never",
"functions": "ignore"
}
]
}
}

BIN
.husky/commit-msg

1
.husky/pre-commit

@ -1 +0,0 @@
npm run lint:lint-staged

23
README.md

@ -0,0 +1,23 @@
### 前端项目模板
##### 文件目录
- src
- components 组件目录
- apis 接口文件目录
- assets 静态资源目录
- hooks hooks目录
- libs 公共工具类
- router 页面路由
- stores 项目公共状态管理
- types 类型申明
- views 页面目录
##### 项目运行
项目采用pnpm 进行依赖构建,请先安装pnpm
[pnpm - 速度快、节省磁盘空间的软件包管理器](https://pnpm.io/)
```bash
npm install pnpm -g
```
```bash
pnpm install // 安装依赖
pnpm dev // 运行项目
```

BIN
commitlint.config.js

39
package.json

@ -1,7 +1,11 @@
{
"name": "template-web",
"type": "module",
"version": "1.0.0",
"description": "",
"author": "",
"license": "ISC",
"keywords": [],
"main": "index.js",
"scripts": {
"dev": "vite --mode dev",
@ -12,11 +16,11 @@
"build:prod": "vite build --mode prod",
"build:dev": "vite build --mode dev",
"prepare": "husky",
"lint:lint-staged": "lint-staged"
"lint:lint-staged": "lint-staged",
"lint": "vue-tsc --noEmit --skipLibCheck && eslint",
"eslint": "eslint --fix --ext .ts,.vue src",
"prettier": "prettier --write ."
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.8.1",
"element-plus": "^2.9.5",
@ -26,12 +30,18 @@
"vue-router": "^4.5.0"
},
"devDependencies": {
"@antfu/eslint-config": "^4.3.0",
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@types/node": "^22.13.5",
"@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^8.25.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.2.0",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.0.2",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-vue": "^9.32.0",
"husky": "^9.1.7",
"lint-staged": "^15.4.3",
"prettier": "^3.5.2",
@ -41,6 +51,7 @@
"unplugin-vue-components": "^28.4.1",
"vite": "^6.2.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vue-tsc": "^2.2.4"
},
"husky": {
@ -49,26 +60,10 @@
}
},
"lint-staged": {
"src/**/*.{js,jsx,vue}": [
"eslint --fix",
"prettier --write",
"lint-staged",
"git add"
],
"*.{cjs,json}": [
"prettier --write"
],
"*.{vue,html}": [
"*.{js,jsx,vue,ts,tsx}": [
"eslint --fix",
"prettier --write",
"stylelint --fix"
],
"*.{scss,css}": [
"stylelint --fix",
"prettier --write"
],
"*.md": [
"prettier --write"
"lint-staged"
]
}
}

3768
pnpm-lock.yaml
File diff suppressed because it is too large
View File

BIN
public/favicon.ico

5
src/apis/system.ts

@ -1,9 +1,8 @@
import http from 'libs/http';
import {AxiosResponse} from "axios";
import http from 'libs/http'
/**
*
* @param params
*/
export const login = (params : {username:string; password:string}) => http.post('/auth/login', params);
export const login = (params: { username: string, password: string }) => http.post('/auth/login', params)

10
src/components/common/FTTable/expand.ts

@ -1,7 +1,7 @@
export default {
props: ['row', 'render', 'index', 'column'],
inheritAttrs: false,
setup(props: any) {
return () => props.render(props.row);
},
props: ['row', 'render', 'index', 'column'],
inheritAttrs: false,
setup(props: any) {
return () => props.render(props.row)
},
}

199
src/components/common/FTTable/index.vue

@ -1,110 +1,52 @@
<template>
<el-main>
<div v-if="hasHeader" class="header">
<div v-for="btn in btnList">
<el-button
:icon="btn.icon"
:type="btn.type"
@click="methodParent(btn.serverUrl)"
>
{{ btn.name }}
</el-button>
</div>
<div class="search">
<el-input>
<template #suffix>
<el-icon class="el-input__icon"><search /></el-icon>
</template>
</el-input>
</div>
</div>
<el-table
:="attrs"
:data="state.tableData"
style="width: 100%"
height="calc(100% - 100px)"
v-loading="state.loading"
:highlight-current-row="true"
class="container-table"
header-row-class-name="header-row-class"
>
<template
v-for="(column, index) in columns"
:key="column.key"
>
<el-table-column
show-overflow-tooltip
:prop="column.key"
:label="column.title"
:width="column.width"
:type="column.type"
:fixed="column.fixed"
>
<template v-if="column.render" #default="scope">
<Expand
:column="column"
:row="scope.row"
:render="column.render"
:index="index"
/>
</template>
</el-table-column>
</template>
</el-table>
</el-main>
</template>
<script setup lang="ts">
import Expand from './expand';
import {onMounted, reactive, useAttrs, VNode} from "vue";
import type { VNode } from 'vue'
import { onMounted, reactive } from 'vue'
import Expand from './expand'
defineOptions({
name: 'xn-table'
name: 'FtTable',
})
const props = withDefaults(defineProps<TableProp>(), {
columns: () => [],
mustInit: true,
hasHeader: true,
})
const emits = defineEmits([])
enum ColumnType {
'index' = 'index',
'selection' = 'selection',
'expand' = 'expand',
index = 'index',
selection = 'selection',
expand = 'expand',
}
interface TableColumn {
title: string;
key: string;
type?:ColumnType;
width?: number; //
fixed?: 'left' | 'right' | undefined; //
render?: (row: any) => VNode; //
title: string
key: string
type?: ColumnType
width?: number //
fixed?: 'left' | 'right' | undefined //
render?: (row: any) => VNode //
}
interface Btn {
name: string;
icon?: string;
type?: string;
serverUrl: string;
name: string
icon?: string
type?: string
serverUrl: string
}
interface TableProp {
columns: TableColumn[],
getDataFn:(params: any) => Promise<any>; //
mustInit?: boolean; //mountedgetDataFn
hasHeader?: boolean;
btnList?: Btn[];
columns: TableColumn[]
getDataFn: (params: any) => Promise<any> //
mustInit?: boolean // mountedgetDataFn
hasHeader?: boolean
btnList?: Btn[]
}
const props = withDefaults(defineProps<TableProp>(), {
columns: () => [],
mustInit: true,
hasHeader: true
})
const emits = defineEmits([])
const attrs = useAttrs();
// const attrs = useAttrs()
const methodParent = async (fn: any) => {
const newFn = fn[0] === '/' ? fn.slice(1) : fn;
emits(newFn as never);
};
async function methodParent(fn: any) {
const newFn = fn[0] === '/' ? fn.slice(1) : fn
emits(newFn as never)
}
onMounted(() => {
if (props.mustInit) {
@ -116,25 +58,72 @@ const state = reactive({
loading: false,
dataTotal: 0,
tableData: [],
});
})
const initData = () => {
state.loading = true;
props.getDataFn({}).then((data) => {
console.log(data)
state.tableData = data;
state.loading = false;
}).finally(() => {
state.loading = false;
})
function initData() {
state.loading = true
props
.getDataFn({})
.then((data) => {
console.log(data)
state.tableData = data
state.loading = false
})
.finally(() => {
state.loading = false
})
}
defineExpose({
initData
initData,
})
</script>
<template>
<el-main>
<div v-if="hasHeader" class="header">
<div v-for="btn in btnList" :key="btn.serverUrl">
<el-button :icon="btn.icon" :type="btn.type" @click="methodParent(btn.serverUrl)">
{{ btn.name }}
</el-button>
</div>
<div class="search">
<el-input>
<template #suffix>
<el-icon class="el-input__icon">
<search />
</el-icon>
</template>
</el-input>
</div>
</div>
<el-table
v-loading="state.loading"
:="$attrs"
:data="state.tableData"
style="width: 100%"
height="calc(100% - 100px)"
:highlight-current-row="true"
class="container-table"
header-row-class-name="header-row-class"
>
<template v-for="(column, index) in columns" :key="column.key">
<el-table-column
show-overflow-tooltip
:prop="column.key"
:label="column.title"
:width="column.width"
:type="column.type"
:fixed="column.fixed"
>
<template v-if="column.render" #default="scope">
<Expand :column="column" :row="scope.row" :render="column.render" :index="index" />
</template>
</el-table-column>
</template>
</el-table>
</el-main>
</template>
<style lang="scss" scoped>
.header {
display: flex;
@ -153,4 +142,4 @@ defineExpose({
color: #000;
}
}
</style>
</style>

10
src/env.d.ts

@ -1,6 +1,6 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

4
src/libs/constant.ts

@ -1,5 +1,5 @@
// 请求头里token的名称
export const HEADER_TOKEN_KEY = 'Authorization';
export const HEADER_TOKEN_KEY = 'Authorization'
// sessionStorage里token的名称
export const SESSIONSTORAGE_TOKEN_KEY = 'web_token';
export const SESSIONSTORAGE_TOKEN_KEY = 'web_token'

154
src/libs/http.ts

@ -1,93 +1,99 @@
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { getToken } from 'libs/token';
import { HEADER_TOKEN_KEY } from '@/libs/constant';
import { HEADER_TOKEN_KEY } from '@/libs/constant'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { getToken } from 'libs/token'
const http = axios.create({
baseURL: `/api`,
timeout: 1000 * 60
});
baseURL: `/api`,
timeout: 1000 * 60,
})
// 请求拦截器
http.interceptors.request.use(
(config) => {
if (getToken()) {
config.headers![HEADER_TOKEN_KEY] = getToken();
}
return config;
},
(error: any) => {
return Promise.reject(error);
(config) => {
if (getToken()) {
config.headers![HEADER_TOKEN_KEY] = getToken()
}
);
return config
},
(error: any) => {
return Promise.reject(error)
},
)
// 响应拦截器
http.interceptors.response.use(
(response) => {
if (
response.status === 200 &&
response.data.data !== undefined &&
!response.data.data
) {
// 返回错误拦截
ElMessage.error(response.data.msg);
return Promise.reject(response);
} else if (
response.config.url?.includes('/files/download') ||
response.config.url?.includes('downloadStream')
) {
return response.data;
} else if (response.data instanceof Blob) {
return response.data;
} else if (
response.config.url?.includes('/espAnalysis/generateReport') ||
response.config.url?.includes('/dcDataQuery/downLoadData')
) {
return response;
}
return response.data.data; // 返回数据体
},
(error: any) => {
console.log(error);
if (error.response && error.response.status === 401) {
ElMessage.error('账号权限过期');
// TODO 登出
} else {
if (error.message.indexOf('timeout') > -1) {
ElMessage.error('请求超时');
} else if (error.message.indexOf('Network') > -1) {
ElMessage.error('网络连接错误');
} else {
ElMessage.error(error.message);
}
error.response = {
data: {
res: false
}
};
return Promise.reject(error.response);
}
(response) => {
if (
response.status === 200
&& response.data.data !== undefined
&& !response.data.data
) {
// 返回错误拦截
ElMessage.error(response.data.msg)
return Promise.reject(response)
}
);
else if (
response.config.url?.includes('/files/download')
|| response.config.url?.includes('downloadStream')
) {
return response.data
}
else if (response.data instanceof Blob) {
return response.data
}
else if (
response.config.url?.includes('/espAnalysis/generateReport')
|| response.config.url?.includes('/dcDataQuery/downLoadData')
) {
return response
}
return response.data.data // 返回数据体
},
(error: any) => {
console.log(error)
if (error.response && error.response.status === 401) {
ElMessage.error('账号权限过期')
// TODO 登出
}
else {
if (error.message.includes('timeout')) {
ElMessage.error('请求超时')
}
else if (error.message.includes('Network')) {
ElMessage.error('网络连接错误')
}
else {
ElMessage.error(error.message)
}
error.response = {
data: {
res: false,
},
}
return Promise.reject(error.response)
}
},
)
// 封装 GET 请求
export const get = <T>(url: string, params?: any): Promise<T> => {
return http.get(url, { params });
};
export function get<T>(url: string, params?: any): Promise<T> {
return http.get(url, { params })
}
// 封装 POST 请求
export const post = <T>(url: string, data?: any): Promise<T> => {
return http.post(url, data);
};
export function post<T>(url: string, data?: any): Promise<T> {
return http.post(url, data)
}
// 封装 PUT 请求
export const put = <T>(url: string, data?: any): Promise<T> => {
return http.put(url, data);
};
export function put<T>(url: string, data?: any): Promise<T> {
return http.put(url, data)
}
// 封装 DELETE 请求
export const del = <T>(url: string, params?: any): Promise<T> => {
return http.delete(url, { params });
};
export function del<T>(url: string, params?: any): Promise<T> {
return http.delete(url, { params })
}
export default http;
export default http

20
src/libs/token.ts

@ -1,13 +1,13 @@
import { SESSIONSTORAGE_TOKEN_KEY } from './constant';
import { SESSIONSTORAGE_TOKEN_KEY } from './constant'
export const getToken = () => {
return sessionStorage[SESSIONSTORAGE_TOKEN_KEY];
};
export function getToken() {
return sessionStorage[SESSIONSTORAGE_TOKEN_KEY]
}
export const setToken = (token: string) => {
sessionStorage[SESSIONSTORAGE_TOKEN_KEY] = token;
};
export function setToken(token: string) {
sessionStorage[SESSIONSTORAGE_TOKEN_KEY] = token
}
export const delToken = () => {
sessionStorage.removeItem(SESSIONSTORAGE_TOKEN_KEY);
};
export function delToken() {
sessionStorage.removeItem(SESSIONSTORAGE_TOKEN_KEY)
}

28
src/main.ts

@ -1,17 +1,17 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './app.vue';
import router from './router';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import locale from 'element-plus/es/locale/lang/zh-cn'
import FtTable from 'components/common/FTTable/index.vue'
import ElementPlus from 'element-plus'
import locale from 'element-plus/es/locale/lang/zh-cn'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './app.vue'
import router from './router'
import 'element-plus/dist/index.css'
const app = createApp(App);
const pinia = createPinia();
app.component('FtTable', FtTable);
const app = createApp(App)
const pinia = createPinia()
app.component('FtTable', FtTable)
app
.use(router)
.use(pinia)
.use(ElementPlus, { locale: locale, zIndex: 3000 })
.mount('#app')
.use(router)
.use(pinia)
.use(ElementPlus, { locale, zIndex: 3000 })
.mount('#app')

37
src/router/index.ts

@ -1,23 +1,26 @@
import { createRouter, createWebHashHistory, RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { getToken } from '@/libs/token';
import routes from './routes';
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { getToken } from '@/libs/token'
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes'
const router = createRouter({
history: createWebHashHistory(),
routes
});
history: createWebHashHistory(),
routes,
})
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (getToken()) {
next();
} else {
// 未登录
if (to.name === 'login') {
next();
} else {
next({ name: 'login' });
}
if (getToken()) {
next()
}
else {
// 未登录
if (to.name === 'login') {
next()
}
});
else {
next({ name: 'login' })
}
}
})
export default router;
export default router

30
src/router/routes.ts

@ -1,28 +1,28 @@
import { RouteRecordRaw } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router'
const authRoutes: RouteRecordRaw[] = [
{
path: '',
name: 'home',
component: () => import('../views/page/index.vue'),
meta: {
isDefault: true,
tagName: '首页'
}
path: '',
name: 'home',
component: () => import('../views/page/index.vue'),
meta: {
isDefault: true,
tagName: '首页',
},
},
];
]
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'login',
component: () => import('../views/login/index.vue')
component: () => import('../views/login/index.vue'),
},
{
path: '/',
component: () => import('../views/main/index.vue'),
children: authRoutes
}
];
children: authRoutes,
},
]
export { authRoutes };
export default routes;
export { authRoutes }
export default routes

12
src/stores/useMainStore.ts

@ -1,13 +1,13 @@
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main',{
state:()=>{
export const useMainStore = defineStore('main', {
state: () => {
return {
userInfo: {
userName: '',
}
},
}
},
getters:{},
actions:{}
})
getters: {},
actions: {},
})

15
src/views/login/index.vue

@ -1,17 +1,16 @@
<script setup lang="ts">
import {setToken} from "libs/token";
import router from "@/router";
import {login} from "apis/system";
import router from '@/router'
import { login } from 'apis/system'
import { setToken } from 'libs/token'
login({username: 'admin', password: '12345'}).then((res) => {
console.log(res)
// setToken(res.data)
login({ username: 'admin', password: '12345' }).then((res) => {
setToken(res.data)
router.push('/')
})
</script>
<template>
<div>登录</div>
</template>
<style scoped></style>

2
src/views/main/index.vue

@ -6,7 +6,7 @@
<el-main>
<Transition name="in-out">
<router-view v-slot="{ Component }" class="main-content">
<component :is="Component" :key="$route.fullPath" />
<component :is="Component" :key="$route.fullPath" />
</router-view>
</Transition>
</el-main>

4
src/views/page/index.vue

@ -3,9 +3,9 @@
</script>
<template>
页面1
页面1
</template>
<style scoped>
</style>
</style>

30
tsconfig.json

@ -1,22 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"composite": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"target": "esnext",
"jsx": "preserve",
"jsxFactory": "h",
"noImplicitThis": false,
"jsxFragmentFactory": "Fragment",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"lib": ["esnext", "dom"],
"useDefineForClassFields": true,
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "node",
"paths": {
"@/*": ["src/*"],
"apis/*": ["src/apis/*"],
@ -27,10 +20,17 @@
"libs/*": ["src/libs/*"],
"store/*": ["src/store/*"],
"views/*": ["src/views/*"]
}
},
"resolveJsonModule": true,
"strict": true,
"strictPropertyInitialization": false,
"noImplicitThis": false,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"types": ["vite/client", "jest", "node","element-plus/global.d.ts", "lodash"],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", ],
"types": ["vite/client", "jest", "node", "element-plus/global.d.ts", "lodash"],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": [
],
]
}

2
vite.config.ts

@ -76,7 +76,7 @@ export default defineConfig({
host: '0.0.0.0',
proxy: {
'/api': {
target: 'http://localhost:8080',
target: 'http://localhost:8090',
// secure: false,
changeOrigin: true, // 是否跨域
rewrite: path => path.replace(/^\/api/, 'api'),

Loading…
Cancel
Save