feat: initial release
Publish to npm / publish (push) Failing after 9s

This commit is contained in:
2026-03-31 06:40:07 +08:00
commit 85020ccbf3
10 changed files with 2278 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
name: Publish to npm
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
environment: release # npm Trusted Publisher 环境
permissions:
id-token: write # OIDC 认证
contents: read # 读取仓库
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # 完整 history 用于版本分析
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
# 从 tag 提取版本号
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Package version: $VERSION"
# 修改 package.json 版本号
- name: Update package.json version
run: |
npm pkg set version=${{ steps.version.outputs.version }}
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
# Trusted Publisher 发布(无需 token
- name: Publish to npm
run: npm publish --access public
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
body: |
## @simcu/simapi v${{ steps.version.outputs.version }}
See [changelog](https://github.com/simcu/simapi-vue/releases) for details.
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+24
View File
@@ -0,0 +1,24 @@
# Dependencies
node_modules/
# Build output
dist/
# IDE
.vscode/
.idea/
# Logs
*.log
npm-debug.log*
# OS
.DS_Store
Thumbs.db
# Test
coverage/
# Environment
.env
.env.local
+25
View File
@@ -0,0 +1,25 @@
# 源码不发布
src/
# 开发文件
tsconfig.json
vite.config.ts
vue.config.js
# 测试
**/*.test.ts
**/*.spec.ts
__tests__/
# IDE
.vscode/
.idea/
# Git
.git
.gitignore
# 其他
*.log
.DS_Store
node_modules/
+137
View File
@@ -0,0 +1,137 @@
# SimApi - 轻量 API 请求库
基于 Axios 的 Vue 3 + Pinia HTTP 客户端库。
## 安装
```bash
npm install @simcu/simapi
```
## 快速开始
### 1. 安装依赖
```bash
npm install @simcu/simapi pinia
```
## 快速开始
### 1. 初始化配置
在 Vue 应用入口(如 `main.ts``App.vue`)中配置端点和调试模式:
```typescript
import { useSimApi } from 'simapi'
const api = useSimApi()
// 设置 API 端点
api.setEndpoints({
default: 'https://api.example.com'
})
// 设置调试模式(默认 true
api.setDebug(true)
```
### 2. 发起请求
```typescript
import { useSimApi } from 'simapi'
const api = useSimApi()
// 简单请求
const result = await api.query('/users/list', { page: 1 })
// 带 Token 认证的请求
const result = await api.query('/protected/resource', { id: 123 })
```
### 3. 登录/登出
```typescript
// 登录
const result = await api.login({
phone: '13800138000',
code: '123456'
})
// 登出
await api.logout()
```
## API 参考
### 配置方法
| 方法 | 说明 |
| ------------------------------------- | ---------------------------------------------------------- |
| `setEndpoints(endpoints)` | 设置 API 端点,如 `{ default: 'https://api.example.com' }` |
| `setDebug(debug: boolean)` | 开启/关闭调试模式 |
| `setBusinessCallback(code, callback)` | 设置业务错误码回调 |
### 请求方法
| 方法 | 说明 |
| ------------------------ | -------------- |
| `api.query(uri, params)` | 发起 POST 请求 |
| `api.login(data)` | 登录 |
| `api.logout()` | 登出 |
### 工具方法
| 方法 | 说明 |
| ------------------------ | -------------- |
| `api.getEndpoint(name?)` | 获取端点地址 |
| `api.getToken()` | 获取当前 Token |
### Getters
| 属性 | 说明 |
| ---------------- | -------------------- |
| `api.token` | 当前 Token(响应式) |
| `api.isLoggedIn` | 是否已登录(响应式) |
## 调试日志
启用调试模式后,控制台会输出:
```
[REQUEST*] queryId -> /uri AUTH: token
[RESPONSE] queryId -> {data: {...}, code: 200}
```
设置端点后会自动打印版本信息:
```
UI主应用版本: 1.0.0
UISimApi版本: 1.0.0
API主应用版本: x.x.x
APISimApi版本: x.x.x
```
## 业务错误处理
```typescript
const api = useSimApi()
// 处理 401 未授权
api.setBusinessCallback(401, (data) => {
console.log('Token 过期', data)
localStorage.removeItem('token')
router.push('/login')
})
// 处理 403 禁止访问
api.setBusinessCallback(403, (data) => {
ElMessage.error('没有权限')
})
// 处理所有非 200 错误
api.setBusinessCallback('common', (data) => {
ElMessage.error(data.message)
})
```
+1728
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@simcu/simapi",
"version": "1.0.1",
"description": "轻量 Vue3 HTTP 客户端库,基于 Axios + Pinia",
"main": "dist/simapi.umd.cjs",
"module": "dist/simapi.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "vite build",
"dev": "vite build --watch",
"types": "vue-tsc --declaration --emitDeclarationOnly",
"lint": "vue-tsc --noEmit"
},
"keywords": [
"vue3",
"pinia",
"http",
"axios",
"api",
"rest"
],
"author": "simcu",
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:simcu/simapi-vue.git"
},
"dependencies": {
"axios": "^1.6.0"
},
"peerDependencies": {
"vue": "^3.0.0",
"pinia": "^2.0.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"typescript": "^5.0.0",
"vite": "^5.0.0",
"vue": "^3.4.0",
"vue-tsc": "^2.0.0"
}
}
+7
View File
@@ -0,0 +1,7 @@
/**
* SimApi - 轻量 API 请求库
*/
export { useSimApi } from './simapi'
export type { SimApiAuthConfig, SimApiConfig, Versions } from './simapi'
export { SimApiVersion, AppVersion } from './simapi'
+207
View File
@@ -0,0 +1,207 @@
/**
* SimApi Pinia Store
*
* 使用方法:
* import { useSimApi } from 'simapi'
* const api = useSimApi()
* await api.query('/api/xxx', data)
*/
import { defineStore } from 'pinia'
import axios, { AxiosRequestHeaders } from 'axios'
// ============ 类型定义 ============
export const SimApiVersion = '1.0.0'
export const AppVersion = '1.0.0'
export interface Versions {
uiApp: string
uiSimApi: string
apiApp: string
apiSimApi: string
apiAppFull: string
apiSimApiFull: string
}
export interface BusinessCallback {
[key: number | string]: (data: any) => void
}
export interface SimApiAuthConfig {
token_name: string
check_url: string
logout_url: string
login_url: string
}
export interface SimApiConfig {
endpoints: { [name: string]: string }
defaultEndpoint: string
businessCallback: BusinessCallback
}
// ============ Store 定义 ============
export const useSimApi = defineStore('simapi', {
// ============ State ============
state: () => ({
debug: true,
auth: {
token_name: 'simapi-auth-token',
check_url: '/auth/check',
logout_url: '/auth/logout',
login_url: '/auth/login'
} as SimApiAuthConfig,
api: {
endpoints: { default: '' },
defaultEndpoint: 'default',
businessCallback: {
401: () => localStorage.removeItem('simapi-auth-token'),
common: () => {}
}
} as SimApiConfig,
versions: {
uiApp: AppVersion,
uiSimApi: SimApiVersion,
apiApp: '0.0.0',
apiSimApi: '0.0.0',
apiAppFull: '0.0.0',
apiSimApiFull: '0.0.0'
} as Versions
}),
// ============ Getters ============
getters: {
token: (state) => localStorage.getItem(state.auth.token_name) || '',
isLoggedIn: (state) => !!localStorage.getItem(state.auth.token_name)
},
// ============ Actions ============
actions: {
/** 设置端点配置 */
setEndpoints(endpoints: { [name: string]: string }): void {
this.api.endpoints = { ...this.api.endpoints, ...endpoints }
// 获取并打印版本信息
this.getVersions()
},
/** 设置业务错误回调 */
setBusinessCallback(code: number | string, callback: (data: any) => void): void {
this.api.businessCallback[code] = callback
},
/** 设置调试模式 */
setDebug(debug: boolean): void {
this.debug = debug
},
/** 打印调试日志 */
debug(title: string, data: any): void {
if (this.debug) {
console.log('[DEBUG]', title, data)
}
},
/** 生成随机字符串 */
genS4(): string {
return (((1 + Math.random()) * 0x10000 * Date.parse(new Date())) | 0).toString(16).substring(1)
},
/** 获取端点地址 */
getEndpoint(name?: string): string {
return this.api.endpoints[name || this.api.defaultEndpoint] || ''
},
/** 获取并打印版本信息 */
async getVersions(): Promise<void> {
try {
const resp = await axios.post(this.getEndpoint() + '/versions', {}, { timeout: 5000 })
if (resp.data?.data) {
this.versions = {
uiApp: AppVersion,
uiSimApi: SimApiVersion,
apiApp: resp.data.data.App?.split('+')[0] || '0.0.0',
apiSimApi: resp.data.data.SimApi?.split('+')[0] || '0.0.0',
apiAppFull: resp.data.data.App || '0.0.0',
apiSimApiFull: resp.data.data.SimApi || '0.0.0'
}
}
} catch (e) {
// 版本获取失败不影响主流程
}
// 打印版本信息,格式与 Angular 一致
console.log(`UI主应用版本: ${this.versions.uiApp}\nUISimApi版本: ${this.versions.uiSimApi}\nAPI主应用版本: ${this.versions.apiApp}\nAPISimApi版本: ${this.versions.apiSimApi}`)
},
/** 发起请求 */
async query(uri: string, params: any = {}): Promise<any> {
const headers: Record<string, string> = {}
const queryId = this.genS4()
if (!(params instanceof FormData)) {
headers['Content-Type'] = 'application/json'
}
const token = this.token
if (token) {
headers['Token'] = token
}
if (this.debug) {
headers['Query-Id'] = queryId
console.log('[REQUEST*]', queryId, '->', uri, 'AUTH:', token)
}
const url = this.getEndpoint() + uri
try {
const response = await axios.post(url, params, { headers: headers as AxiosRequestHeaders })
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', response.data)
}
return this.handleResponse(response.data)
} catch (error) {
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', error)
}
throw error
}
},
/** 处理响应 */
handleResponse(data: any): any {
if (data.code !== 200) {
const callback = this.api.businessCallback[data.code] || this.api.businessCallback['common']
callback?.(data)
}
return data
},
/** 登录 */
async login(request: Record<string, any>): Promise<any> {
const result = await this.query(this.auth.login_url, request)
if (result.data) {
localStorage.setItem(this.auth.token_name, result.data)
}
return result
},
/** 登出 */
async logout(url?: string | null): Promise<any> {
localStorage.removeItem(this.auth.token_name)
if (url !== null) {
return this.query(url ?? this.auth.logout_url).catch(() => true)
}
return true
},
/** 获取 Token */
getToken(): string {
return localStorage.getItem(this.auth.token_name) || ''
}
}
})
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts"]
}
+22
View File
@@ -0,0 +1,22 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/index.ts',
name: 'SimApi',
formats: ['es', 'umd'],
fileName: (format) => `simapi.${format}.${format === 'es' ? 'js' : 'cjs'}`
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
})