feat: 增加代码
This commit is contained in:
commit
9284f8029b
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
git-static.js
|
3
.eslintrc.js
Normal file
3
.eslintrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@mistjs'],
|
||||
}
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.idea
|
||||
.vscode
|
||||
*.log
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
|
||||
git-static.js
|
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install lint-staged
|
2
.npmrc
Normal file
2
.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
registry=http://10.0.59.229:5055/
|
||||
//10.0.59.229:5055/:_authToken="zkijreGTZRgAkrch3Qza8pwSpWNeg0tWTBHhnM32rsY="
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# @crami/http@2.x
|
||||
|
||||
## 开发
|
||||
|
||||
```shell
|
||||
# 安装依赖
|
||||
pnpm i
|
||||
|
||||
# 版本发布
|
||||
pnpm release
|
||||
```
|
||||
|
||||
### 版本发布注意事项
|
||||
|
||||
本项目使用自动化版本发布脚本,执行release以后,选择对应的版本号,会自动tag一个版本号。查看版本自动发布流程前往[droneCI](http://10.0.59.229:8119/),找到对应的项目即可查看。
|
12
build.config.ts
Normal file
12
build.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
],
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
externals: ['axios', 'vue'],
|
||||
declaration: true,
|
||||
})
|
6
commitlint.config.js
Normal file
6
commitlint.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-enum': [2, 'always', ['feat', 'fix', 'style', 'refactor', 'perf', 'test', 'build', 'chore', 'revert', 'release']],
|
||||
},
|
||||
}
|
58
package.json
Normal file
58
package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@crami/http",
|
||||
"version": "2.0.10",
|
||||
"packageManager": "pnpm@7.1.7",
|
||||
"description": "this is crami http",
|
||||
"keywords": [],
|
||||
"license": "ISC",
|
||||
"author": "crami-original",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"playground": "vite dev playground --port 7788 --open",
|
||||
"prepare": "husky install",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub",
|
||||
"release": "bumpp package.json --commit \"release: v\" --push --tag",
|
||||
"prepublish": "pnpm build",
|
||||
"pb": "pnpm publish --registry=http://10.0.59.229:5055/ --no-git-checks",
|
||||
"pb:b": "pnpm publish --registry=http://10.0.59.229:5055/ --no-git-checks --tag=beta"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.10.5",
|
||||
"vue": "^3.2.36"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
"@unocss/reset": "^0.37.4",
|
||||
"@commitlint/config-conventional": "^17.0.2",
|
||||
"@iconify-json/carbon": "^1.1.5",
|
||||
"@mistjs/eslint-config": "^0.0.2",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.40",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"bumpp": "^7.1.1",
|
||||
"eslint": "^8.17.0",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "^4.7.3",
|
||||
"unbuild": "^0.7.4",
|
||||
"unocss": "^0.37.4",
|
||||
"vite": "^2.9.9",
|
||||
"vitest": "^0.14.0",
|
||||
"vue-tsc": "^0.35.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,vue}": "eslint src --fix"
|
||||
}
|
||||
}
|
28
playground/.gitignore
vendored
Normal file
28
playground/.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
40
playground/README.md
Normal file
40
playground/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# playground
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
1
playground/env.d.ts
vendored
Normal file
1
playground/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
17
playground/index.html
Normal file
17
playground/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Crami Http</title>
|
||||
<style>
|
||||
html{
|
||||
background: #121212;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
13
playground/src/App.vue
Normal file
13
playground/src/App.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import GetComp from './components/get.vue'
|
||||
import PostComp from './components/post.vue'
|
||||
import Upload from './components/upload.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<get-comp />
|
||||
<post-comp />
|
||||
<Upload />
|
||||
</div>
|
||||
</template>
|
10
playground/src/api/index.ts
Normal file
10
playground/src/api/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface RequestParams{
|
||||
username?: string
|
||||
password?: string
|
||||
}
|
||||
export interface ResponseData{
|
||||
data?: any
|
||||
success?: boolean
|
||||
code: number
|
||||
msg: string
|
||||
}
|
74
playground/src/assets/base.css
Normal file
74
playground/src/assets/base.css
Normal file
@ -0,0 +1,74 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
1
playground/src/assets/logo.svg
Normal file
1
playground/src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 308 B |
49
playground/src/components/get.vue
Normal file
49
playground/src/components/get.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import { Http, useGet, useHttp } from '@crami/http'
|
||||
import type { ResponseData } from '~/api'
|
||||
|
||||
const handleGet = async() => {
|
||||
const data = await Http.get<ResponseData>({
|
||||
url: '/api/table/columns',
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const handleUseHttp = async() => {
|
||||
const data = await useHttp<ResponseData>({
|
||||
url: '/api/table/columns',
|
||||
params: {
|
||||
aaa: 'SAdasdas',
|
||||
nnn: 'dasdasd',
|
||||
},
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const useGetData = async() => {
|
||||
const data = await useGet('/api/table/columns')
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div p-3>
|
||||
<div text-white mb-2>
|
||||
Get类型请求测试
|
||||
</div>
|
||||
<div flex="~ wrap gap-2">
|
||||
<button btn @click="handleGet">
|
||||
Http测试请求
|
||||
</button>
|
||||
<button btn @click="handleUseHttp">
|
||||
useHttp请求测试
|
||||
</button>
|
||||
<button btn @click="useGetData">
|
||||
useGet请求测试
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
54
playground/src/components/post.vue
Normal file
54
playground/src/components/post.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<script lang="ts" setup>
|
||||
import { Http, useHttp, usePost } from '@crami/http'
|
||||
import type { RequestParams, ResponseData } from '~/api'
|
||||
|
||||
const handleGet = async() => {
|
||||
const data = await Http.post<ResponseData>({
|
||||
url: '/api/date/table/data',
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const handleUseHttp = async() => {
|
||||
const data = await useHttp<ResponseData>({
|
||||
url: '/api/date/table/data',
|
||||
method: 'POST',
|
||||
data: [
|
||||
'1', '2',
|
||||
],
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const useGetData = async() => {
|
||||
const data = await usePost<ResponseData, RequestParams>(
|
||||
'/api/date/table/data',
|
||||
{
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div p-3>
|
||||
<div text-white mb-2>
|
||||
Post类型请求测试
|
||||
</div>
|
||||
<div flex="~ wrap gap-2">
|
||||
<button btn @click="handleGet">
|
||||
Http测试请求
|
||||
</button>
|
||||
<button btn @click="handleUseHttp">
|
||||
useHttp请求测试
|
||||
</button>
|
||||
<button btn @click="useGetData">
|
||||
usePost请求测试
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
31
playground/src/components/upload.vue
Normal file
31
playground/src/components/upload.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { Http } from '@crami/http'
|
||||
import { ref } from 'vue'
|
||||
const file = ref<File>()
|
||||
const handleChange = (e: any) => {
|
||||
// console.log(e.target.files)
|
||||
file.value = e.target.files[0]
|
||||
}
|
||||
const handleGet = () => {
|
||||
Http.uploadFile({
|
||||
url: '/api/table/columns',
|
||||
}, {
|
||||
filename: 'test.txt',
|
||||
file: file.value,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div p-3>
|
||||
<div text-white mb-2>
|
||||
文件上传测试
|
||||
</div>
|
||||
<div flex="~ wrap gap-2">
|
||||
<input type="file" @change="handleChange">
|
||||
<button btn @click="handleGet">
|
||||
文件上传测试
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
34
playground/src/main.ts
Normal file
34
playground/src/main.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createAxios, createHttp, icpxTransform, icpxTransformConfig } from '@crami/http'
|
||||
import App from './App.vue'
|
||||
import '@unocss/reset/tailwind.css'
|
||||
import 'uno.css'
|
||||
|
||||
const axios = createAxios({
|
||||
baseURL: '/',
|
||||
})
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
return config
|
||||
})
|
||||
|
||||
axios.interceptors.response.use((response) => {
|
||||
return response.data
|
||||
})
|
||||
|
||||
// axios.interceptors.request = function() {
|
||||
//
|
||||
// }
|
||||
const http = createHttp(axios, {
|
||||
responseTransform: icpxTransform,
|
||||
requestTransform: icpxTransformConfig,
|
||||
headers: () => ({
|
||||
token: 'this is my token',
|
||||
}),
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(http)
|
||||
|
||||
app.mount('#app')
|
38
playground/unocss.config.ts
Normal file
38
playground/unocss.config.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss'
|
||||
|
||||
export default defineConfig({
|
||||
shortcuts: [
|
||||
['btn', 'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
|
||||
['icon-btn', 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'],
|
||||
],
|
||||
presets: [
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetIcons({
|
||||
scale: 1.2,
|
||||
warn: true,
|
||||
}),
|
||||
presetTypography(),
|
||||
presetWebFonts({
|
||||
fonts: {
|
||||
sans: 'DM Sans',
|
||||
serif: 'DM Serif Display',
|
||||
mono: 'DM Mono',
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformers: [
|
||||
transformerDirectives(),
|
||||
transformerVariantGroup(),
|
||||
],
|
||||
safelist: 'prose prose-sm m-auto text-left'.split(' '),
|
||||
})
|
30
playground/vite.config.ts
Normal file
30
playground/vite.config.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { URL, fileURLToPath } from 'url'
|
||||
import Unocss from 'unocss/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
reactivityTransform: true,
|
||||
}),
|
||||
Unocss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@crami/http': fileURLToPath(new URL('../src', import.meta.url)),
|
||||
'~/': fileURLToPath(new URL('./src/', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://10.0.59.229:7300/mock/622ff143ec91f63c580c0560',
|
||||
rewrite: (path) => {
|
||||
return path.replace(/^\/api/, '')
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
5212
pnpm-lock.yaml
generated
Normal file
5212
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
src/composables/http.ts
Normal file
64
src/composables/http.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import type { BaseApi } from '../typing'
|
||||
import { Http } from '../constant'
|
||||
|
||||
type RequestBaseApiType = Omit<BaseApi, 'url'|'params'|'method'|'data'>
|
||||
function useHttp <R=any, D=any>(api: BaseApi<D>): Promise<R> {
|
||||
return Http.request<R, D>(api)
|
||||
}
|
||||
|
||||
function useRequest<R, D = any>(url: string, options?: Omit<BaseApi<D>, 'url'>): Promise<R> {
|
||||
return useHttp<R, D>({
|
||||
...(options || {}),
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
function useGet<R, D = any>(url: string, params?: D, options?: RequestBaseApiType): Promise<R> {
|
||||
return Http.get<R, D>({
|
||||
...(options || {}),
|
||||
url,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
function usePost<R, D = any>(url: string, data?: D, options?: RequestBaseApiType): Promise<R> {
|
||||
return Http.post<R, D>({
|
||||
...(options || {}),
|
||||
url,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
function usePut<R, D = any>(url: string, data?: D, options?: RequestBaseApiType): Promise<R> {
|
||||
return Http.put<R, D>({
|
||||
...(options || {}),
|
||||
url,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
function useDelete<R, D=any>(url: string, data?: D, options?: RequestBaseApiType): Promise<R> {
|
||||
return Http.delete<R, D>({
|
||||
...(options || {}),
|
||||
url,
|
||||
data,
|
||||
})
|
||||
}
|
||||
function useUpload<R>(url: string, data?: FormData, options?: RequestBaseApiType): Promise<R> {
|
||||
return Http.upload<R>({
|
||||
...(options || {}),
|
||||
url,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
useRequest,
|
||||
useGet,
|
||||
usePost,
|
||||
usePut,
|
||||
useDelete,
|
||||
useUpload,
|
||||
}
|
||||
|
||||
export default useHttp
|
62
src/constant.ts
Normal file
62
src/constant.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { InitHttpCallback } from './init-http'
|
||||
import type { CramiHttpOptions } from './typing'
|
||||
|
||||
export interface HttpObjType extends Partial<InitHttpCallback>{
|
||||
__isMerging?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
// 去除vue中的白名单
|
||||
const ALLOW_WHITE_LIST = [
|
||||
'__isVue',
|
||||
'__v_isRef',
|
||||
'__v_isReadonly',
|
||||
'__v_isReactive',
|
||||
]
|
||||
|
||||
const ERROR_LIST = [
|
||||
'request',
|
||||
'get',
|
||||
'post',
|
||||
'put',
|
||||
'delete',
|
||||
'option',
|
||||
]
|
||||
|
||||
const httpObj: HttpObjType = {
|
||||
__isMerging: false,
|
||||
}
|
||||
export const Http: InitHttpCallback = new Proxy(
|
||||
httpObj, {
|
||||
get: (target, key) => {
|
||||
const isExist = Reflect.has(target, key)
|
||||
// 如果我们想要的属性不存在,就给他报错
|
||||
if (!isExist && !target.__isMerging && !ALLOW_WHITE_LIST.includes(String(key))) {
|
||||
if (ERROR_LIST.includes(String(key)))
|
||||
console.error(`Http.${String(key)} is not exist,maybe you forgot to import {createHttp} from "@crami/http" to init it`)
|
||||
|
||||
return undefined
|
||||
}
|
||||
return Reflect.get(target, key)
|
||||
},
|
||||
set(target, key, value) {
|
||||
return Reflect.set(target, key, value)
|
||||
},
|
||||
},
|
||||
) as InitHttpCallback
|
||||
|
||||
interface AppOptions{
|
||||
hooks: Omit<CramiHttpOptions, 'headers'>
|
||||
}
|
||||
|
||||
export const app: AppOptions = {
|
||||
hooks: {},
|
||||
}
|
||||
|
||||
export enum ContentTypeEnum {
|
||||
// json
|
||||
JSON = 'application/json;charset=UTF-8',
|
||||
// form-data qs
|
||||
FORM = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
// form-data upload
|
||||
UPLOAD = 'multipart/form-data;charset=UTF-8',
|
||||
}
|
7
src/create-axios.ts
Normal file
7
src/create-axios.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
||||
|
||||
export const createAxios = (config: AxiosRequestConfig): AxiosInstance => {
|
||||
// TODO
|
||||
return axios.create(config)
|
||||
}
|
27
src/create-http.ts
Normal file
27
src/create-http.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { AxiosInstance } from 'axios'
|
||||
import type { App, Plugin } from 'vue'
|
||||
import { merge } from 'lodash'
|
||||
import type { CramiHttpOptions } from './typing'
|
||||
import initOptions from './init-options'
|
||||
import type { InitHttpCallback } from './init-http'
|
||||
import initHttp from './init-http'
|
||||
import { Http } from './constant'
|
||||
|
||||
export const createHttp = (instance: AxiosInstance, options?: CramiHttpOptions): Plugin &{ version: string } => {
|
||||
// 初始化配置
|
||||
initOptions(instance, options)
|
||||
// 创建axios实例
|
||||
const InitHttp = initHttp(instance) as InitHttpCallback
|
||||
// 合并实例
|
||||
(Http as any).__isMerging = true
|
||||
merge(Http as any, InitHttp);
|
||||
(Http as any).__isMerging = false
|
||||
delete (Http as any).__isMerging
|
||||
// 返回需要实例化的对象的信息
|
||||
return {
|
||||
version: '2.0.0',
|
||||
install: (app: App) => {
|
||||
app.config.globalProperties.$http = Http
|
||||
},
|
||||
}
|
||||
}
|
17
src/index.ts
Normal file
17
src/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { createAxios } from './create-axios'
|
||||
import { createHttp } from './create-http'
|
||||
import { ContentTypeEnum, Http } from './constant'
|
||||
import useHttp from './composables/http'
|
||||
export * from './composables/http'
|
||||
export * from './transformer'
|
||||
export type { BaseApi, CramiHttpOptions, HttpHeaderType } from './typing'
|
||||
export type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosResponseHeaders, AxiosResponseTransformer, AxiosRequestTransformer, AxiosRequestHeaders } from 'axios'
|
||||
export {
|
||||
createAxios,
|
||||
createHttp,
|
||||
Http,
|
||||
useHttp,
|
||||
ContentTypeEnum,
|
||||
}
|
||||
|
||||
export default createHttp
|
182
src/init-http.ts
Normal file
182
src/init-http.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
||||
import qs from 'qs'
|
||||
import { ContentTypeEnum, app } from './constant'
|
||||
import { defaultTransform, defaultTransformConfig } from './transformer'
|
||||
|
||||
import type { BaseApi } from './typing'
|
||||
import { checkContentType, checkIsGet, checkParams, isString, runFunction } from './utils'
|
||||
type BaseApiNoMethod<D> = Omit<BaseApi<D>, 'method'>
|
||||
|
||||
interface UploadFileParams<D>{
|
||||
data?: D
|
||||
name?: string
|
||||
file: File | Blob
|
||||
filename?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface InitHttpCallback {
|
||||
request <R=any, D=any>(config: BaseApi<D>): Promise<R>
|
||||
get <R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R>
|
||||
post <R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R>
|
||||
put <R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R>
|
||||
form <R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R>
|
||||
upload <R=any>(config: BaseApiNoMethod<FormData>): Promise<R>
|
||||
delete <R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R>
|
||||
uploadFile <R=any, D=any>(config: Omit<BaseApiNoMethod<any>, 'params'|'data'>, params: UploadFileParams<D>): Promise<R>
|
||||
}
|
||||
|
||||
export default function initHttp(instance: AxiosInstance): InitHttpCallback {
|
||||
// 请求request
|
||||
async function request<R = any, D=any>(config: BaseApi<D>): Promise<R> {
|
||||
const { beforeRequest, afterRequest, beforeRequestConfig, beforeRequestQuery, onError, responseTransform, urlParser, requestTransform, dataType, ...restConfig } = config
|
||||
let newConfig
|
||||
// 处理请求类型
|
||||
if (dataType) {
|
||||
switch (dataType) {
|
||||
case 'form':
|
||||
restConfig.headers = {
|
||||
...restConfig.headers,
|
||||
'Content-type': ContentTypeEnum.FORM,
|
||||
}
|
||||
break
|
||||
case 'upload':
|
||||
restConfig.headers = {
|
||||
...restConfig.headers,
|
||||
'Content-type': ContentTypeEnum.UPLOAD,
|
||||
}
|
||||
break
|
||||
case 'json':
|
||||
restConfig.headers = {
|
||||
...restConfig.headers,
|
||||
'Content-type': ContentTypeEnum.JSON,
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!checkContentType(restConfig.headers as any)) {
|
||||
restConfig.headers = {
|
||||
...restConfig.headers,
|
||||
'Content-type': ContentTypeEnum.JSON,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const requestFun = requestTransform || app.hooks?.requestTransform || defaultTransformConfig
|
||||
newConfig = checkParams(requestFun(restConfig), restConfig)
|
||||
newConfig = checkParams(await runFunction<AxiosRequestConfig<D>>(app.hooks?.beforeRequestConfig, newConfig), newConfig)
|
||||
newConfig = checkParams(await runFunction<AxiosRequestConfig<D>>(beforeRequestConfig, newConfig), newConfig)
|
||||
// 拦截url的处理钩子
|
||||
newConfig.url = checkParams(await runFunction(app.hooks?.urlParser, newConfig.url), newConfig.url)
|
||||
newConfig.url = checkParams(await runFunction(urlParser, newConfig.url), newConfig.url)
|
||||
// GET 请求钩子特殊处理
|
||||
if (checkIsGet(newConfig?.method)) {
|
||||
// 全局的钩子
|
||||
newConfig.params = checkParams(await runFunction(app.hooks?.beforeRequest, newConfig.params), newConfig.params)
|
||||
// 单个请求的钩子
|
||||
newConfig.params = checkParams(await runFunction(beforeRequest, newConfig.params), newConfig.params)
|
||||
}
|
||||
else {
|
||||
// 管他存不存在的都要走这些钩子
|
||||
newConfig.data = checkParams(await runFunction(app.hooks?.beforeRequest, newConfig.data), newConfig.data)
|
||||
newConfig.data = checkParams(await runFunction(beforeRequest, newConfig.data), newConfig.data)
|
||||
// 如果存在query的参数,就直接使用
|
||||
newConfig.params = checkParams(await runFunction(app.hooks?.beforeRequestQuery, newConfig.params), newConfig.params)
|
||||
newConfig.params = checkParams(await runFunction(beforeRequestQuery, newConfig.params), newConfig.params)
|
||||
}
|
||||
|
||||
try {
|
||||
let response = await instance.request<R>(newConfig)
|
||||
// 请求后的钩子函数
|
||||
response = await checkParams(await runFunction(afterRequest, response), response)
|
||||
// transform优先级从左到右,由大到小: 接口自定义 -> 全局自定义 -> 默认
|
||||
const responseFun = responseTransform || app.hooks?.responseTransform || defaultTransform
|
||||
// 这里还可以加处理
|
||||
return responseFun(response)
|
||||
}
|
||||
catch (e) {
|
||||
app.hooks?.onError?.(e)
|
||||
// TODO
|
||||
onError?.(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// get请求
|
||||
async function get<R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R> {
|
||||
return request<R, D>({ ...config, method: 'GET' })
|
||||
}
|
||||
// POST请求
|
||||
async function post<R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R> {
|
||||
return request<R, D>({ ...config, method: 'POST' })
|
||||
}
|
||||
// Put请求
|
||||
async function put<R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R> {
|
||||
return request<R, D>({ ...config, method: 'PUT' })
|
||||
}
|
||||
// delete请求
|
||||
async function deleteFun<R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R> {
|
||||
return request<R, D>({ ...config, method: 'DELETE' })
|
||||
}
|
||||
// upload请求
|
||||
async function upload<R=any>(config: BaseApiNoMethod<FormData>): Promise<R> {
|
||||
return request<R>({
|
||||
...config,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...config.headers,
|
||||
'Content-Type': ContentTypeEnum.UPLOAD,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function uploadFile<R=any, D = any>(config: Omit<BaseApiNoMethod<any>, 'params'|'data'>, params: UploadFileParams<D>): Promise<R> {
|
||||
// 处理上传文件类型
|
||||
const formData = new FormData()
|
||||
if (params.data) {
|
||||
for (const pKey in params.data) {
|
||||
const value = params.data[pKey]
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
formData.append(`${pKey}[]`, item)
|
||||
})
|
||||
}
|
||||
else {
|
||||
formData.append(pKey, (value as any))
|
||||
}
|
||||
}
|
||||
}
|
||||
formData.append(params.name || 'file', params.file, params.filename)
|
||||
if (params.filename)
|
||||
formData.append('fileName', params.filename)
|
||||
|
||||
return upload<R>(
|
||||
{
|
||||
...config,
|
||||
data: formData,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// form请求
|
||||
async function form<R=any, D=any>(config: BaseApiNoMethod<D>): Promise<R> {
|
||||
return request<R, string>({
|
||||
...config,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...config.headers,
|
||||
'Content-Type': ContentTypeEnum.FORM,
|
||||
},
|
||||
data: isString(config.data) ? config.data : qs.stringify(config.data),
|
||||
})
|
||||
}
|
||||
return {
|
||||
request,
|
||||
get,
|
||||
delete: deleteFun,
|
||||
post,
|
||||
upload,
|
||||
form,
|
||||
put,
|
||||
uploadFile,
|
||||
}
|
||||
}
|
25
src/init-options.ts
Normal file
25
src/init-options.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { AxiosInstance } from 'axios'
|
||||
import { merge, omit } from 'lodash'
|
||||
import { ContentTypeEnum, app } from './constant'
|
||||
import type { CramiHttpOptions, HttpHeaderType } from './typing'
|
||||
|
||||
import { runFunction } from './utils'
|
||||
export default function(instance: AxiosInstance, options?: CramiHttpOptions) {
|
||||
if (!instance) {
|
||||
// 不存在instance
|
||||
console.error('[crami] createHttp: instance is required, maybe you forgot to import {createAxios} from "@crami/http"')
|
||||
return
|
||||
}
|
||||
merge(app.hooks, omit(options, ['headers']))
|
||||
// 处理headers
|
||||
if (options?.headers) {
|
||||
// 存在判断是不是一个函数
|
||||
const headers = runFunction<HttpHeaderType>(options?.headers || {})
|
||||
headers.then((res) => {
|
||||
// 设置headers
|
||||
merge(instance.defaults.headers, {
|
||||
'Content-Type': ContentTypeEnum.JSON,
|
||||
}, res)
|
||||
})
|
||||
}
|
||||
}
|
12
src/transformer/default-transform.ts
Normal file
12
src/transformer/default-transform.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import type { BaseApi } from '../typing'
|
||||
export const defaultTransform = <R=any>(response: AxiosResponse<R>): R => {
|
||||
if (response.config && response.data && response.status && response.headers && response.statusText)
|
||||
return response.data
|
||||
|
||||
return response as any
|
||||
}
|
||||
|
||||
export const defaultTransformConfig = (config: BaseApi): BaseApi => {
|
||||
return config
|
||||
}
|
71
src/transformer/icpx-transform.ts
Normal file
71
src/transformer/icpx-transform.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import type { BaseApi } from '../typing'
|
||||
import { checkIsGet, isObject } from '../utils'
|
||||
|
||||
export type ICPXResponseData<D> = {
|
||||
data: D
|
||||
code?: number
|
||||
msg?: string
|
||||
success?: boolean
|
||||
} & Record<string, any>
|
||||
|
||||
export const icpxTransform = <D>(response: AxiosResponse<D>): any => {
|
||||
if (
|
||||
response.config
|
||||
&& response.data
|
||||
&& response.status
|
||||
&& response.headers
|
||||
&& response.statusText
|
||||
) {
|
||||
const data: any = response.data
|
||||
const status = response.status
|
||||
if (status === 200) {
|
||||
if (!data?.code) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'ok',
|
||||
success: true,
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
else {
|
||||
if (!(response as any)?.code) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'ok',
|
||||
success: true,
|
||||
data: response,
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export const icpxTransformConfig = (config: BaseApi): BaseApi => {
|
||||
// 判断请求方式
|
||||
if (config.method && !checkIsGet(config.method)) {
|
||||
if (isObject(config.params)) {
|
||||
config.data = {
|
||||
...(config.data || {}),
|
||||
...(config.params || {}),
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!config.data)
|
||||
config.data = config.params
|
||||
}
|
||||
config.params = undefined
|
||||
}
|
||||
else {
|
||||
// 如果是get请求是不是需要增加一个请求参数,防止缓存请求
|
||||
const _t = Date.now()
|
||||
config.params = {
|
||||
_t,
|
||||
...(config.params),
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
2
src/transformer/index.ts
Normal file
2
src/transformer/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './default-transform'
|
||||
export * from './icpx-transform'
|
42
src/typing.ts
Normal file
42
src/typing.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
|
||||
export type HttpHeaderType = Record<string, any>
|
||||
|
||||
export interface BaseApi<D = any> extends AxiosRequestConfig<D>{
|
||||
// 请求前的钩子函数
|
||||
beforeRequest?: (params: any) => any
|
||||
// 请求前的参数处理
|
||||
beforeRequestConfig?: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||
// 请求前置的query中的参数
|
||||
beforeRequestQuery?: (query: any) => any
|
||||
// 请求失败的钩子
|
||||
onError?: (e: any) => void
|
||||
// 请求接口格式化处理
|
||||
urlParser?: (url: string) => string
|
||||
// 请求数据格式化 同步方法,不支持Promise
|
||||
responseTransform?: <R, D>(response: AxiosResponse<D>) => R
|
||||
// 请求前的数据格式化 同步方法,不支持Promise
|
||||
requestTransform?: <D>(config: BaseApi<D>) => BaseApi<D>
|
||||
// 请求类型
|
||||
dataType?: 'form'|'upload'|'json'
|
||||
// 请求后的钩子函数
|
||||
afterRequest?: (response: AxiosResponse) => AxiosResponse | undefined
|
||||
}
|
||||
|
||||
export interface CramiHttpOptions{
|
||||
headers?: (() => HttpHeaderType)|HttpHeaderType
|
||||
// 请求前的钩子函数
|
||||
beforeRequest?: (params: any) => any
|
||||
// 请求前的参数处理
|
||||
beforeRequestConfig?: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||
// 请求前置的query中的参数
|
||||
beforeRequestQuery?: (query: any) => any
|
||||
// 请求失败的钩子
|
||||
onError?: (e: any) => void
|
||||
// 请求接口格式化处理
|
||||
urlParser?: (url: string) => string
|
||||
// 请求数据格式化 同步方法,不支持Promise
|
||||
responseTransform?: <R = any, D = any>(response: AxiosResponse<D>) => R
|
||||
// 请求前的数据格式化 同步方法,不支持Promise
|
||||
requestTransform?: <D>(config: BaseApi<D>) => BaseApi<D>
|
||||
}
|
36
src/utils.ts
Normal file
36
src/utils.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { Method } from 'axios'
|
||||
|
||||
export const isPromise = (value: any): value is Promise<any> => {
|
||||
return value && typeof value.then === 'function' && typeof value.catch === 'function'
|
||||
}
|
||||
|
||||
export const isObject = (value: unknown): value is object => Object.prototype.toString.call(value) === '[Object Object]'
|
||||
|
||||
export const isArray = (value: unknown): value is any[] => Array.isArray(value)
|
||||
|
||||
export const isString = (value: unknown): value is string => typeof value === 'string'
|
||||
|
||||
export const isFunction = (value: any): value is Function => typeof value === 'function'
|
||||
|
||||
export const runFunction = async<T>(fn: any, ...args: any[]): Promise<T> => {
|
||||
if (!isFunction(fn))
|
||||
return fn
|
||||
|
||||
// TODO
|
||||
const result = fn(...args)
|
||||
if (isPromise(result))
|
||||
return await result
|
||||
return result
|
||||
}
|
||||
|
||||
export const checkParams = <T>(checkerParams: T, defaultParams: T): T => checkerParams ?? defaultParams
|
||||
|
||||
export const checkIsGet = (method?: Method | string|any): boolean => method === 'get' || method === 'GET' || method === undefined || method === null
|
||||
|
||||
export const checkContentType = (headers: Record<string, any>): boolean => {
|
||||
if (Reflect.get(headers || {}, 'content-type'))
|
||||
return true
|
||||
else if (Reflect.get(headers || {}, 'Content-Type'))
|
||||
return true
|
||||
return false
|
||||
}
|
8
tsconfig.config.json
Normal file
8
tsconfig.config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["./playground/vite.config.*", "vitest.config.*", "cypress.config.*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["./playground/src/*", "./playground/src/env.d.ts", "./playground/src/**/*.vue"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@crami/http":["./src/index.ts"],
|
||||
"~/*": ["./playground/src/*"],
|
||||
}
|
||||
},
|
||||
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.config.json"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user