92 changed files with 418 additions and 6161 deletions
@ -0,0 +1,4 @@ |
|||
VITE_API_URL=https://test.tall.wiki/gateway |
|||
VITE_BASE_URL=https://test.tall.wiki |
|||
VITE_PUBLIC_PATH=/wuliu |
|||
VITE_PROXY_URL=/gateway |
|||
@ -0,0 +1,4 @@ |
|||
VITE_API_URL=https://www.tall.wiki/gateway |
|||
VITE_BASE_URL=https://www.tall.wiki |
|||
VITE_PUBLIC_PATH=/wuliu |
|||
VITE_PROXY_URL=/gateway |
|||
@ -1,9 +0,0 @@ |
|||
{ |
|||
"baseUrl": "http://localhost:3000", |
|||
"fixturesFolder": "tests/e2e/fixtures", |
|||
"integrationFolder": "tests/e2e/integration", |
|||
"pluginsFile": "tests/e2e/plugins/index.ts", |
|||
"screenshotsFolder": "tests/e2e/screenshots", |
|||
"supportFile": "tests/e2e/support/index.ts", |
|||
"videosFolder": "tests/e2e/videos" |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
@url=https://test.tall.wiki/gateway/logistics |
|||
@type=Content-Type: application/json |
|||
|
|||
POST {{url}}/warehouse/selAllWarehouse |
|||
{{type}} |
|||
|
|||
{ |
|||
"param": { |
|||
"parkId": 0 |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
import http from 'utils/http'; |
|||
import axios from 'axios' |
|||
|
|||
//
|
|||
export const getWarehouses = (param: any) => axios.post('/gateway/logistics/mbps/selAllMbps', { param }); |
|||
// export const getWarehouses = (param: any) => axios.get('/api/todos/1', );
|
|||
@ -1,10 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { GetAccountInfoModel } from './model/accountModel'; |
|||
|
|||
enum Api { |
|||
ACCOUNT_INFO = '/warehouse/selAllWarehouse', |
|||
} |
|||
|
|||
// Get personal center-basic settings
|
|||
|
|||
export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO }); |
|||
@ -1,12 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
|
|||
enum Api { |
|||
// The address does not exist
|
|||
Error = '/error', |
|||
} |
|||
|
|||
/** |
|||
* @description: Trigger ajax error |
|||
*/ |
|||
|
|||
export const fireErrorApi = () => defHttp.get({ url: Api.Error }); |
|||
@ -1,7 +0,0 @@ |
|||
export interface GetAccountInfoModel { |
|||
email: string; |
|||
name: string; |
|||
introduction: string; |
|||
phone: string; |
|||
address: string; |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
import { BasicFetchResult } from '/@/api/model/baseModel'; |
|||
|
|||
export interface DemoOptionsItem { |
|||
label: string; |
|||
value: string; |
|||
} |
|||
|
|||
/** |
|||
* @description: Request list return value |
|||
*/ |
|||
export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem[]>; |
|||
@ -1,74 +0,0 @@ |
|||
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; |
|||
|
|||
export type AccountParams = BasicPageParams & { |
|||
account?: string; |
|||
nickname?: string; |
|||
}; |
|||
|
|||
export type RoleParams = { |
|||
roleName?: string; |
|||
status?: string; |
|||
}; |
|||
|
|||
export type RolePageParams = BasicPageParams & RoleParams; |
|||
|
|||
export type DeptParams = { |
|||
deptName?: string; |
|||
status?: string; |
|||
}; |
|||
|
|||
export type MenuParams = { |
|||
menuName?: string; |
|||
status?: string; |
|||
}; |
|||
|
|||
export interface AccountListItem { |
|||
id: string; |
|||
account: string; |
|||
email: string; |
|||
nickname: string; |
|||
role: number; |
|||
createTime: string; |
|||
remark: string; |
|||
status: number; |
|||
} |
|||
|
|||
export interface DeptListItem { |
|||
id: string; |
|||
orderNo: string; |
|||
createTime: string; |
|||
remark: string; |
|||
status: number; |
|||
} |
|||
|
|||
export interface MenuListItem { |
|||
id: string; |
|||
orderNo: string; |
|||
createTime: string; |
|||
status: number; |
|||
icon: string; |
|||
component: string; |
|||
permission: string; |
|||
} |
|||
|
|||
export interface RoleListItem { |
|||
id: string; |
|||
roleName: string; |
|||
roleValue: string; |
|||
status: number; |
|||
orderNo: string; |
|||
createTime: string; |
|||
} |
|||
|
|||
/** |
|||
* @description: Request list return value |
|||
*/ |
|||
export type AccountListGetResultModel = BasicFetchResult<AccountListItem>; |
|||
|
|||
export type DeptListGetResultModel = BasicFetchResult<DeptListItem>; |
|||
|
|||
export type MenuListGetResultModel = BasicFetchResult<MenuListItem>; |
|||
|
|||
export type RolePageListGetResultModel = BasicFetchResult<RoleListItem>; |
|||
|
|||
export type RoleListGetResultModel = RoleListItem[]; |
|||
@ -1,20 +0,0 @@ |
|||
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; |
|||
/** |
|||
* @description: Request list interface parameters |
|||
*/ |
|||
export type DemoParams = BasicPageParams; |
|||
|
|||
export interface DemoListItem { |
|||
id: string; |
|||
beginTime: string; |
|||
endTime: string; |
|||
address: string; |
|||
name: string; |
|||
no: number; |
|||
status: number; |
|||
} |
|||
|
|||
/** |
|||
* @description: Request list return value |
|||
*/ |
|||
export type DemoListGetResultModel = BasicFetchResult<DemoListItem>; |
|||
@ -1,12 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { DemoOptionsGetResultModel } from './model/optionsModel'; |
|||
|
|||
enum Api { |
|||
OPTIONS_LIST = '/select/getDemoOptions', |
|||
} |
|||
|
|||
/** |
|||
* @description: Get sample options value |
|||
*/ |
|||
export const optionsListApi = () => |
|||
defHttp.get<DemoOptionsGetResultModel>({ url: Api.OPTIONS_LIST }); |
|||
@ -1,36 +0,0 @@ |
|||
import { |
|||
AccountParams, |
|||
DeptListItem, |
|||
MenuParams, |
|||
RoleParams, |
|||
RolePageParams, |
|||
MenuListGetResultModel, |
|||
DeptListGetResultModel, |
|||
AccountListGetResultModel, |
|||
RolePageListGetResultModel, |
|||
RoleListGetResultModel, |
|||
} from './model/systemModel'; |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
|
|||
enum Api { |
|||
AccountList = '/system/getAccountList', |
|||
DeptList = '/system/getDeptList', |
|||
MenuList = '/system/getMenuList', |
|||
RolePageList = '/system/getRoleListByPage', |
|||
GetAllRoleList = '/system/getAllRoleList', |
|||
} |
|||
|
|||
export const getAccountList = (params: AccountParams) => |
|||
defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params }); |
|||
|
|||
export const getDeptList = (params?: DeptListItem) => |
|||
defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params }); |
|||
|
|||
export const getMenuList = (params?: MenuParams) => |
|||
defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params }); |
|||
|
|||
export const getRoleListByPage = (params?: RolePageParams) => |
|||
defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params }); |
|||
|
|||
export const getAllRoleList = (params?: RoleParams) => |
|||
defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params }); |
|||
@ -1,19 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { DemoParams, DemoListGetResultModel } from './model/tableModel'; |
|||
|
|||
enum Api { |
|||
DEMO_LIST = '/table/getDemoList', |
|||
} |
|||
|
|||
/** |
|||
* @description: Get sample list value |
|||
*/ |
|||
|
|||
export const demoListApi = (params: DemoParams) => |
|||
defHttp.get<DemoListGetResultModel>({ |
|||
url: Api.DEMO_LIST, |
|||
params, |
|||
headers: { |
|||
ignoreCancelToken: true, |
|||
}, |
|||
}); |
|||
@ -1,9 +0,0 @@ |
|||
export interface BasicPageParams { |
|||
page: number; |
|||
pageSize: number; |
|||
} |
|||
|
|||
export interface BasicFetchResult<T extends any> { |
|||
items: T; |
|||
total: number; |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { getMenuListByIdParams, getMenuListByIdParamsResultModel } from './model/menuModel'; |
|||
|
|||
enum Api { |
|||
GetMenuListById = '/getMenuListById', |
|||
} |
|||
|
|||
/** |
|||
* @description: Get user menu based on id |
|||
*/ |
|||
|
|||
export const getMenuListById = (params: getMenuListByIdParams) => { |
|||
return defHttp.get<getMenuListByIdParamsResultModel>({ url: Api.GetMenuListById, params }); |
|||
}; |
|||
@ -1,23 +0,0 @@ |
|||
import { RouteMeta } from '/@/router/types'; |
|||
export interface RouteItem { |
|||
path: string; |
|||
component: any; |
|||
meta: RouteMeta; |
|||
name?: string; |
|||
alias?: string | string[]; |
|||
redirect?: string; |
|||
caseSensitive?: boolean; |
|||
children?: RouteItem[]; |
|||
} |
|||
|
|||
/** |
|||
* @description: Get menu interface |
|||
*/ |
|||
export interface getMenuListByIdParams { |
|||
id: number | string; |
|||
} |
|||
|
|||
/** |
|||
* @description: Get menu return value |
|||
*/ |
|||
export type getMenuListByIdParamsResultModel = RouteItem[]; |
|||
@ -1,5 +0,0 @@ |
|||
export interface UploadApiResult { |
|||
message: string; |
|||
code: number; |
|||
url: string; |
|||
} |
|||
@ -1,43 +0,0 @@ |
|||
/** |
|||
* @description: Login interface parameters |
|||
*/ |
|||
export interface LoginParams { |
|||
username: string; |
|||
password: string; |
|||
} |
|||
|
|||
/** |
|||
* @description: Get user information |
|||
*/ |
|||
export interface GetUserInfoByUserIdParams { |
|||
userId: string | number; |
|||
} |
|||
|
|||
export interface RoleInfo { |
|||
roleName: string; |
|||
value: string; |
|||
} |
|||
|
|||
/** |
|||
* @description: Login interface return value |
|||
*/ |
|||
export interface LoginResultModel { |
|||
userId: string | number; |
|||
token: string; |
|||
role: RoleInfo; |
|||
} |
|||
|
|||
/** |
|||
* @description: Get user information return value |
|||
*/ |
|||
export interface GetUserInfoByUserIdModel { |
|||
roles: RoleInfo[]; |
|||
// 用户id
|
|||
userId: string | number; |
|||
// 用户名
|
|||
username: string; |
|||
// 真实名字
|
|||
realName: string; |
|||
// 介绍
|
|||
desc?: string; |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
import { UploadApiResult } from './model/uploadModel'; |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { UploadFileParams } from '/@/utils/http/axios/types'; |
|||
import { useGlobSetting } from '/@/hooks/setting'; |
|||
|
|||
const { uploadUrl = '' } = useGlobSetting(); |
|||
|
|||
/** |
|||
* @description: Upload interface |
|||
*/ |
|||
export function uploadApi( |
|||
params: UploadFileParams, |
|||
onUploadProgress: (progressEvent: ProgressEvent) => void |
|||
) { |
|||
return defHttp.uploadFile<UploadApiResult>( |
|||
{ |
|||
url: uploadUrl, |
|||
onUploadProgress, |
|||
}, |
|||
params |
|||
); |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
import { defHttp } from '/@/utils/http/axios'; |
|||
import { |
|||
LoginParams, |
|||
LoginResultModel, |
|||
GetUserInfoByUserIdParams, |
|||
GetUserInfoByUserIdModel, |
|||
} from './model/userModel'; |
|||
|
|||
import { ErrorMessageMode } from '/@/utils/http/axios/types'; |
|||
|
|||
enum Api { |
|||
Login = '/login', |
|||
GetUserInfoById = '/getUserInfoById', |
|||
GetPermCodeByUserId = '/getPermCodeByUserId', |
|||
} |
|||
|
|||
/** |
|||
* @description: user login api |
|||
*/ |
|||
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { |
|||
return defHttp.post<LoginResultModel>( |
|||
{ |
|||
url: Api.Login, |
|||
params, |
|||
}, |
|||
{ |
|||
errorMessageMode: mode, |
|||
} |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @description: getUserInfoById |
|||
*/ |
|||
export function getUserInfoById(params: GetUserInfoByUserIdParams) { |
|||
return defHttp.get<GetUserInfoByUserIdModel>({ |
|||
url: Api.GetUserInfoById, |
|||
params, |
|||
}); |
|||
} |
|||
|
|||
export function getPermCodeByUserId(params: GetUserInfoByUserIdParams) { |
|||
return defHttp.get<string[]>({ |
|||
url: Api.GetPermCodeByUserId, |
|||
params, |
|||
}); |
|||
} |
|||
@ -1,10 +1,25 @@ |
|||
import { createApp } from 'vue' |
|||
import router, { setupRouter } from './router/index'; |
|||
import App from './App.vue' |
|||
import './index.css' |
|||
import * as echarts from 'echarts' |
|||
import http from 'utils/http'; |
|||
|
|||
|
|||
const app = createApp(App) |
|||
app.config.globalProperties.$echarts = echarts |
|||
(async () => { |
|||
const app = createApp(App); |
|||
|
|||
createApp(App).mount('#app') |
|||
app.config.globalProperties.$http = http; |
|||
|
|||
// Configure routing
|
|||
setupRouter(app); |
|||
|
|||
// Mount when the route is ready
|
|||
// https://next.router.vuejs.org/api/#isready
|
|||
await router.isReady(); |
|||
|
|||
app.mount('#app', true); |
|||
|
|||
if (import.meta.env.DEV) { |
|||
window.__APP__ = app; |
|||
} |
|||
})(); |
|||
|
|||
@ -0,0 +1,23 @@ |
|||
import type { RouteRecordRaw } from 'vue-router'; |
|||
import type { App } from 'vue'; |
|||
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'; |
|||
|
|||
|
|||
// app router
|
|||
const router = createRouter({ |
|||
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH), |
|||
routes: [ |
|||
{ path: '/', redirect: '/jht' }, |
|||
{ path: '/jht', component: () => import('views/repo-five.vue') }, |
|||
], |
|||
strict: true |
|||
}); |
|||
|
|||
|
|||
// config router
|
|||
export function setupRouter(app: App<Element>) { |
|||
app.use(router); |
|||
} |
|||
|
|||
export default router; |
|||
@ -0,0 +1,98 @@ |
|||
import type { RouteRecordRaw } from 'vue-router'; |
|||
import { RoleEnum } from '/@/enums/roleEnum'; |
|||
|
|||
import { defineComponent } from 'vue'; |
|||
|
|||
export type Component<T extends any = any> = |
|||
| ReturnType<typeof defineComponent> |
|||
| (() => Promise<typeof import('*.vue')>) |
|||
| (() => Promise<T>); |
|||
|
|||
export interface RouteMeta { |
|||
// title
|
|||
title: string; |
|||
// Whether to ignore permissions
|
|||
ignoreAuth?: boolean; |
|||
// role info
|
|||
roles?: RoleEnum[]; |
|||
// Whether not to cache
|
|||
ignoreKeepAlive?: boolean; |
|||
// Is it fixed on tab
|
|||
affix?: boolean; |
|||
// icon on tab
|
|||
icon?: string; |
|||
|
|||
frameSrc?: string; |
|||
|
|||
// current page transition
|
|||
transitionName?: string; |
|||
|
|||
// Whether the route has been dynamically added
|
|||
hideBreadcrumb?: boolean; |
|||
|
|||
// Hide submenu
|
|||
hideChildrenInMenu?: boolean; |
|||
|
|||
// Carrying parameters
|
|||
carryParam?: boolean; |
|||
|
|||
// Used internally to mark single-level menus
|
|||
single?: boolean; |
|||
|
|||
// Currently active menu
|
|||
currentActiveMenu?: string; |
|||
|
|||
// Never show in tab
|
|||
hideTab?: boolean; |
|||
|
|||
// Never show in menu
|
|||
hideMenu?: boolean; |
|||
|
|||
isLink?: boolean; |
|||
} |
|||
|
|||
// @ts-ignore
|
|||
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { |
|||
name: string; |
|||
meta: RouteMeta; |
|||
component?: Component | string; |
|||
components?: Component; |
|||
children?: AppRouteRecordRaw[]; |
|||
props?: Recordable; |
|||
fullPath?: string; |
|||
} |
|||
export interface MenuTag { |
|||
type?: 'primary' | 'error' | 'warn' | 'success'; |
|||
content?: string; |
|||
dot?: boolean; |
|||
} |
|||
|
|||
export interface Menu { |
|||
name: string; |
|||
|
|||
icon?: string; |
|||
|
|||
path: string; |
|||
|
|||
disabled?: boolean; |
|||
|
|||
children?: Menu[]; |
|||
|
|||
orderNo?: number; |
|||
|
|||
roles?: RoleEnum[]; |
|||
|
|||
meta?: Partial<RouteMeta>; |
|||
|
|||
tag?: MenuTag; |
|||
|
|||
hideMenu?: boolean; |
|||
} |
|||
|
|||
export interface MenuModule { |
|||
orderNo?: number; |
|||
menu: Menu; |
|||
} |
|||
|
|||
// export type AppRouteModule = RouteModule | AppRouteRecordRaw;
|
|||
export type AppRouteModule = AppRouteRecordRaw; |
|||
@ -1,25 +0,0 @@ |
|||
import { ActionTree, ActionContext } from 'vuex' |
|||
|
|||
import { State } from './state' |
|||
import { Mutations, Mutation } from './mutations' |
|||
|
|||
export enum Action { |
|||
initApp = 'initApp', |
|||
} |
|||
|
|||
type AugmentedActionContext = { |
|||
commit<K extends keyof Mutations>( |
|||
key: K, |
|||
payload?: Parameters<Mutations[K]>[1] |
|||
): ReturnType<Mutations[K]> |
|||
} & Omit<ActionContext<State, State>, 'commit'> |
|||
|
|||
export interface Actions { |
|||
[Action.initApp]({ state, commit, dispatch }: AugmentedActionContext): void |
|||
} |
|||
|
|||
export const actions: ActionTree<State, State> & Actions = { |
|||
[Action.initApp]({ state, commit, dispatch }) { |
|||
console.log('app inited!') |
|||
}, |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
import { GetterTree } from 'vuex' |
|||
import { State } from './state' |
|||
|
|||
export type Getters = { |
|||
isReady(state: State): boolean |
|||
} |
|||
|
|||
export const getters: GetterTree<State, State> & Getters = { |
|||
isReady(state) { |
|||
return !state.isInitialized |
|||
}, |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
/* eslint-disable @typescript-eslint/no-extra-semi */ |
|||
import { InjectionKey } from 'vue' |
|||
import { createStore, Store as VuexStore, CommitOptions, DispatchOptions } from 'vuex' |
|||
|
|||
import { mutations, Mutations, Mutation } from './mutations' |
|||
import { actions, Actions, Action } from './actions' |
|||
import { getters, Getters } from './getters' |
|||
import { state } from './state' |
|||
import type { State } from './state' |
|||
|
|||
export const key: InjectionKey<VuexStore<State>> = Symbol() |
|||
|
|||
export const store = createStore({ |
|||
state, |
|||
getters, |
|||
mutations, |
|||
actions, |
|||
}) |
|||
|
|||
export type Store = Omit<VuexStore<State>, 'getters' | 'commit' | 'dispatch'> & { |
|||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>( |
|||
key: K, |
|||
payload: P, |
|||
options?: CommitOptions |
|||
): ReturnType<Mutations[K]> |
|||
} & { |
|||
dispatch<K extends keyof Actions>( |
|||
key: K, |
|||
payload?: Parameters<Actions[K]>[1], |
|||
options?: DispatchOptions |
|||
): ReturnType<Actions[K]> |
|||
} & { |
|||
getters: { |
|||
[K in keyof Getters]: ReturnType<Getters[K]> |
|||
} |
|||
} |
|||
|
|||
export function useStore(): Store { |
|||
return store as Store |
|||
} |
|||
|
|||
export { State, Mutation, Action } |
|||
@ -1,16 +0,0 @@ |
|||
import { MutationTree } from 'vuex' |
|||
import { State } from './state' |
|||
|
|||
export enum Mutation { |
|||
INCREMENT = 'INCREMENT', |
|||
} |
|||
|
|||
export type Mutations<S = State> = { |
|||
[Mutation.INCREMENT](state: S, payload: number): void |
|||
} |
|||
|
|||
export const mutations: MutationTree<State> & Mutations = { |
|||
[Mutation.INCREMENT](state: State, payload: number = 1) { |
|||
state.count += payload |
|||
}, |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
export interface State { |
|||
debug: boolean |
|||
version: string |
|||
isInitialized: boolean |
|||
count: number |
|||
} |
|||
|
|||
const versionString = import.meta.env.MODE === 'development' ? _APP_VERSION + '-dev' : _APP_VERSION |
|||
|
|||
export const state: State = { |
|||
debug: import.meta.env.MODE === 'development', |
|||
version: versionString, |
|||
isInitialized: false, |
|||
count: 0, |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
import { Persistent, BasicKeys } from '/@/utils/cache/persistent'; |
|||
import { CacheTypeEnum } from '/@/enums/cacheEnum'; |
|||
import projectSetting from '/@/settings/projectSetting'; |
|||
import { TOKEN_KEY } from '/@/enums/cacheEnum'; |
|||
|
|||
const { permissionCacheType } = projectSetting; |
|||
const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; |
|||
|
|||
export function getToken() { |
|||
return getAuthCache(TOKEN_KEY); |
|||
} |
|||
|
|||
export function getAuthCache<T>(key: BasicKeys) { |
|||
const fn = isLocal ? Persistent.getLocal : Persistent.getSession; |
|||
return fn(key) as T; |
|||
} |
|||
|
|||
export function setAuthCache(key: BasicKeys, value) { |
|||
const fn = isLocal ? Persistent.setLocal : Persistent.setSession; |
|||
return fn(key, value, true); |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
import { getStorageShortName } from '/@/utils/env'; |
|||
import { createStorage as create, CreateStorageParams } from './storageCache'; |
|||
import { enableStorageEncryption } from '/@/settings/encryptionSetting'; |
|||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; |
|||
|
|||
export type Options = Partial<CreateStorageParams>; |
|||
|
|||
const createOptions = (storage: Storage, options: Options = {}): Options => { |
|||
return { |
|||
// No encryption in debug mode
|
|||
hasEncrypt: enableStorageEncryption, |
|||
storage, |
|||
prefixKey: getStorageShortName(), |
|||
...options, |
|||
}; |
|||
}; |
|||
|
|||
export const WebStorage = create(createOptions(sessionStorage)); |
|||
|
|||
export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => { |
|||
return create(createOptions(storage, options)); |
|||
}; |
|||
|
|||
export const createSessionStorage = (options: Options = {}) => { |
|||
return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); |
|||
}; |
|||
|
|||
export const createLocalStorage = (options: Options = {}) => { |
|||
return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); |
|||
}; |
|||
|
|||
export default WebStorage; |
|||
@ -1,102 +0,0 @@ |
|||
export interface Cache<V = any> { |
|||
value?: V; |
|||
timeoutId?: ReturnType<typeof setTimeout>; |
|||
time?: number; |
|||
alive?: number; |
|||
} |
|||
|
|||
const NOT_ALIVE = 0; |
|||
|
|||
export class Memory<T = any, V = any> { |
|||
private cache: { [key in keyof T]?: Cache<V> } = {}; |
|||
private alive: number; |
|||
|
|||
constructor(alive = NOT_ALIVE) { |
|||
// Unit second
|
|||
this.alive = alive * 1000; |
|||
} |
|||
|
|||
get getCache() { |
|||
return this.cache; |
|||
} |
|||
|
|||
setCache(cache) { |
|||
this.cache = cache; |
|||
} |
|||
|
|||
// get<K extends keyof T>(key: K) {
|
|||
// const item = this.getItem(key);
|
|||
// const time = item?.time;
|
|||
// if (!isNullOrUnDef(time) && time < new Date().getTime()) {
|
|||
// this.remove(key);
|
|||
// }
|
|||
// return item?.value ?? undefined;
|
|||
// }
|
|||
|
|||
get<K extends keyof T>(key: K) { |
|||
return this.cache[key]; |
|||
} |
|||
|
|||
set<K extends keyof T>(key: K, value: V, expires?: number) { |
|||
let item = this.get(key); |
|||
|
|||
if (!expires || (expires as number) <= 0) { |
|||
expires = this.alive; |
|||
} |
|||
if (item) { |
|||
if (item.timeoutId) { |
|||
clearTimeout(item.timeoutId); |
|||
item.timeoutId = undefined; |
|||
} |
|||
item.value = value; |
|||
} else { |
|||
item = { value, alive: expires }; |
|||
this.cache[key] = item; |
|||
} |
|||
|
|||
if (!expires) { |
|||
return value; |
|||
} |
|||
const now = new Date().getTime(); |
|||
item.time = now + this.alive; |
|||
item.timeoutId = setTimeout( |
|||
() => { |
|||
this.remove(key); |
|||
}, |
|||
expires > now ? expires - now : expires |
|||
); |
|||
|
|||
return value; |
|||
} |
|||
|
|||
remove<K extends keyof T>(key: K) { |
|||
const item = this.get(key); |
|||
Reflect.deleteProperty(this.cache, key); |
|||
if (item) { |
|||
clearTimeout(item.timeoutId!); |
|||
return item.value; |
|||
} |
|||
} |
|||
|
|||
resetCache(cache: { [K in keyof T]: Cache }) { |
|||
Object.keys(cache).forEach((key) => { |
|||
const k = (key as any) as keyof T; |
|||
const item = cache[k]; |
|||
if (item && item.time) { |
|||
const now = new Date().getTime(); |
|||
const expire = item.time; |
|||
if (expire > now) { |
|||
this.set(k, item.value, expire); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
clear() { |
|||
Object.keys(this.cache).forEach((key) => { |
|||
const item = this.cache[key]; |
|||
item.timeoutId && clearTimeout(item.timeoutId); |
|||
}); |
|||
this.cache = {}; |
|||
} |
|||
} |
|||
@ -1,115 +0,0 @@ |
|||
import type { LockInfo, UserInfo } from '/#/store'; |
|||
import type { ProjectConfig } from '/#/config'; |
|||
import type { RouteLocationNormalized } from 'vue-router'; |
|||
|
|||
import { createLocalStorage, createSessionStorage } from '/@/utils/cache'; |
|||
import { Memory } from './memory'; |
|||
import { |
|||
TOKEN_KEY, |
|||
USER_INFO_KEY, |
|||
ROLES_KEY, |
|||
LOCK_INFO_KEY, |
|||
PROJ_CFG_KEY, |
|||
APP_LOCAL_CACHE_KEY, |
|||
APP_SESSION_CACHE_KEY, |
|||
MULTIPLE_TABS_KEY, |
|||
} from '/@/enums/cacheEnum'; |
|||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; |
|||
import { toRaw } from 'vue'; |
|||
|
|||
interface BasicStore { |
|||
[TOKEN_KEY]: string | number | null | undefined; |
|||
[USER_INFO_KEY]: UserInfo; |
|||
[ROLES_KEY]: string[]; |
|||
[LOCK_INFO_KEY]: LockInfo; |
|||
[PROJ_CFG_KEY]: ProjectConfig; |
|||
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[]; |
|||
} |
|||
|
|||
type LocalStore = BasicStore; |
|||
|
|||
type SessionStore = BasicStore; |
|||
|
|||
export type BasicKeys = keyof BasicStore; |
|||
type LocalKeys = keyof LocalStore; |
|||
type SessionKeys = keyof SessionStore; |
|||
|
|||
const ls = createLocalStorage(); |
|||
const ss = createSessionStorage(); |
|||
|
|||
const localMemory = new Memory(DEFAULT_CACHE_TIME); |
|||
const sessionMemory = new Memory(DEFAULT_CACHE_TIME); |
|||
|
|||
function initPersistentMemory() { |
|||
const localCache = ls.get(APP_LOCAL_CACHE_KEY); |
|||
const sessionCache = ss.get(APP_SESSION_CACHE_KEY); |
|||
localCache && localMemory.resetCache(localCache); |
|||
sessionCache && sessionMemory.resetCache(sessionCache); |
|||
} |
|||
|
|||
export class Persistent { |
|||
static getLocal<T>(key: LocalKeys) { |
|||
return localMemory.get(key)?.value as Nullable<T>; |
|||
} |
|||
|
|||
static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void { |
|||
localMemory.set(key, toRaw(value)); |
|||
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); |
|||
} |
|||
|
|||
static removeLocal(key: LocalKeys): void { |
|||
localMemory.remove(key); |
|||
} |
|||
|
|||
static clearLocal(): void { |
|||
localMemory.clear(); |
|||
} |
|||
|
|||
static getSession<T>(key: SessionKeys) { |
|||
return sessionMemory.get(key)?.value as Nullable<T>; |
|||
} |
|||
|
|||
static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void { |
|||
sessionMemory.set(key, toRaw(value)); |
|||
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); |
|||
} |
|||
|
|||
static removeSession(key: SessionKeys): void { |
|||
sessionMemory.remove(key); |
|||
} |
|||
static clearSession(): void { |
|||
sessionMemory.clear(); |
|||
} |
|||
|
|||
static clearAll() { |
|||
sessionMemory.clear(); |
|||
localMemory.clear(); |
|||
} |
|||
} |
|||
|
|||
window.addEventListener('beforeunload', function () { |
|||
ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); |
|||
ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); |
|||
}); |
|||
|
|||
function storageChange(e: any) { |
|||
const { key, newValue, oldValue } = e; |
|||
|
|||
if (!key) { |
|||
Persistent.clearAll(); |
|||
return; |
|||
} |
|||
|
|||
if (!!newValue && !!oldValue) { |
|||
if (APP_LOCAL_CACHE_KEY === key) { |
|||
Persistent.clearLocal(); |
|||
} |
|||
if (APP_SESSION_CACHE_KEY === key) { |
|||
Persistent.clearSession(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
window.addEventListener('storage', storageChange); |
|||
|
|||
initPersistentMemory(); |
|||
@ -1,114 +0,0 @@ |
|||
import { cacheCipher } from '/@/settings/encryptionSetting'; |
|||
|
|||
import type { EncryptionParams } from '/@/utils/cipher'; |
|||
|
|||
import { AesEncryption } from '/@/utils/cipher'; |
|||
|
|||
import { isNullOrUnDef } from '/@/utils/is'; |
|||
|
|||
export interface CreateStorageParams extends EncryptionParams { |
|||
prefixKey: string; |
|||
storage: Storage; |
|||
hasEncrypt: boolean; |
|||
timeout?: Nullable<number>; |
|||
} |
|||
export const createStorage = ({ |
|||
prefixKey = '', |
|||
storage = sessionStorage, |
|||
key = cacheCipher.key, |
|||
iv = cacheCipher.iv, |
|||
timeout = null, |
|||
hasEncrypt = true, |
|||
}: Partial<CreateStorageParams> = {}) => { |
|||
if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) { |
|||
throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!'); |
|||
} |
|||
|
|||
const encryption = new AesEncryption({ key, iv }); |
|||
|
|||
/** |
|||
*Cache class |
|||
*Construction parameters can be passed into sessionStorage, localStorage, |
|||
* @class Cache |
|||
* @example |
|||
*/ |
|||
const WebStorage = class WebStorage { |
|||
private storage: Storage; |
|||
private prefixKey?: string; |
|||
private encryption: AesEncryption; |
|||
private hasEncrypt: boolean; |
|||
/** |
|||
* |
|||
* @param {*} storage |
|||
*/ |
|||
constructor() { |
|||
this.storage = storage; |
|||
this.prefixKey = prefixKey; |
|||
this.encryption = encryption; |
|||
this.hasEncrypt = hasEncrypt; |
|||
} |
|||
|
|||
private getKey(key: string) { |
|||
return `${this.prefixKey}${key}`.toUpperCase(); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* Set cache |
|||
* @param {string} key |
|||
* @param {*} value |
|||
* @expire Expiration time in seconds |
|||
* @memberof Cache |
|||
*/ |
|||
set(key: string, value: any, expire: number | null = timeout) { |
|||
const stringData = JSON.stringify({ |
|||
value, |
|||
time: Date.now(), |
|||
expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null, |
|||
}); |
|||
const stringifyValue = this.hasEncrypt |
|||
? this.encryption.encryptByAES(stringData) |
|||
: stringData; |
|||
this.storage.setItem(this.getKey(key), stringifyValue); |
|||
} |
|||
|
|||
/** |
|||
*Read cache |
|||
* @param {string} key |
|||
* @memberof Cache |
|||
*/ |
|||
get(key: string, def: any = null): any { |
|||
const val = this.storage.getItem(this.getKey(key)); |
|||
if (!val) return def; |
|||
|
|||
try { |
|||
const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val; |
|||
const data = JSON.parse(decVal); |
|||
const { value, expire } = data; |
|||
if (isNullOrUnDef(expire) || expire >= new Date().getTime()) { |
|||
return value; |
|||
} |
|||
this.remove(key); |
|||
} catch (e) { |
|||
return def; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Delete cache based on key |
|||
* @param {string} key |
|||
* @memberof Cache |
|||
*/ |
|||
remove(key: string) { |
|||
this.storage.removeItem(this.getKey(key)); |
|||
} |
|||
|
|||
/** |
|||
* Delete all caches of this instance |
|||
*/ |
|||
clear(): void { |
|||
this.storage.clear(); |
|||
} |
|||
}; |
|||
return new WebStorage(); |
|||
}; |
|||
@ -1,55 +0,0 @@ |
|||
import { encrypt, decrypt } from 'crypto-js/aes'; |
|||
import { parse } from 'crypto-js/enc-utf8'; |
|||
import pkcs7 from 'crypto-js/pad-pkcs7'; |
|||
import ECB from 'crypto-js/mode-ecb'; |
|||
import md5 from 'crypto-js/md5'; |
|||
import UTF8 from 'crypto-js/enc-utf8'; |
|||
import Base64 from 'crypto-js/enc-base64'; |
|||
|
|||
export interface EncryptionParams { |
|||
key: string; |
|||
iv: string; |
|||
} |
|||
|
|||
export class AesEncryption { |
|||
private key; |
|||
private iv; |
|||
|
|||
constructor(opt: Partial<EncryptionParams> = {}) { |
|||
const { key, iv } = opt; |
|||
if (key) { |
|||
this.key = parse(key); |
|||
} |
|||
if (iv) { |
|||
this.iv = parse(iv); |
|||
} |
|||
} |
|||
|
|||
get getOptions() { |
|||
return { |
|||
mode: ECB, |
|||
padding: pkcs7, |
|||
iv: this.iv, |
|||
}; |
|||
} |
|||
|
|||
encryptByAES(cipherText: string) { |
|||
return encrypt(cipherText, this.key, this.getOptions).toString(); |
|||
} |
|||
|
|||
decryptByAES(cipherText: string) { |
|||
return decrypt(cipherText, this.key, this.getOptions).toString(UTF8); |
|||
} |
|||
} |
|||
|
|||
export function encryptByBase64(cipherText: string) { |
|||
return Base64.parse(cipherText).toString(UTF8); |
|||
} |
|||
|
|||
export function decodeByBase64(cipherText: string) { |
|||
return Base64.parse(cipherText).toString(UTF8); |
|||
} |
|||
|
|||
export function encryptByMd5(password: string) { |
|||
return md5(password).toString(); |
|||
} |
|||
@ -1,151 +0,0 @@ |
|||
/** |
|||
* 判断是否 十六进制颜色值. |
|||
* 输入形式可为 #fff000 #f00 |
|||
* |
|||
* @param String color 十六进制颜色值 |
|||
* @return Boolean |
|||
*/ |
|||
export function isHexColor(color: string) { |
|||
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/; |
|||
return reg.test(color); |
|||
} |
|||
|
|||
/** |
|||
* RGB 颜色值转换为 十六进制颜色值. |
|||
* r, g, 和 b 需要在 [0, 255] 范围内 |
|||
* |
|||
* @return String 类似#ff00ff |
|||
* @param r |
|||
* @param g |
|||
* @param b |
|||
*/ |
|||
export function rgbToHex(r: number, g: number, b: number) { |
|||
// tslint:disable-next-line:no-bitwise
|
|||
const hex = ((r << 16) | (g << 8) | b).toString(16); |
|||
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex; |
|||
} |
|||
|
|||
/** |
|||
* Transform a HEX color to its RGB representation |
|||
* @param {string} hex The color to transform |
|||
* @returns The RGB representation of the passed color |
|||
*/ |
|||
export function hexToRGB(hex: string) { |
|||
let sHex = hex.toLowerCase(); |
|||
if (isHexColor(hex)) { |
|||
if (sHex.length === 4) { |
|||
let sColorNew = '#'; |
|||
for (let i = 1; i < 4; i += 1) { |
|||
sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)); |
|||
} |
|||
sHex = sColorNew; |
|||
} |
|||
const sColorChange: number[] = []; |
|||
for (let i = 1; i < 7; i += 2) { |
|||
sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))); |
|||
} |
|||
return 'RGB(' + sColorChange.join(',') + ')'; |
|||
} |
|||
return sHex; |
|||
} |
|||
|
|||
export function colorIsDark(color: string) { |
|||
if (!isHexColor(color)) return; |
|||
const [r, g, b] = hexToRGB(color) |
|||
.replace(/(?:\(|\)|rgb|RGB)*/g, '') |
|||
.split(',') |
|||
.map((item) => Number(item)); |
|||
return r * 0.299 + g * 0.578 + b * 0.114 < 192; |
|||
} |
|||
|
|||
/** |
|||
* Darkens a HEX color given the passed percentage |
|||
* @param {string} color The color to process |
|||
* @param {number} amount The amount to change the color by |
|||
* @returns {string} The HEX representation of the processed color |
|||
*/ |
|||
export function darken(color: string, amount: number) { |
|||
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; |
|||
amount = Math.trunc((255 * amount) / 100); |
|||
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( |
|||
color.substring(2, 4), |
|||
amount |
|||
)}${subtractLight(color.substring(4, 6), amount)}`;
|
|||
} |
|||
|
|||
/** |
|||
* Lightens a 6 char HEX color according to the passed percentage |
|||
* @param {string} color The color to change |
|||
* @param {number} amount The amount to change the color by |
|||
* @returns {string} The processed color represented as HEX |
|||
*/ |
|||
export function lighten(color: string, amount: number) { |
|||
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; |
|||
amount = Math.trunc((255 * amount) / 100); |
|||
return `#${addLight(color.substring(0, 2), amount)}${addLight( |
|||
color.substring(2, 4), |
|||
amount |
|||
)}${addLight(color.substring(4, 6), amount)}`;
|
|||
} |
|||
|
|||
/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ |
|||
/** |
|||
* Sums the passed percentage to the R, G or B of a HEX color |
|||
* @param {string} color The color to change |
|||
* @param {number} amount The amount to change the color by |
|||
* @returns {string} The processed part of the color |
|||
*/ |
|||
function addLight(color: string, amount: number) { |
|||
const cc = parseInt(color, 16) + amount; |
|||
const c = cc > 255 ? 255 : cc; |
|||
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; |
|||
} |
|||
|
|||
/** |
|||
* Calculates luminance of an rgb color |
|||
* @param {number} r red |
|||
* @param {number} g green |
|||
* @param {number} b blue |
|||
*/ |
|||
function luminanace(r: number, g: number, b: number) { |
|||
const a = [r, g, b].map((v) => { |
|||
v /= 255; |
|||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); |
|||
}); |
|||
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; |
|||
} |
|||
|
|||
/** |
|||
* Calculates contrast between two rgb colors |
|||
* @param {string} rgb1 rgb color 1 |
|||
* @param {string} rgb2 rgb color 2 |
|||
*/ |
|||
function contrast(rgb1: string[], rgb2: number[]) { |
|||
return ( |
|||
(luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / |
|||
(luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Determines what the best text color is (black or white) based con the contrast with the background |
|||
* @param hexColor - Last selected color by the user |
|||
*/ |
|||
export function calculateBestTextColor(hexColor: string) { |
|||
const rgbColor = hexToRGB(hexColor.substring(1)); |
|||
const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]); |
|||
|
|||
return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'; |
|||
} |
|||
|
|||
/** |
|||
* Subtracts the indicated percentage to the R, G or B of a HEX color |
|||
* @param {string} color The color to change |
|||
* @param {number} amount The amount to change the color by |
|||
* @returns {string} The processed part of the color |
|||
*/ |
|||
function subtractLight(color: string, amount: number) { |
|||
const cc = parseInt(color, 16) - amount; |
|||
const c = cc < 0 ? 0 : cc; |
|||
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
/** |
|||
* Independent time operation tool to facilitate subsequent switch to dayjs |
|||
*/ |
|||
import moment from 'moment'; |
|||
|
|||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; |
|||
const DATE_FORMAT = 'YYYY-MM-DD '; |
|||
|
|||
export function formatToDateTime( |
|||
date: moment.MomentInput = null, |
|||
format = DATE_TIME_FORMAT |
|||
): string { |
|||
return moment(date).format(format); |
|||
} |
|||
|
|||
export function formatToDate(date: moment.MomentInput = null, format = DATE_FORMAT): string { |
|||
return moment(date).format(format); |
|||
} |
|||
|
|||
export const dateUtil = moment; |
|||
@ -1,165 +0,0 @@ |
|||
import { upperFirst } from 'lodash-es'; |
|||
|
|||
export interface ViewportOffsetResult { |
|||
left: number; |
|||
top: number; |
|||
right: number; |
|||
bottom: number; |
|||
rightIncludeBody: number; |
|||
bottomIncludeBody: number; |
|||
} |
|||
|
|||
export function getBoundingClientRect(element: Element): DOMRect | number { |
|||
if (!element || !element.getBoundingClientRect) { |
|||
return 0; |
|||
} |
|||
return element.getBoundingClientRect(); |
|||
} |
|||
|
|||
function trim(string: string) { |
|||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function hasClass(el: Element, cls: string) { |
|||
if (!el || !cls) return false; |
|||
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); |
|||
if (el.classList) { |
|||
return el.classList.contains(cls); |
|||
} else { |
|||
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; |
|||
} |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function addClass(el: Element, cls: string) { |
|||
if (!el) return; |
|||
let curClass = el.className; |
|||
const classes = (cls || '').split(' '); |
|||
|
|||
for (let i = 0, j = classes.length; i < j; i++) { |
|||
const clsName = classes[i]; |
|||
if (!clsName) continue; |
|||
|
|||
if (el.classList) { |
|||
el.classList.add(clsName); |
|||
} else if (!hasClass(el, clsName)) { |
|||
curClass += ' ' + clsName; |
|||
} |
|||
} |
|||
if (!el.classList) { |
|||
el.className = curClass; |
|||
} |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function removeClass(el: Element, cls: string) { |
|||
if (!el || !cls) return; |
|||
const classes = cls.split(' '); |
|||
let curClass = ' ' + el.className + ' '; |
|||
|
|||
for (let i = 0, j = classes.length; i < j; i++) { |
|||
const clsName = classes[i]; |
|||
if (!clsName) continue; |
|||
|
|||
if (el.classList) { |
|||
el.classList.remove(clsName); |
|||
} else if (hasClass(el, clsName)) { |
|||
curClass = curClass.replace(' ' + clsName + ' ', ' '); |
|||
} |
|||
} |
|||
if (!el.classList) { |
|||
el.className = trim(curClass); |
|||
} |
|||
} |
|||
/** |
|||
* Get the left and top offset of the current element |
|||
* left: the distance between the leftmost element and the left side of the document |
|||
* top: the distance from the top of the element to the top of the document |
|||
* right: the distance from the far right of the element to the right of the document |
|||
* bottom: the distance from the bottom of the element to the bottom of the document |
|||
* rightIncludeBody: the distance between the leftmost element and the right side of the document |
|||
* bottomIncludeBody: the distance from the bottom of the element to the bottom of the document |
|||
* |
|||
* @description: |
|||
*/ |
|||
export function getViewportOffset(element: Element): ViewportOffsetResult { |
|||
const doc = document.documentElement; |
|||
|
|||
const docScrollLeft = doc.scrollLeft; |
|||
const docScrollTop = doc.scrollTop; |
|||
const docClientLeft = doc.clientLeft; |
|||
const docClientTop = doc.clientTop; |
|||
|
|||
const pageXOffset = window.pageXOffset; |
|||
const pageYOffset = window.pageYOffset; |
|||
|
|||
const box = getBoundingClientRect(element); |
|||
|
|||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect; |
|||
|
|||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0); |
|||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0); |
|||
const offsetLeft = retLeft + pageXOffset; |
|||
const offsetTop = rectTop + pageYOffset; |
|||
|
|||
const left = offsetLeft - scrollLeft; |
|||
const top = offsetTop - scrollTop; |
|||
|
|||
const clientWidth = window.document.documentElement.clientWidth; |
|||
const clientHeight = window.document.documentElement.clientHeight; |
|||
return { |
|||
left: left, |
|||
top: top, |
|||
right: clientWidth - rectWidth - left, |
|||
bottom: clientHeight - rectHeight - top, |
|||
rightIncludeBody: clientWidth - left, |
|||
bottomIncludeBody: clientHeight - top, |
|||
}; |
|||
} |
|||
|
|||
export function hackCss(attr: string, value: string) { |
|||
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']; |
|||
|
|||
const styleObj: any = {}; |
|||
prefix.forEach((item) => { |
|||
styleObj[`${item}${upperFirst(attr)}`] = value; |
|||
}); |
|||
return { |
|||
...styleObj, |
|||
[attr]: value, |
|||
}; |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function on( |
|||
element: Element | HTMLElement | Document | Window, |
|||
event: string, |
|||
handler: EventListenerOrEventListenerObject |
|||
): void { |
|||
if (element && event && handler) { |
|||
element.addEventListener(event, handler, false); |
|||
} |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function off( |
|||
element: Element | HTMLElement | Document | Window, |
|||
event: string, |
|||
handler: Fn |
|||
): void { |
|||
if (element && event && handler) { |
|||
element.removeEventListener(event, handler, false); |
|||
} |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function once(el: HTMLElement, event: string, fn: EventListener): void { |
|||
const listener = function (this: any, ...args: unknown[]) { |
|||
if (fn) { |
|||
fn.apply(this, args); |
|||
} |
|||
off(el, event, listener); |
|||
}; |
|||
on(el, event, listener); |
|||
} |
|||
@ -1,83 +0,0 @@ |
|||
import type { GlobEnvConfig } from '/#/config'; |
|||
|
|||
import { warn } from '/@/utils/log'; |
|||
import pkg from '../../package.json'; |
|||
import { getConfigFileName } from '../../build/getConfigFileName'; |
|||
|
|||
export function getCommonStoragePrefix() { |
|||
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); |
|||
return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase(); |
|||
} |
|||
|
|||
// Generate cache key according to version
|
|||
export function getStorageShortName() { |
|||
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); |
|||
} |
|||
|
|||
export function getAppEnvConfig() { |
|||
const ENV_NAME = getConfigFileName(import.meta.env); |
|||
|
|||
const ENV = ((import.meta.env.DEV |
|||
? // Get the global configuration (the configuration will be extracted independently when packaging)
|
|||
((import.meta.env as unknown) as GlobEnvConfig) |
|||
: window[ENV_NAME as any]) as unknown) as GlobEnvConfig; |
|||
|
|||
const { |
|||
VITE_GLOB_APP_TITLE, |
|||
VITE_GLOB_API_URL, |
|||
VITE_GLOB_APP_SHORT_NAME, |
|||
VITE_GLOB_API_URL_PREFIX, |
|||
VITE_GLOB_UPLOAD_URL, |
|||
} = ENV; |
|||
|
|||
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { |
|||
warn( |
|||
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` |
|||
); |
|||
} |
|||
|
|||
return { |
|||
VITE_GLOB_APP_TITLE, |
|||
VITE_GLOB_API_URL, |
|||
VITE_GLOB_APP_SHORT_NAME, |
|||
VITE_GLOB_API_URL_PREFIX, |
|||
VITE_GLOB_UPLOAD_URL, |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* @description: Development model |
|||
*/ |
|||
export const devMode = 'development'; |
|||
|
|||
/** |
|||
* @description: Production mode |
|||
*/ |
|||
export const prodMode = 'production'; |
|||
|
|||
/** |
|||
* @description: Get environment variables |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function getEnv(): string { |
|||
return import.meta.env.MODE; |
|||
} |
|||
|
|||
/** |
|||
* @description: Is it a development mode |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function isDevMode(): boolean { |
|||
return import.meta.env.DEV; |
|||
} |
|||
|
|||
/** |
|||
* @description: Is it a production mode |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function isProdMode(): boolean { |
|||
return import.meta.env.PROD; |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
import ResizeObserver from 'resize-observer-polyfill'; |
|||
|
|||
const isServer = typeof window === 'undefined'; |
|||
|
|||
/* istanbul ignore next */ |
|||
function resizeHandler(entries: any[]) { |
|||
for (const entry of entries) { |
|||
const listeners = entry.target.__resizeListeners__ || []; |
|||
if (listeners.length) { |
|||
listeners.forEach((fn: () => any) => { |
|||
fn(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function addResizeListener(element: any, fn: () => any) { |
|||
if (isServer) return; |
|||
if (!element.__resizeListeners__) { |
|||
element.__resizeListeners__ = []; |
|||
element.__ro__ = new ResizeObserver(resizeHandler); |
|||
element.__ro__.observe(element); |
|||
} |
|||
element.__resizeListeners__.push(fn); |
|||
} |
|||
|
|||
/* istanbul ignore next */ |
|||
export function removeResizeListener(element: any, fn: () => any) { |
|||
if (!element || !element.__resizeListeners__) return; |
|||
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); |
|||
if (!element.__resizeListeners__.length) { |
|||
element.__ro__.disconnect(); |
|||
} |
|||
} |
|||
|
|||
export function triggerWindowResize() { |
|||
const event = document.createEvent('HTMLEvents'); |
|||
event.initEvent('resize', true, true); |
|||
(event as any).eventType = 'message'; |
|||
window.dispatchEvent(event); |
|||
} |
|||
@ -1,63 +0,0 @@ |
|||
import { |
|||
defineAsyncComponent, |
|||
// FunctionalComponent, CSSProperties
|
|||
} from 'vue'; |
|||
import { Spin } from 'ant-design-vue'; |
|||
import { noop } from '/@/utils/index'; |
|||
|
|||
// const Loading: FunctionalComponent<{ size: 'small' | 'default' | 'large' }> = (props) => {
|
|||
// const style: CSSProperties = {
|
|||
// position: 'absolute',
|
|||
// display: 'flex',
|
|||
// justifyContent: 'center',
|
|||
// alignItems: 'center',
|
|||
// };
|
|||
// return (
|
|||
// <div style={style}>
|
|||
// <Spin spinning={true} size={props.size} />
|
|||
// </div>
|
|||
// );
|
|||
// };
|
|||
|
|||
interface Options { |
|||
size?: 'default' | 'small' | 'large'; |
|||
delay?: number; |
|||
timeout?: number; |
|||
loading?: boolean; |
|||
retry?: boolean; |
|||
} |
|||
|
|||
export function createAsyncComponent(loader: Fn, options: Options = {}) { |
|||
const { size = 'small', delay = 100, timeout = 30000, loading = false, retry = true } = options; |
|||
return defineAsyncComponent({ |
|||
loader, |
|||
loadingComponent: loading ? <Spin spinning={true} size={size} /> : undefined, |
|||
// The error component will be displayed if a timeout is
|
|||
// provided and exceeded. Default: Infinity.
|
|||
// TODO
|
|||
timeout, |
|||
// errorComponent
|
|||
// Defining if component is suspensible. Default: true.
|
|||
// suspensible: false,
|
|||
delay, |
|||
/** |
|||
* |
|||
* @param {*} error Error message object |
|||
* @param {*} retry A function that indicating whether the async component should retry when the loader promise rejects |
|||
* @param {*} fail End of failure |
|||
* @param {*} attempts Maximum allowed retries number |
|||
*/ |
|||
onError: !retry |
|||
? noop |
|||
: (error, retry, fail, attempts) => { |
|||
if (error.message.match(/fetch/) && attempts <= 3) { |
|||
// retry on fetch errors, 3 max attempts
|
|||
retry(); |
|||
} else { |
|||
// Note that retry/fail are like resolve/reject of a promise:
|
|||
// one of them must be called for the error handling to continue.
|
|||
fail(); |
|||
} |
|||
}, |
|||
}); |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
/** |
|||
* @description: base64 to blob |
|||
*/ |
|||
export function dataURLtoBlob(base64Buf: string): Blob { |
|||
const arr = base64Buf.split(','); |
|||
const typeItem = arr[0]; |
|||
const mime = typeItem.match(/:(.*?);/)![1]; |
|||
const bstr = atob(arr[1]); |
|||
let n = bstr.length; |
|||
const u8arr = new Uint8Array(n); |
|||
while (n--) { |
|||
u8arr[n] = bstr.charCodeAt(n); |
|||
} |
|||
return new Blob([u8arr], { type: mime }); |
|||
} |
|||
|
|||
/** |
|||
* img url to base64 |
|||
* @param url |
|||
*/ |
|||
export function urlToBase64(url: string, mineType?: string): Promise<string> { |
|||
return new Promise((resolve, reject) => { |
|||
let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>; |
|||
const ctx = canvas!.getContext('2d'); |
|||
|
|||
const img = new Image(); |
|||
img.crossOrigin = ''; |
|||
img.onload = function () { |
|||
if (!canvas || !ctx) { |
|||
return reject(); |
|||
} |
|||
canvas.height = img.height; |
|||
canvas.width = img.width; |
|||
ctx.drawImage(img, 0, 0); |
|||
const dataURL = canvas.toDataURL(mineType || 'image/png'); |
|||
canvas = null; |
|||
resolve(dataURL); |
|||
}; |
|||
img.src = url; |
|||
}); |
|||
} |
|||
@ -1,99 +0,0 @@ |
|||
import { openWindow } from '..'; |
|||
import { dataURLtoBlob, urlToBase64 } from './base64Conver'; |
|||
|
|||
/** |
|||
* Download online pictures |
|||
* @param url |
|||
* @param filename |
|||
* @param mime |
|||
* @param bom |
|||
*/ |
|||
export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) { |
|||
urlToBase64(url).then((base64) => { |
|||
downloadByBase64(base64, filename, mime, bom); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Download pictures based on base64 |
|||
* @param buf |
|||
* @param filename |
|||
* @param mime |
|||
* @param bom |
|||
*/ |
|||
export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) { |
|||
const base64Buf = dataURLtoBlob(buf); |
|||
downloadByData(base64Buf, filename, mime, bom); |
|||
} |
|||
|
|||
/** |
|||
* Download according to the background interface file stream |
|||
* @param {*} data |
|||
* @param {*} filename |
|||
* @param {*} mime |
|||
* @param {*} bom |
|||
*/ |
|||
export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) { |
|||
const blobData = typeof bom !== 'undefined' ? [bom, data] : [data]; |
|||
const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }); |
|||
if (typeof window.navigator.msSaveBlob !== 'undefined') { |
|||
window.navigator.msSaveBlob(blob, filename); |
|||
} else { |
|||
const blobURL = window.URL.createObjectURL(blob); |
|||
const tempLink = document.createElement('a'); |
|||
tempLink.style.display = 'none'; |
|||
tempLink.href = blobURL; |
|||
tempLink.setAttribute('download', filename); |
|||
if (typeof tempLink.download === 'undefined') { |
|||
tempLink.setAttribute('target', '_blank'); |
|||
} |
|||
document.body.appendChild(tempLink); |
|||
tempLink.click(); |
|||
document.body.removeChild(tempLink); |
|||
window.URL.revokeObjectURL(blobURL); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Download file according to file address |
|||
* @param {*} sUrl |
|||
*/ |
|||
export function downloadByUrl({ |
|||
url, |
|||
target = '_blank', |
|||
fileName, |
|||
}: { |
|||
url: string; |
|||
target?: TargetContext; |
|||
fileName?: string; |
|||
}): boolean { |
|||
const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1; |
|||
const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1; |
|||
|
|||
if (/(iP)/g.test(window.navigator.userAgent)) { |
|||
console.error('Your browser does not support download!'); |
|||
return false; |
|||
} |
|||
if (isChrome || isSafari) { |
|||
const link = document.createElement('a'); |
|||
link.href = url; |
|||
link.target = target; |
|||
|
|||
if (link.download !== undefined) { |
|||
link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length); |
|||
} |
|||
|
|||
if (document.createEvent) { |
|||
const e = document.createEvent('MouseEvents'); |
|||
e.initEvent('click', true, true); |
|||
link.dispatchEvent(e); |
|||
return true; |
|||
} |
|||
} |
|||
if (url.indexOf('?') === -1) { |
|||
url += '?download'; |
|||
} |
|||
|
|||
openWindow(url, { target }); |
|||
return true; |
|||
} |
|||
@ -1,186 +0,0 @@ |
|||
interface TreeHelperConfig { |
|||
id: string; |
|||
children: string; |
|||
pid: string; |
|||
} |
|||
const DEFAULT_CONFIG: TreeHelperConfig = { |
|||
id: 'id', |
|||
children: 'children', |
|||
pid: 'pid', |
|||
}; |
|||
|
|||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config); |
|||
|
|||
// tree from list
|
|||
export function listToTree<T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] { |
|||
const conf = getConfig(config) as TreeHelperConfig; |
|||
const nodeMap = new Map(); |
|||
const result: T[] = []; |
|||
const { id, children, pid } = conf; |
|||
|
|||
for (const node of list) { |
|||
node[children] = node[children] || []; |
|||
nodeMap.set(node[id], node); |
|||
} |
|||
for (const node of list) { |
|||
const parent = nodeMap.get(node[pid]); |
|||
(parent ? parent.children : result).push(node); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
export function treeToList<T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T { |
|||
config = getConfig(config); |
|||
const { children } = config; |
|||
const result: any = [...tree]; |
|||
for (let i = 0; i < result.length; i++) { |
|||
if (!result[i][children!]) continue; |
|||
result.splice(i + 1, 0, ...result[i][children!]); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
export function findNode<T = any>( |
|||
tree: any, |
|||
func: Fn, |
|||
config: Partial<TreeHelperConfig> = {} |
|||
): T | null { |
|||
config = getConfig(config); |
|||
const { children } = config; |
|||
const list = [...tree]; |
|||
for (const node of list) { |
|||
if (func(node)) return node; |
|||
node[children!] && list.push(...node[children!]); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
export function findNodeAll<T = any>( |
|||
tree: any, |
|||
func: Fn, |
|||
config: Partial<TreeHelperConfig> = {} |
|||
): T[] { |
|||
config = getConfig(config); |
|||
const { children } = config; |
|||
const list = [...tree]; |
|||
const result: T[] = []; |
|||
for (const node of list) { |
|||
func(node) && result.push(node); |
|||
node[children!] && list.push(...node[children!]); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
export function findPath<T = any>( |
|||
tree: any, |
|||
func: Fn, |
|||
config: Partial<TreeHelperConfig> = {} |
|||
): T | T[] | null { |
|||
config = getConfig(config); |
|||
const path: T[] = []; |
|||
const list = [...tree]; |
|||
const visitedSet = new Set(); |
|||
const { children } = config; |
|||
while (list.length) { |
|||
const node = list[0]; |
|||
if (visitedSet.has(node)) { |
|||
path.pop(); |
|||
list.shift(); |
|||
} else { |
|||
visitedSet.add(node); |
|||
node[children!] && list.unshift(...node[children!]); |
|||
path.push(node); |
|||
if (func(node)) { |
|||
return path; |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
export function findPathAll(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) { |
|||
config = getConfig(config); |
|||
const path: any[] = []; |
|||
const list = [...tree]; |
|||
const result: any[] = []; |
|||
const visitedSet = new Set(), |
|||
{ children } = config; |
|||
while (list.length) { |
|||
const node = list[0]; |
|||
if (visitedSet.has(node)) { |
|||
path.pop(); |
|||
list.shift(); |
|||
} else { |
|||
visitedSet.add(node); |
|||
node[children!] && list.unshift(...node[children!]); |
|||
path.push(node); |
|||
func(node) && result.push([...path]); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
export function filter<T = any>( |
|||
tree: T[], |
|||
func: (n: T) => boolean, |
|||
config: Partial<TreeHelperConfig> = {} |
|||
): T[] { |
|||
config = getConfig(config); |
|||
const children = config.children as string; |
|||
function listFilter(list: T[]) { |
|||
return list |
|||
.map((node: any) => ({ ...node })) |
|||
.filter((node) => { |
|||
node[children] = node[children] && listFilter(node[children]); |
|||
return func(node) || (node[children] && node[children].length); |
|||
}); |
|||
} |
|||
return listFilter(tree); |
|||
} |
|||
|
|||
export function forEach<T = any>( |
|||
tree: T[], |
|||
func: (n: T) => any, |
|||
config: Partial<TreeHelperConfig> = {} |
|||
): void { |
|||
config = getConfig(config); |
|||
const list: any[] = [...tree]; |
|||
const { children } = config; |
|||
for (let i = 0; i < list.length; i++) { |
|||
func(list[i]); |
|||
children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description: Extract tree specified structure |
|||
*/ |
|||
export function treeMap<T = any>(treeData: T[], opt: { children?: string; conversion: Fn }): T[] { |
|||
return treeData.map((item) => treeMapEach(item, opt)); |
|||
} |
|||
|
|||
/** |
|||
* @description: Extract tree specified structure |
|||
*/ |
|||
export function treeMapEach( |
|||
data: any, |
|||
{ children = 'children', conversion }: { children?: string; conversion: Fn } |
|||
) { |
|||
const haveChildren = Array.isArray(data[children]) && data[children].length > 0; |
|||
const conversionData = conversion(data) || {}; |
|||
if (haveChildren) { |
|||
return { |
|||
...conversionData, |
|||
[children]: data[children].map((i: number) => |
|||
treeMapEach(i, { |
|||
children, |
|||
conversion, |
|||
}) |
|||
), |
|||
}; |
|||
} else { |
|||
return { |
|||
...conversionData, |
|||
}; |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
import { Slots } from 'vue'; |
|||
import { isFunction } from '/@/utils/is'; |
|||
|
|||
/** |
|||
* @description: Get slot to prevent empty error |
|||
*/ |
|||
export function getSlot(slots: Slots, slot = 'default', data?: any) { |
|||
if (!slots || !Reflect.has(slots, slot)) { |
|||
return null; |
|||
} |
|||
if (!isFunction(slots[slot])) { |
|||
console.error(`${slot} is not a function!`); |
|||
return null; |
|||
} |
|||
const slotFn = slots[slot]; |
|||
if (!slotFn) return null; |
|||
return slotFn(data); |
|||
} |
|||
|
|||
/** |
|||
* extends slots |
|||
* @param slots |
|||
* @param excludeKeys |
|||
*/ |
|||
export function extendSlots(slots: Slots, excludeKeys: string[] = []) { |
|||
const slotKeys = Object.keys(slots); |
|||
const ret: any = {}; |
|||
slotKeys.map((key) => { |
|||
if (excludeKeys.includes(key)) { |
|||
return null; |
|||
} |
|||
ret[key] = () => getSlot(slots, key); |
|||
}); |
|||
return ret; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; |
|||
|
|||
const service = axios.create({ |
|||
// baseURL: '/gateway/logistics'
|
|||
}); |
|||
|
|||
service.interceptors.request.use((config: AxiosRequestConfig) => { |
|||
|
|||
return config; |
|||
}); |
|||
|
|||
service.interceptors.response.use((response: AxiosResponse) => { |
|||
|
|||
return response; |
|||
}) |
|||
|
|||
export default service; |
|||
@ -1,226 +0,0 @@ |
|||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'; |
|||
import type { RequestOptions, Result, UploadFileParams } from './types'; |
|||
import type { CreateAxiosOptions } from './axiosTransform'; |
|||
|
|||
import axios from 'axios'; |
|||
import qs from 'qs'; |
|||
import { AxiosCanceler } from './axiosCancel'; |
|||
import { isFunction } from '/@/utils/is'; |
|||
import { cloneDeep } from 'lodash-es'; |
|||
|
|||
import { errorResult } from './const'; |
|||
import { ContentTypeEnum } from '/@/enums/httpEnum'; |
|||
import { RequestEnum } from '../../../enums/httpEnum'; |
|||
|
|||
export * from './axiosTransform'; |
|||
|
|||
/** |
|||
* @description: axios module |
|||
*/ |
|||
export class VAxios { |
|||
private axiosInstance: AxiosInstance; |
|||
private readonly options: CreateAxiosOptions; |
|||
|
|||
constructor(options: CreateAxiosOptions) { |
|||
this.options = options; |
|||
this.axiosInstance = axios.create(options); |
|||
this.setupInterceptors(); |
|||
} |
|||
|
|||
/** |
|||
* @description: Create axios instance |
|||
*/ |
|||
private createAxios(config: CreateAxiosOptions): void { |
|||
this.axiosInstance = axios.create(config); |
|||
} |
|||
|
|||
private getTransform() { |
|||
const { transform } = this.options; |
|||
return transform; |
|||
} |
|||
|
|||
getAxios(): AxiosInstance { |
|||
return this.axiosInstance; |
|||
} |
|||
|
|||
/** |
|||
* @description: Reconfigure axios |
|||
*/ |
|||
configAxios(config: CreateAxiosOptions) { |
|||
if (!this.axiosInstance) { |
|||
return; |
|||
} |
|||
this.createAxios(config); |
|||
} |
|||
|
|||
/** |
|||
* @description: Set general header |
|||
*/ |
|||
setHeader(headers: any): void { |
|||
if (!this.axiosInstance) { |
|||
return; |
|||
} |
|||
Object.assign(this.axiosInstance.defaults.headers, headers); |
|||
} |
|||
|
|||
/** |
|||
* @description: Interceptor configuration |
|||
*/ |
|||
private setupInterceptors() { |
|||
const transform = this.getTransform(); |
|||
if (!transform) { |
|||
return; |
|||
} |
|||
const { |
|||
requestInterceptors, |
|||
requestInterceptorsCatch, |
|||
responseInterceptors, |
|||
responseInterceptorsCatch, |
|||
} = transform; |
|||
|
|||
const axiosCanceler = new AxiosCanceler(); |
|||
|
|||
// Request interceptor configuration processing
|
|||
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => { |
|||
// If cancel repeat request is turned on, then cancel repeat request is prohibited
|
|||
const { |
|||
headers: { ignoreCancelToken }, |
|||
} = config; |
|||
|
|||
const ignoreCancel = |
|||
ignoreCancelToken !== undefined |
|||
? ignoreCancelToken |
|||
: this.options.requestOptions?.ignoreCancelToken; |
|||
|
|||
!ignoreCancel && axiosCanceler.addPending(config); |
|||
if (requestInterceptors && isFunction(requestInterceptors)) { |
|||
config = requestInterceptors(config); |
|||
} |
|||
return config; |
|||
}, undefined); |
|||
|
|||
// Request interceptor error capture
|
|||
requestInterceptorsCatch && |
|||
isFunction(requestInterceptorsCatch) && |
|||
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch); |
|||
|
|||
// Response result interceptor processing
|
|||
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => { |
|||
res && axiosCanceler.removePending(res.config); |
|||
if (responseInterceptors && isFunction(responseInterceptors)) { |
|||
res = responseInterceptors(res); |
|||
} |
|||
return res; |
|||
}, undefined); |
|||
|
|||
// Response result interceptor error capture
|
|||
responseInterceptorsCatch && |
|||
isFunction(responseInterceptorsCatch) && |
|||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); |
|||
} |
|||
|
|||
/** |
|||
* @description: File Upload |
|||
*/ |
|||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) { |
|||
const formData = new window.FormData(); |
|||
|
|||
if (params.data) { |
|||
Object.keys(params.data).forEach((key) => { |
|||
if (!params.data) return; |
|||
const value = params.data[key]; |
|||
if (Array.isArray(value)) { |
|||
value.forEach((item) => { |
|||
formData.append(`${key}[]`, item); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
formData.append(key, params.data[key]); |
|||
}); |
|||
} |
|||
|
|||
formData.append(params.name || 'file', params.file, params.filename); |
|||
|
|||
return this.axiosInstance.request<T>({ |
|||
...config, |
|||
method: 'POST', |
|||
data: formData, |
|||
headers: { |
|||
'Content-type': ContentTypeEnum.FORM_DATA, |
|||
ignoreCancelToken: true, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
// support form-data
|
|||
supportFormData(config: AxiosRequestConfig) { |
|||
const headers = config.headers || this.options.headers; |
|||
const contentType = headers?.['Content-Type'] || headers?.['content-type']; |
|||
|
|||
if ( |
|||
contentType !== ContentTypeEnum.FORM_URLENCODED || |
|||
!Reflect.has(config, 'data') || |
|||
config.method?.toUpperCase() === RequestEnum.GET |
|||
) { |
|||
return config; |
|||
} |
|||
|
|||
return { |
|||
...config, |
|||
data: qs.stringify(config.data, { arrayFormat: 'brackets' }), |
|||
}; |
|||
} |
|||
|
|||
get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { |
|||
return this.request({ ...config, method: 'GET' }, options); |
|||
} |
|||
|
|||
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { |
|||
return this.request({ ...config, method: 'POST' }, options); |
|||
} |
|||
|
|||
put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { |
|||
return this.request({ ...config, method: 'PUT' }, options); |
|||
} |
|||
|
|||
delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { |
|||
return this.request({ ...config, method: 'DELETE' }, options); |
|||
} |
|||
|
|||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { |
|||
let conf: AxiosRequestConfig = cloneDeep(config); |
|||
const transform = this.getTransform(); |
|||
|
|||
const { requestOptions } = this.options; |
|||
|
|||
const opt: RequestOptions = Object.assign({}, requestOptions, options); |
|||
|
|||
const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}; |
|||
if (beforeRequestHook && isFunction(beforeRequestHook)) { |
|||
conf = beforeRequestHook(conf, opt); |
|||
} |
|||
|
|||
conf = this.supportFormData(conf); |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
this.axiosInstance |
|||
.request<any, AxiosResponse<Result>>(conf) |
|||
.then((res: AxiosResponse<Result>) => { |
|||
if (transformRequestHook && isFunction(transformRequestHook)) { |
|||
const ret = transformRequestHook(res, opt); |
|||
ret !== errorResult ? resolve(ret) : reject(new Error('request error!')); |
|||
return; |
|||
} |
|||
resolve((res as unknown) as Promise<T>); |
|||
}) |
|||
.catch((e: Error) => { |
|||
if (requestCatchHook && isFunction(requestCatchHook)) { |
|||
reject(requestCatchHook(e)); |
|||
return; |
|||
} |
|||
reject(e); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -1,61 +0,0 @@ |
|||
import type { AxiosRequestConfig, Canceler } from 'axios'; |
|||
import axios from 'axios'; |
|||
|
|||
import { isFunction } from '/@/utils/is'; |
|||
|
|||
// Used to store the identification and cancellation function of each request
|
|||
let pendingMap = new Map<string, Canceler>(); |
|||
|
|||
export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&'); |
|||
|
|||
export class AxiosCanceler { |
|||
/** |
|||
* Add request |
|||
* @param {Object} config |
|||
*/ |
|||
addPending(config: AxiosRequestConfig) { |
|||
this.removePending(config); |
|||
const url = getPendingUrl(config); |
|||
config.cancelToken = |
|||
config.cancelToken || |
|||
new axios.CancelToken((cancel) => { |
|||
if (!pendingMap.has(url)) { |
|||
// If there is no current request in pending, add it
|
|||
pendingMap.set(url, cancel); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description: Clear all pending |
|||
*/ |
|||
removeAllPending() { |
|||
pendingMap.forEach((cancel) => { |
|||
cancel && isFunction(cancel) && cancel(); |
|||
}); |
|||
pendingMap.clear(); |
|||
} |
|||
|
|||
/** |
|||
* Removal request |
|||
* @param {Object} config |
|||
*/ |
|||
removePending(config: AxiosRequestConfig) { |
|||
const url = getPendingUrl(config); |
|||
|
|||
if (pendingMap.has(url)) { |
|||
// If there is a current request identifier in pending,
|
|||
// the current request needs to be cancelled and removed
|
|||
const cancel = pendingMap.get(url); |
|||
cancel && cancel(url); |
|||
pendingMap.delete(url); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description: reset |
|||
*/ |
|||
reset(): void { |
|||
pendingMap = new Map<string, Canceler>(); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
/** |
|||
* Data processing class, can be configured according to the project |
|||
*/ |
|||
import type { AxiosRequestConfig, AxiosResponse } from 'axios'; |
|||
import type { RequestOptions, Result } from './types'; |
|||
|
|||
export interface CreateAxiosOptions extends AxiosRequestConfig { |
|||
prefixUrl?: string; |
|||
transform?: AxiosTransform; |
|||
requestOptions?: RequestOptions; |
|||
} |
|||
|
|||
export abstract class AxiosTransform { |
|||
/** |
|||
* @description: Process configuration before request |
|||
* @description: Process configuration before request |
|||
*/ |
|||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; |
|||
|
|||
/** |
|||
* @description: Request successfully processed |
|||
*/ |
|||
transformRequestHook?: (res: AxiosResponse<Result>, options: RequestOptions) => any; |
|||
|
|||
/** |
|||
* @description: 请求失败处理 |
|||
*/ |
|||
requestCatchHook?: (e: Error) => Promise<any>; |
|||
|
|||
/** |
|||
* @description: 请求之前的拦截器 |
|||
*/ |
|||
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig; |
|||
|
|||
/** |
|||
* @description: 请求之后的拦截器 |
|||
*/ |
|||
responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>; |
|||
|
|||
/** |
|||
* @description: 请求之前的拦截器错误处理 |
|||
*/ |
|||
requestInterceptorsCatch?: (error: Error) => void; |
|||
|
|||
/** |
|||
* @description: 请求之后的拦截器错误处理 |
|||
*/ |
|||
responseInterceptorsCatch?: (error: Error) => void; |
|||
} |
|||
@ -1,55 +0,0 @@ |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import router from '/@/router'; |
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
|
|||
const { createMessage } = useMessage(); |
|||
|
|||
const error = createMessage.error!; |
|||
export function checkStatus(status: number, msg: string): void { |
|||
const { t } = useI18n(); |
|||
switch (status) { |
|||
case 400: |
|||
error(`${msg}`); |
|||
break; |
|||
// 401: Not logged in
|
|||
// Jump to the login page if not logged in, and carry the path of the current page
|
|||
// Return to the current page after successful login. This step needs to be operated on the login page.
|
|||
case 401: |
|||
error(t('sys.api.errMsg401')); |
|||
router.push(PageEnum.BASE_LOGIN); |
|||
break; |
|||
case 403: |
|||
error(t('sys.api.errMsg403')); |
|||
break; |
|||
// 404请求不存在
|
|||
case 404: |
|||
error(t('sys.api.errMsg404')); |
|||
break; |
|||
case 405: |
|||
error(t('sys.api.errMsg405')); |
|||
break; |
|||
case 408: |
|||
error(t('sys.api.errMsg408')); |
|||
break; |
|||
case 500: |
|||
error(t('sys.api.errMsg500')); |
|||
break; |
|||
case 501: |
|||
error(t('sys.api.errMsg501')); |
|||
break; |
|||
case 502: |
|||
error(t('sys.api.errMsg502')); |
|||
break; |
|||
case 503: |
|||
error(t('sys.api.errMsg503')); |
|||
break; |
|||
case 504: |
|||
error(t('sys.api.errMsg504')); |
|||
break; |
|||
case 505: |
|||
error(t('sys.api.errMsg505')); |
|||
break; |
|||
default: |
|||
} |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
// The interface return value data cannot be this, otherwise the request will be judged as a failure
|
|||
export const errorResult = '__ERROR_RESULT__'; |
|||
@ -1,42 +0,0 @@ |
|||
import { isObject, isString } from '/@/utils/is'; |
|||
|
|||
export function createNow<T extends boolean>( |
|||
join: boolean, |
|||
restful: T |
|||
): T extends true ? string : object; |
|||
|
|||
export function createNow(join: boolean, restful = false): string | object { |
|||
if (!join) { |
|||
return restful ? '' : {}; |
|||
} |
|||
const now = new Date().getTime(); |
|||
if (restful) { |
|||
return `?_t=${now}`; |
|||
} |
|||
return { _t: now }; |
|||
} |
|||
|
|||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; |
|||
/** |
|||
* @description: Format request parameter time |
|||
*/ |
|||
export function formatRequestDate(params: any) { |
|||
for (const key in params) { |
|||
if (params[key] && params[key]._isAMomentObject) { |
|||
params[key] = params[key].format(DATE_TIME_FORMAT); |
|||
} |
|||
if (isString(key)) { |
|||
const value = params[key]; |
|||
if (value) { |
|||
try { |
|||
params[key] = isString(value) ? value.trim() : value; |
|||
} catch (error) { |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
} |
|||
if (isObject(params[key])) { |
|||
formatRequestDate(params[key]); |
|||
} |
|||
} |
|||
} |
|||
@ -1,222 +0,0 @@ |
|||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
|||
// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged
|
|||
|
|||
import type { AxiosResponse } from 'axios'; |
|||
import type { RequestOptions, Result } from './types'; |
|||
import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; |
|||
|
|||
import { VAxios } from './Axios'; |
|||
import { checkStatus } from './checkStatus'; |
|||
|
|||
import { useGlobSetting } from '/@/hooks/setting'; |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
|
|||
import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum'; |
|||
|
|||
import { isString } from '/@/utils/is'; |
|||
import { getToken } from '/@/utils/auth'; |
|||
import { setObjToUrlParams, deepMerge } from '/@/utils'; |
|||
import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; |
|||
|
|||
import { errorResult } from './const'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { createNow, formatRequestDate } from './helper'; |
|||
|
|||
const globSetting = useGlobSetting(); |
|||
const prefix = globSetting.urlPrefix; |
|||
const { createMessage, createErrorModal } = useMessage(); |
|||
|
|||
/** |
|||
* @description: 数据处理,方便区分多种处理方式 |
|||
*/ |
|||
const transform: AxiosTransform = { |
|||
/** |
|||
* @description: 处理请求数据 |
|||
*/ |
|||
transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => { |
|||
const { t } = useI18n(); |
|||
const { isTransformRequestResult } = options; |
|||
// 不进行任何处理,直接返回
|
|||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
|||
if (!isTransformRequestResult) { |
|||
return res.data; |
|||
} |
|||
// 错误的时候返回
|
|||
|
|||
const { data } = res; |
|||
if (!data) { |
|||
// return '[HTTP] Request has no return value';
|
|||
return errorResult; |
|||
} |
|||
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
|
|||
const { code, result, message } = data; |
|||
|
|||
// 这里逻辑可以根据项目进行修改
|
|||
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; |
|||
if (!hasSuccess) { |
|||
if (message) { |
|||
// errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
|||
if (options.errorMessageMode === 'modal') { |
|||
createErrorModal({ title: t('sys.api.errorTip'), content: message }); |
|||
} else if (options.errorMessageMode === 'message') { |
|||
createMessage.error(message); |
|||
} |
|||
} |
|||
Promise.reject(new Error(message)); |
|||
return errorResult; |
|||
} |
|||
|
|||
// 接口请求成功,直接返回结果
|
|||
if (code === ResultEnum.SUCCESS) { |
|||
return result; |
|||
} |
|||
// 接口请求错误,统一提示错误信息
|
|||
if (code === ResultEnum.ERROR) { |
|||
if (message) { |
|||
createMessage.error(data.message); |
|||
Promise.reject(new Error(message)); |
|||
} else { |
|||
const msg = t('sys.api.errorMessage'); |
|||
createMessage.error(msg); |
|||
Promise.reject(new Error(msg)); |
|||
} |
|||
return errorResult; |
|||
} |
|||
// 登录超时
|
|||
if (code === ResultEnum.TIMEOUT) { |
|||
const timeoutMsg = t('sys.api.timeoutMessage'); |
|||
createErrorModal({ |
|||
title: t('sys.api.operationFailed'), |
|||
content: timeoutMsg, |
|||
}); |
|||
Promise.reject(new Error(timeoutMsg)); |
|||
return errorResult; |
|||
} |
|||
return errorResult; |
|||
}, |
|||
|
|||
// 请求之前处理config
|
|||
beforeRequestHook: (config, options) => { |
|||
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options; |
|||
|
|||
if (joinPrefix) { |
|||
config.url = `${prefix}${config.url}`; |
|||
} |
|||
|
|||
if (apiUrl && isString(apiUrl)) { |
|||
config.url = `${apiUrl}${config.url}`; |
|||
} |
|||
const params = config.params || {}; |
|||
if (config.method?.toUpperCase() === RequestEnum.GET) { |
|||
if (!isString(params)) { |
|||
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
|||
config.params = Object.assign(params || {}, createNow(joinTime, false)); |
|||
} else { |
|||
// 兼容restful风格
|
|||
config.url = config.url + params + `${createNow(joinTime, true)}`; |
|||
config.params = undefined; |
|||
} |
|||
} else { |
|||
if (!isString(params)) { |
|||
formatDate && formatRequestDate(params); |
|||
config.data = params; |
|||
config.params = undefined; |
|||
if (joinParamsToUrl) { |
|||
config.url = setObjToUrlParams(config.url as string, config.data); |
|||
} |
|||
} else { |
|||
// 兼容restful风格
|
|||
config.url = config.url + params; |
|||
config.params = undefined; |
|||
} |
|||
} |
|||
return config; |
|||
}, |
|||
|
|||
/** |
|||
* @description: 请求拦截器处理 |
|||
*/ |
|||
requestInterceptors: (config) => { |
|||
// 请求之前处理config
|
|||
const token = getToken(); |
|||
if (token) { |
|||
// jwt token
|
|||
config.headers.Authorization = token; |
|||
} |
|||
return config; |
|||
}, |
|||
|
|||
/** |
|||
* @description: 响应错误处理 |
|||
*/ |
|||
responseInterceptorsCatch: (error: any) => { |
|||
const { t } = useI18n(); |
|||
const errorLogStore = useErrorLogStoreWithOut(); |
|||
errorLogStore.addAjaxErrorInfo(error); |
|||
const { response, code, message } = error || {}; |
|||
const msg: string = response?.data?.error?.message ?? ''; |
|||
const err: string = error?.toString?.() ?? ''; |
|||
try { |
|||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { |
|||
createMessage.error(t('sys.api.apiTimeoutMessage')); |
|||
} |
|||
if (err?.includes('Network Error')) { |
|||
createErrorModal({ |
|||
title: t('sys.api.networkException'), |
|||
content: t('sys.api.networkExceptionMsg'), |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
throw new Error(error); |
|||
} |
|||
checkStatus(error?.response?.status, msg); |
|||
return Promise.reject(error); |
|||
}, |
|||
}; |
|||
|
|||
function createAxios(opt?: Partial<CreateAxiosOptions>) { |
|||
return new VAxios( |
|||
deepMerge( |
|||
{ |
|||
timeout: 10 * 1000, |
|||
// 基础接口地址
|
|||
// baseURL: globSetting.apiUrl,
|
|||
// 接口可能会有通用的地址部分,可以统一抽取出来
|
|||
prefixUrl: prefix, |
|||
headers: { 'Content-Type': ContentTypeEnum.JSON }, |
|||
// 如果是form-data格式
|
|||
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
|
|||
// 数据处理方式
|
|||
transform, |
|||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
|||
requestOptions: { |
|||
// 默认将prefix 添加到url
|
|||
joinPrefix: true, |
|||
// 需要对返回数据进行处理
|
|||
isTransformRequestResult: true, |
|||
// post请求的时候添加参数到url
|
|||
joinParamsToUrl: false, |
|||
// 格式化提交参数时间
|
|||
formatDate: true, |
|||
// 消息提示类型
|
|||
errorMessageMode: 'message', |
|||
// 接口地址
|
|||
apiUrl: globSetting.apiUrl, |
|||
// 是否加入时间戳
|
|||
joinTime: true, |
|||
// 忽略重复请求
|
|||
ignoreCancelToken: true, |
|||
}, |
|||
}, |
|||
opt || {} |
|||
) |
|||
); |
|||
} |
|||
export const defHttp = createAxios(); |
|||
|
|||
// other api url
|
|||
// export const otherHttp = createAxios({
|
|||
// requestOptions: {
|
|||
// apiUrl: 'xxx',
|
|||
// },
|
|||
// });
|
|||
@ -1,39 +0,0 @@ |
|||
export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined; |
|||
|
|||
export interface RequestOptions { |
|||
// Splicing request parameters to url
|
|||
joinParamsToUrl?: boolean; |
|||
// Format request parameter time
|
|||
formatDate?: boolean; |
|||
// Whether to process the request result
|
|||
isTransformRequestResult?: boolean; |
|||
// Whether to join url
|
|||
joinPrefix?: boolean; |
|||
// Interface address, use the default apiUrl if you leave it blank
|
|||
apiUrl?: string; |
|||
// Error message prompt type
|
|||
errorMessageMode?: ErrorMessageMode; |
|||
// Whether to add a timestamp
|
|||
joinTime?: boolean; |
|||
ignoreCancelToken?: boolean; |
|||
} |
|||
|
|||
export interface Result<T = any> { |
|||
code: number; |
|||
type: 'success' | 'error' | 'warning'; |
|||
message: string; |
|||
result: T; |
|||
} |
|||
|
|||
// multipart/form-data: upload file
|
|||
export interface UploadFileParams { |
|||
// Other parameters
|
|||
data?: Recordable; |
|||
// File parameter interface field name
|
|||
name?: string; |
|||
// file name
|
|||
file: File | Blob; |
|||
// file name
|
|||
filename?: string; |
|||
[key: string]: any; |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; |
|||
import { unref } from 'vue'; |
|||
import { isObject } from '/@/utils/is'; |
|||
|
|||
export const noop = () => {}; |
|||
|
|||
/** |
|||
* @description: Set ui mount node |
|||
*/ |
|||
export function getPopupContainer(node?: HTMLElement): HTMLElement { |
|||
return (node?.parentNode as HTMLElement) ?? document.body; |
|||
} |
|||
|
|||
/** |
|||
* Add the object as a parameter to the URL |
|||
* @param baseUrl url |
|||
* @param obj |
|||
* @returns {string} |
|||
* eg: |
|||
* let obj = {a: '3', b: '4'} |
|||
* setObjToUrlParams('www.baidu.com', obj) |
|||
* ==>www.baidu.com?a=3&b=4 |
|||
*/ |
|||
export function setObjToUrlParams(baseUrl: string, obj: any): string { |
|||
let parameters = ''; |
|||
for (const key in obj) { |
|||
parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; |
|||
} |
|||
parameters = parameters.replace(/&$/, ''); |
|||
return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters; |
|||
} |
|||
|
|||
export function deepMerge<T = any>(src: any = {}, target: any = {}): T { |
|||
let key: string; |
|||
for (key in target) { |
|||
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]); |
|||
} |
|||
return src; |
|||
} |
|||
|
|||
export function openWindow( |
|||
url: string, |
|||
opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean } |
|||
) { |
|||
const { target = '__blank', noopener = true, noreferrer = true } = opt || {}; |
|||
const feature: string[] = []; |
|||
|
|||
noopener && feature.push('noopener=yes'); |
|||
noreferrer && feature.push('noreferrer=yes'); |
|||
|
|||
window.open(url, target, feature.join(',')); |
|||
} |
|||
|
|||
// dynamic use hook props
|
|||
export function getDynamicProps<T, U>(props: T): Partial<U> { |
|||
const ret: Recordable = {}; |
|||
|
|||
Object.keys(props).map((key) => { |
|||
ret[key] = unref((props as Recordable)[key]); |
|||
}); |
|||
|
|||
return ret as Partial<U>; |
|||
} |
|||
|
|||
export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { |
|||
if (!route) return route; |
|||
const { matched, ...opt } = route; |
|||
return { |
|||
...opt, |
|||
matched: (matched |
|||
? matched.map((item) => ({ |
|||
meta: item.meta, |
|||
name: item.name, |
|||
path: item.path, |
|||
})) |
|||
: undefined) as RouteRecordNormalized[], |
|||
}; |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
const toString = Object.prototype.toString; |
|||
|
|||
export function is(val: unknown, type: string) { |
|||
return toString.call(val) === `[object ${type}]`; |
|||
} |
|||
|
|||
export function isDef<T = unknown>(val?: T): val is T { |
|||
return typeof val !== 'undefined'; |
|||
} |
|||
|
|||
export function isUnDef<T = unknown>(val?: T): val is T { |
|||
return !isDef(val); |
|||
} |
|||
|
|||
export function isObject(val: any): val is Record<any, any> { |
|||
return val !== null && is(val, 'Object'); |
|||
} |
|||
|
|||
export function isEmpty<T = unknown>(val: T): val is T { |
|||
if (isArray(val) || isString(val)) { |
|||
return val.length === 0; |
|||
} |
|||
|
|||
if (val instanceof Map || val instanceof Set) { |
|||
return val.size === 0; |
|||
} |
|||
|
|||
if (isObject(val)) { |
|||
return Object.keys(val).length === 0; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
export function isDate(val: unknown): val is Date { |
|||
return is(val, 'Date'); |
|||
} |
|||
|
|||
export function isNull(val: unknown): val is null { |
|||
return val === null; |
|||
} |
|||
|
|||
export function isNullAndUnDef(val: unknown): val is null | undefined { |
|||
return isUnDef(val) && isNull(val); |
|||
} |
|||
|
|||
export function isNullOrUnDef(val: unknown): val is null | undefined { |
|||
return isUnDef(val) || isNull(val); |
|||
} |
|||
|
|||
export function isNumber(val: unknown): val is number { |
|||
return is(val, 'Number'); |
|||
} |
|||
|
|||
export function isPromise<T = any>(val: unknown): val is Promise<T> { |
|||
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch); |
|||
} |
|||
|
|||
export function isString(val: unknown): val is string { |
|||
return is(val, 'String'); |
|||
} |
|||
|
|||
export function isFunction(val: unknown): val is Function { |
|||
return typeof val === 'function'; |
|||
} |
|||
|
|||
export function isBoolean(val: unknown): val is boolean { |
|||
return is(val, 'Boolean'); |
|||
} |
|||
|
|||
export function isRegExp(val: unknown): val is RegExp { |
|||
return is(val, 'RegExp'); |
|||
} |
|||
|
|||
export function isArray(val: any): val is Array<any> { |
|||
return val && Array.isArray(val); |
|||
} |
|||
|
|||
export function isWindow(val: any): val is Window { |
|||
return typeof window !== 'undefined' && is(val, 'Window'); |
|||
} |
|||
|
|||
export function isElement(val: unknown): val is Element { |
|||
return isObject(val) && !!val.tagName; |
|||
} |
|||
|
|||
export const isServer = typeof window === 'undefined'; |
|||
|
|||
export const isClient = !isServer; |
|||
|
|||
export function isUrl(path: string): boolean { |
|||
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; |
|||
return reg.test(path); |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
import * as echarts from 'echarts/core'; |
|||
|
|||
import { |
|||
BarChart, |
|||
LineChart, |
|||
PieChart, |
|||
MapChart, |
|||
PictorialBarChart, |
|||
RadarChart, |
|||
} from 'echarts/charts'; |
|||
|
|||
import { |
|||
TitleComponent, |
|||
TooltipComponent, |
|||
GridComponent, |
|||
PolarComponent, |
|||
AriaComponent, |
|||
ParallelComponent, |
|||
LegendComponent, |
|||
RadarComponent, |
|||
ToolboxComponent, |
|||
DataZoomComponent, |
|||
VisualMapComponent, |
|||
TimelineComponent, |
|||
CalendarComponent, |
|||
} from 'echarts/components'; |
|||
|
|||
import { SVGRenderer } from 'echarts/renderers'; |
|||
|
|||
echarts.use([ |
|||
LegendComponent, |
|||
TitleComponent, |
|||
TooltipComponent, |
|||
GridComponent, |
|||
PolarComponent, |
|||
AriaComponent, |
|||
ParallelComponent, |
|||
BarChart, |
|||
LineChart, |
|||
PieChart, |
|||
MapChart, |
|||
RadarChart, |
|||
SVGRenderer, |
|||
PictorialBarChart, |
|||
RadarComponent, |
|||
ToolboxComponent, |
|||
DataZoomComponent, |
|||
VisualMapComponent, |
|||
TimelineComponent, |
|||
CalendarComponent, |
|||
]); |
|||
|
|||
export default echarts; |
|||
@ -1,9 +0,0 @@ |
|||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE; |
|||
|
|||
export function warn(message: string) { |
|||
console.warn(`[${projectName} warn]:${message}`); |
|||
} |
|||
|
|||
export function error(message: string) { |
|||
throw new Error(`[${projectName} error]:${message}`); |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
/** |
|||
* Mitt: Tiny functional event emitter / pubsub |
|||
* |
|||
* @name mitt |
|||
* @param {Array} [all] Optional array of event names to registered handler functions |
|||
* @returns {Function} The function's instance |
|||
*/ |
|||
export default class Mitt { |
|||
private cache: Map<string | Symbol, Array<(...data: any) => void>>; |
|||
constructor(all = []) { |
|||
// A Map of event names to registered handler functions.
|
|||
this.cache = new Map(all); |
|||
} |
|||
|
|||
once(type: string | Symbol, handler: Fn) { |
|||
const decor = (...args: any[]) => { |
|||
handler && handler.apply(this, args); |
|||
this.off(type, decor); |
|||
}; |
|||
this.on(type, decor); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Register an event handler for the given type. |
|||
* |
|||
* @param {string|symbol} type Type of event to listen for, or `"*"` for all events |
|||
* @param {Function} handler Function to call in response to given event |
|||
*/ |
|||
on(type: string | Symbol, handler: Fn) { |
|||
const handlers = this.cache?.get(type); |
|||
const added = handlers && handlers.push(handler); |
|||
if (!added) { |
|||
this.cache.set(type, [handler]); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Remove an event handler for the given type. |
|||
* |
|||
* @param {string|symbol} type Type of event to unregister `handler` from, or `"*"` |
|||
* @param {Function} handler Handler function to remove |
|||
*/ |
|||
off(type: string | Symbol, handler: Fn) { |
|||
const handlers = this.cache.get(type); |
|||
if (handlers) { |
|||
handlers.splice(handlers.indexOf(handler) >>> 0, 1); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Invoke all handlers for the given type. |
|||
* If present, `"*"` handlers are invoked after type-matched handlers. |
|||
* |
|||
* Note: Manually firing "*" handlers is not supported. |
|||
* |
|||
* @param {string|symbol} type The event type to invoke |
|||
* @param {*} [evt] Any value (object is recommended and powerful), passed to each handler |
|||
*/ |
|||
emit(type: string | Symbol, evt?: any) { |
|||
for (const handler of (this.cache.get(type) || []).slice()) handler(evt); |
|||
for (const handler of (this.cache.get('*') || []).slice()) handler(type, evt); |
|||
} |
|||
|
|||
/** |
|||
* Remove all event handlers. |
|||
* |
|||
* Note: This will also remove event handlers passed via `mitt(all: EventHandlerMap)`. |
|||
*/ |
|||
clear() { |
|||
this.cache.clear(); |
|||
} |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
import { CSSProperties, VNodeChild } from 'vue'; |
|||
import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; |
|||
|
|||
export type VueNode = VNodeChild | JSX.Element; |
|||
|
|||
type PropTypes = VueTypesInterface & { |
|||
readonly style: VueTypeValidableDef<CSSProperties>; |
|||
readonly VNodeChild: VueTypeValidableDef<VueNode>; |
|||
// readonly trueBool: VueTypeValidableDef<boolean>;
|
|||
}; |
|||
|
|||
const propTypes = createTypes({ |
|||
func: undefined, |
|||
bool: undefined, |
|||
string: undefined, |
|||
number: undefined, |
|||
object: undefined, |
|||
integer: undefined, |
|||
}) as PropTypes; |
|||
|
|||
propTypes.extend([ |
|||
{ |
|||
name: 'style', |
|||
getter: true, |
|||
type: [String, Object], |
|||
default: undefined, |
|||
}, |
|||
{ |
|||
name: 'VNodeChild', |
|||
getter: true, |
|||
type: undefined, |
|||
}, |
|||
// {
|
|||
// name: 'trueBool',
|
|||
// getter: true,
|
|||
// type: Boolean,
|
|||
// default: true,
|
|||
// },
|
|||
]); |
|||
export { propTypes }; |
|||
@ -1,28 +0,0 @@ |
|||
const hexList: string[] = []; |
|||
for (let i = 0; i <= 15; i++) { |
|||
hexList[i] = i.toString(16); |
|||
} |
|||
|
|||
export function buildUUID(): string { |
|||
let uuid = ''; |
|||
for (let i = 1; i <= 36; i++) { |
|||
if (i === 9 || i === 14 || i === 19 || i === 24) { |
|||
uuid += '-'; |
|||
} else if (i === 15) { |
|||
uuid += 4; |
|||
} else if (i === 20) { |
|||
uuid += hexList[(Math.random() * 4) | 8]; |
|||
} else { |
|||
uuid += hexList[(Math.random() * 16) | 0]; |
|||
} |
|||
} |
|||
return uuid.replace(/-/g, ''); |
|||
} |
|||
|
|||
let unique = 0; |
|||
export function buildShortUUID(prefix = ''): string { |
|||
const time = Date.now(); |
|||
const random = Math.floor(Math.random() * 1000000000); |
|||
unique++; |
|||
return prefix + '_' + random + unique + String(time); |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
<template> |
|||
<div class="flex flex-col"> |
|||
<!-- 标题 --> |
|||
<Toolbar /> |
|||
|
|||
<div class="flex flex-col flex-1 con"> |
|||
<div class="flex justify-between floor1"> |
|||
<!-- 温度 --> |
|||
<Temperature /> |
|||
<!-- 湿度 --> |
|||
<Humidity /> |
|||
</div> |
|||
|
|||
<div class="flex-1 iframe-wrap"> |
|||
<!-- <iframe src="https://www.tall.wiki/wl/0"></iframe> --> |
|||
</div> |
|||
|
|||
<div class="flex justify-between floor3"> |
|||
<!-- 吞土量实时统计 --> |
|||
<Amount /> |
|||
<!-- 仓库热力图 --> |
|||
<Heat /> |
|||
</div> |
|||
<!-- 曲线对比图 --> |
|||
<div class="floor4"> |
|||
<Curve /> |
|||
</div> |
|||
</div> |
|||
<!-- 悬浮监控 --> |
|||
<img @click="show = true" class="monitor" src="../assets/monitor.png" style /> |
|||
<img @click="show = false" class="vedio" src="../assets/vedio.png" v-show="show" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue' |
|||
import Toolbar from 'comp/toolbar.vue' |
|||
import Temperature from 'comp/temperature.vue' |
|||
import Humidity from 'comp/humidity.vue' |
|||
import Amount from 'comp/amount.vue' |
|||
import Heat from 'comp/heat.vue' |
|||
import Curve from 'comp/curve.vue' |
|||
import { getWarehouses } from 'api/api' |
|||
|
|||
export default defineComponent({ |
|||
components: { |
|||
Toolbar, |
|||
Temperature, |
|||
Humidity, |
|||
Amount, |
|||
Heat, |
|||
Curve, |
|||
}, |
|||
data() { |
|||
return { show: false } |
|||
}, |
|||
created() { |
|||
this.getData() |
|||
}, |
|||
methods: { |
|||
async getData() { |
|||
const param = { parkId: 0 } |
|||
try { |
|||
const res = await getWarehouses(param) |
|||
console.log('res: ', res) |
|||
} catch (error) { |
|||
console.log('error: ', error) |
|||
} |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.con { |
|||
margin-left: 20rem; |
|||
margin-right: 20rem; |
|||
} |
|||
|
|||
.floor1 { |
|||
width: 100%; |
|||
height: 390rem; |
|||
margin-top: 22rem; |
|||
} |
|||
|
|||
.floor3 { |
|||
width: 100%; |
|||
height: 390rem; |
|||
margin-bottom: 22rem; |
|||
} |
|||
|
|||
.floor4 { |
|||
width: 100%; |
|||
height: 390rem; |
|||
margin-bottom: 22rem; |
|||
} |
|||
|
|||
.monitor { |
|||
position: absolute; |
|||
right: 0; |
|||
top: 10%; |
|||
z-index: 99; |
|||
width: 48rem; |
|||
height: 50rem; |
|||
} |
|||
|
|||
.vedio { |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 16%; |
|||
z-index: 99; |
|||
width: 720rem; |
|||
height: 495rem; |
|||
} |
|||
</style> |
|||
|
|||
<style> |
|||
.box-bg { |
|||
width: 497rem; |
|||
height: 392rem; |
|||
background: url('../assets/box1.png') center no-repeat; |
|||
background-size: 100% 100%; |
|||
} |
|||
|
|||
.box2-bg { |
|||
width: 100%; |
|||
height: 392rem; |
|||
background: url('../assets/box2.png') center no-repeat; |
|||
background-size: 100% 100%; |
|||
} |
|||
.iframe-wrap { |
|||
padding-top: 20rem; |
|||
padding-bottom: 20rem; |
|||
} |
|||
iframe { |
|||
display: block; |
|||
width: 100%; |
|||
height: 100%; |
|||
border: none !important; |
|||
} |
|||
</style> |
|||
@ -1,299 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Actions', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/actions') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
|
|||
it('.type() - type into a DOM element', () => { |
|||
// https://on.cypress.io/type
|
|||
cy.get('.action-email') |
|||
.type('fake@email.com').should('have.value', 'fake@email.com') |
|||
|
|||
// .type() with special character sequences
|
|||
.type('{leftarrow}{rightarrow}{uparrow}{downarrow}') |
|||
.type('{del}{selectall}{backspace}') |
|||
|
|||
// .type() with key modifiers
|
|||
.type('{alt}{option}') //these are equivalent
|
|||
.type('{ctrl}{control}') //these are equivalent
|
|||
.type('{meta}{command}{cmd}') //these are equivalent
|
|||
.type('{shift}') |
|||
|
|||
// Delay each keypress by 0.1 sec
|
|||
.type('slow.typing@email.com', { delay: 100 }) |
|||
.should('have.value', 'slow.typing@email.com') |
|||
|
|||
cy.get('.action-disabled') |
|||
// Ignore error checking prior to type
|
|||
// like whether the input is visible or disabled
|
|||
.type('disabled error checking', { force: true }) |
|||
.should('have.value', 'disabled error checking') |
|||
}) |
|||
|
|||
it('.focus() - focus on a DOM element', () => { |
|||
// https://on.cypress.io/focus
|
|||
cy.get('.action-focus').focus() |
|||
.should('have.class', 'focus') |
|||
.prev().should('have.attr', 'style', 'color: orange;') |
|||
}) |
|||
|
|||
it('.blur() - blur off a DOM element', () => { |
|||
// https://on.cypress.io/blur
|
|||
cy.get('.action-blur').type('About to blur').blur() |
|||
.should('have.class', 'error') |
|||
.prev().should('have.attr', 'style', 'color: red;') |
|||
}) |
|||
|
|||
it('.clear() - clears an input or textarea element', () => { |
|||
// https://on.cypress.io/clear
|
|||
cy.get('.action-clear').type('Clear this text') |
|||
.should('have.value', 'Clear this text') |
|||
.clear() |
|||
.should('have.value', '') |
|||
}) |
|||
|
|||
it('.submit() - submit a form', () => { |
|||
// https://on.cypress.io/submit
|
|||
cy.get('.action-form') |
|||
.find('[type="text"]').type('HALFOFF') |
|||
|
|||
cy.get('.action-form').submit() |
|||
.next().should('contain', 'Your form has been submitted!') |
|||
}) |
|||
|
|||
it('.click() - click on a DOM element', () => { |
|||
// https://on.cypress.io/click
|
|||
cy.get('.action-btn').click() |
|||
|
|||
// You can click on 9 specific positions of an element:
|
|||
// -----------------------------------
|
|||
// | topLeft top topRight |
|
|||
// | |
|
|||
// | |
|
|||
// | |
|
|||
// | left center right |
|
|||
// | |
|
|||
// | |
|
|||
// | |
|
|||
// | bottomLeft bottom bottomRight |
|
|||
// -----------------------------------
|
|||
|
|||
// clicking in the center of the element is the default
|
|||
cy.get('#action-canvas').click() |
|||
|
|||
cy.get('#action-canvas').click('topLeft') |
|||
cy.get('#action-canvas').click('top') |
|||
cy.get('#action-canvas').click('topRight') |
|||
cy.get('#action-canvas').click('left') |
|||
cy.get('#action-canvas').click('right') |
|||
cy.get('#action-canvas').click('bottomLeft') |
|||
cy.get('#action-canvas').click('bottom') |
|||
cy.get('#action-canvas').click('bottomRight') |
|||
|
|||
// .click() accepts an x and y coordinate
|
|||
// that controls where the click occurs :)
|
|||
|
|||
cy.get('#action-canvas') |
|||
.click(80, 75) // click 80px on x coord and 75px on y coord
|
|||
.click(170, 75) |
|||
.click(80, 165) |
|||
.click(100, 185) |
|||
.click(125, 190) |
|||
.click(150, 185) |
|||
.click(170, 165) |
|||
|
|||
// click multiple elements by passing multiple: true
|
|||
cy.get('.action-labels>.label').click({ multiple: true }) |
|||
|
|||
// Ignore error checking prior to clicking
|
|||
cy.get('.action-opacity>.btn').click({ force: true }) |
|||
}) |
|||
|
|||
it('.dblclick() - double click on a DOM element', () => { |
|||
// https://on.cypress.io/dblclick
|
|||
|
|||
// Our app has a listener on 'dblclick' event in our 'scripts.js'
|
|||
// that hides the div and shows an input on double click
|
|||
cy.get('.action-div').dblclick().should('not.be.visible') |
|||
cy.get('.action-input-hidden').should('be.visible') |
|||
}) |
|||
|
|||
it('.rightclick() - right click on a DOM element', () => { |
|||
// https://on.cypress.io/rightclick
|
|||
|
|||
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
|
|||
// that hides the div and shows an input on right click
|
|||
cy.get('.rightclick-action-div').rightclick().should('not.be.visible') |
|||
cy.get('.rightclick-action-input-hidden').should('be.visible') |
|||
}) |
|||
|
|||
it('.check() - check a checkbox or radio element', () => { |
|||
// https://on.cypress.io/check
|
|||
|
|||
// By default, .check() will check all
|
|||
// matching checkbox or radio elements in succession, one after another
|
|||
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') |
|||
.check().should('be.checked') |
|||
|
|||
cy.get('.action-radios [type="radio"]').not('[disabled]') |
|||
.check().should('be.checked') |
|||
|
|||
// .check() accepts a value argument
|
|||
cy.get('.action-radios [type="radio"]') |
|||
.check('radio1').should('be.checked') |
|||
|
|||
// .check() accepts an array of values
|
|||
cy.get('.action-multiple-checkboxes [type="checkbox"]') |
|||
.check(['checkbox1', 'checkbox2']).should('be.checked') |
|||
|
|||
// Ignore error checking prior to checking
|
|||
cy.get('.action-checkboxes [disabled]') |
|||
.check({ force: true }).should('be.checked') |
|||
|
|||
cy.get('.action-radios [type="radio"]') |
|||
.check('radio3', { force: true }).should('be.checked') |
|||
}) |
|||
|
|||
it('.uncheck() - uncheck a checkbox element', () => { |
|||
// https://on.cypress.io/uncheck
|
|||
|
|||
// By default, .uncheck() will uncheck all matching
|
|||
// checkbox elements in succession, one after another
|
|||
cy.get('.action-check [type="checkbox"]') |
|||
.not('[disabled]') |
|||
.uncheck().should('not.be.checked') |
|||
|
|||
// .uncheck() accepts a value argument
|
|||
cy.get('.action-check [type="checkbox"]') |
|||
.check('checkbox1') |
|||
.uncheck('checkbox1').should('not.be.checked') |
|||
|
|||
// .uncheck() accepts an array of values
|
|||
cy.get('.action-check [type="checkbox"]') |
|||
.check(['checkbox1', 'checkbox3']) |
|||
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') |
|||
|
|||
// Ignore error checking prior to unchecking
|
|||
cy.get('.action-check [disabled]') |
|||
.uncheck({ force: true }).should('not.be.checked') |
|||
}) |
|||
|
|||
it('.select() - select an option in a <select> element', () => { |
|||
// https://on.cypress.io/select
|
|||
|
|||
// at first, no option should be selected
|
|||
cy.get('.action-select') |
|||
.should('have.value', '--Select a fruit--') |
|||
|
|||
// Select option(s) with matching text content
|
|||
cy.get('.action-select').select('apples') |
|||
// confirm the apples were selected
|
|||
// note that each value starts with "fr-" in our HTML
|
|||
cy.get('.action-select').should('have.value', 'fr-apples') |
|||
|
|||
cy.get('.action-select-multiple') |
|||
.select(['apples', 'oranges', 'bananas']) |
|||
// when getting multiple values, invoke "val" method first
|
|||
.invoke('val') |
|||
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas']) |
|||
|
|||
// Select option(s) with matching value
|
|||
cy.get('.action-select').select('fr-bananas') |
|||
// can attach an assertion right away to the element
|
|||
.should('have.value', 'fr-bananas') |
|||
|
|||
cy.get('.action-select-multiple') |
|||
.select(['fr-apples', 'fr-oranges', 'fr-bananas']) |
|||
.invoke('val') |
|||
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas']) |
|||
|
|||
// assert the selected values include oranges
|
|||
cy.get('.action-select-multiple') |
|||
.invoke('val').should('include', 'fr-oranges') |
|||
}) |
|||
|
|||
it('.scrollIntoView() - scroll an element into view', () => { |
|||
// https://on.cypress.io/scrollintoview
|
|||
|
|||
// normally all of these buttons are hidden,
|
|||
// because they're not within
|
|||
// the viewable area of their parent
|
|||
// (we need to scroll to see them)
|
|||
cy.get('#scroll-horizontal button') |
|||
.should('not.be.visible') |
|||
|
|||
// scroll the button into view, as if the user had scrolled
|
|||
cy.get('#scroll-horizontal button').scrollIntoView() |
|||
.should('be.visible') |
|||
|
|||
cy.get('#scroll-vertical button') |
|||
.should('not.be.visible') |
|||
|
|||
// Cypress handles the scroll direction needed
|
|||
cy.get('#scroll-vertical button').scrollIntoView() |
|||
.should('be.visible') |
|||
|
|||
cy.get('#scroll-both button') |
|||
.should('not.be.visible') |
|||
|
|||
// Cypress knows to scroll to the right and down
|
|||
cy.get('#scroll-both button').scrollIntoView() |
|||
.should('be.visible') |
|||
}) |
|||
|
|||
it('.trigger() - trigger an event on a DOM element', () => { |
|||
// https://on.cypress.io/trigger
|
|||
|
|||
// To interact with a range input (slider)
|
|||
// we need to set its value & trigger the
|
|||
// event to signal it changed
|
|||
|
|||
// Here, we invoke jQuery's val() method to set
|
|||
// the value and trigger the 'change' event
|
|||
cy.get('.trigger-input-range') |
|||
.invoke('val', 25) |
|||
.trigger('change') |
|||
.get('input[type=range]').siblings('p') |
|||
.should('have.text', '25') |
|||
}) |
|||
|
|||
it('cy.scrollTo() - scroll the window or element to a position', () => { |
|||
// https://on.cypress.io/scrollTo
|
|||
|
|||
// You can scroll to 9 specific positions of an element:
|
|||
// -----------------------------------
|
|||
// | topLeft top topRight |
|
|||
// | |
|
|||
// | |
|
|||
// | |
|
|||
// | left center right |
|
|||
// | |
|
|||
// | |
|
|||
// | |
|
|||
// | bottomLeft bottom bottomRight |
|
|||
// -----------------------------------
|
|||
|
|||
// if you chain .scrollTo() off of cy, we will
|
|||
// scroll the entire window
|
|||
cy.scrollTo('bottom') |
|||
|
|||
cy.get('#scrollable-horizontal').scrollTo('right') |
|||
|
|||
// or you can scroll to a specific coordinate:
|
|||
// (x axis, y axis) in pixels
|
|||
cy.get('#scrollable-vertical').scrollTo(250, 250) |
|||
|
|||
// or you can scroll to a specific percentage
|
|||
// of the (width, height) of the element
|
|||
cy.get('#scrollable-both').scrollTo('75%', '25%') |
|||
|
|||
// control the easing of the scroll (default is 'swing')
|
|||
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' }) |
|||
|
|||
// control the duration of the scroll (in ms)
|
|||
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 }) |
|||
}) |
|||
}) |
|||
@ -1,40 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Aliasing', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/aliasing') |
|||
}) |
|||
|
|||
it('.as() - alias a DOM element for later use', () => { |
|||
// https://on.cypress.io/as
|
|||
|
|||
// Alias a DOM element for use later
|
|||
// We don't have to traverse to the element
|
|||
// later in our code, we reference it with @
|
|||
|
|||
cy.get('.as-table').find('tbody>tr') |
|||
.first().find('td').first() |
|||
.find('button').as('firstBtn') |
|||
|
|||
// when we reference the alias, we place an
|
|||
// @ in front of its name
|
|||
cy.get('@firstBtn').click() |
|||
|
|||
cy.get('@firstBtn') |
|||
.should('have.class', 'btn-success') |
|||
.and('contain', 'Changed') |
|||
}) |
|||
|
|||
it('.as() - alias a route for later use', () => { |
|||
// Alias the route to wait for its response
|
|||
cy.server() |
|||
cy.route('GET', 'comments/*').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.network-btn').click() |
|||
|
|||
// https://on.cypress.io/wait
|
|||
cy.wait('@getComment').its('status').should('eq', 200) |
|||
}) |
|||
}) |
|||
@ -1,177 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Assertions', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/assertions') |
|||
}) |
|||
|
|||
describe('Implicit Assertions', () => { |
|||
it('.should() - make an assertion about the current subject', () => { |
|||
// https://on.cypress.io/should
|
|||
cy.get('.assertion-table') |
|||
.find('tbody tr:last') |
|||
.should('have.class', 'success') |
|||
.find('td') |
|||
.first() |
|||
// checking the text of the <td> element in various ways
|
|||
.should('have.text', 'Column content') |
|||
.should('contain', 'Column content') |
|||
.should('have.html', 'Column content') |
|||
// chai-jquery uses "is()" to check if element matches selector
|
|||
.should('match', 'td') |
|||
// to match text content against a regular expression
|
|||
// first need to invoke jQuery method text()
|
|||
// and then match using regular expression
|
|||
.invoke('text') |
|||
.should('match', /column content/i) |
|||
|
|||
// a better way to check element's text content against a regular expression
|
|||
// is to use "cy.contains"
|
|||
// https://on.cypress.io/contains
|
|||
cy.get('.assertion-table') |
|||
.find('tbody tr:last') |
|||
// finds first <td> element with text content matching regular expression
|
|||
.contains('td', /column content/i) |
|||
.should('be.visible') |
|||
|
|||
// for more information about asserting element's text
|
|||
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
|
|||
}) |
|||
|
|||
it('.and() - chain multiple assertions together', () => { |
|||
// https://on.cypress.io/and
|
|||
cy.get('.assertions-link') |
|||
.should('have.class', 'active') |
|||
.and('have.attr', 'href') |
|||
.and('include', 'cypress.io') |
|||
}) |
|||
}) |
|||
|
|||
describe('Explicit Assertions', () => { |
|||
// https://on.cypress.io/assertions
|
|||
it('expect - make an assertion about a specified subject', () => { |
|||
// We can use Chai's BDD style assertions
|
|||
expect(true).to.be.true |
|||
const o = { foo: 'bar' } |
|||
|
|||
expect(o).to.equal(o) |
|||
expect(o).to.deep.equal({ foo: 'bar' }) |
|||
// matching text using regular expression
|
|||
expect('FooBar').to.match(/bar$/i) |
|||
}) |
|||
|
|||
it('pass your own callback function to should()', () => { |
|||
// Pass a function to should that can have any number
|
|||
// of explicit assertions within it.
|
|||
// The ".should(cb)" function will be retried
|
|||
// automatically until it passes all your explicit assertions or times out.
|
|||
cy.get('.assertions-p') |
|||
.find('p') |
|||
.should(($p) => { |
|||
// https://on.cypress.io/$
|
|||
// return an array of texts from all of the p's
|
|||
// @ts-ignore TS6133 unused variable
|
|||
const texts = $p.map((i, el) => Cypress.$(el).text()) |
|||
|
|||
// jquery map returns jquery object
|
|||
// and .get() convert this to simple array
|
|||
const paragraphs = texts.get() |
|||
|
|||
// array should have length of 3
|
|||
expect(paragraphs, 'has 3 paragraphs').to.have.length(3) |
|||
|
|||
// use second argument to expect(...) to provide clear
|
|||
// message with each assertion
|
|||
expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([ |
|||
'Some text from first p', |
|||
'More text from second p', |
|||
'And even more text from third p', |
|||
]) |
|||
}) |
|||
}) |
|||
|
|||
it('finds element by class name regex', () => { |
|||
cy.get('.docs-header') |
|||
.find('div') |
|||
// .should(cb) callback function will be retried
|
|||
.should(($div) => { |
|||
expect($div).to.have.length(1) |
|||
|
|||
const className = $div[0].className |
|||
|
|||
expect(className).to.match(/heading-/) |
|||
}) |
|||
// .then(cb) callback is not retried,
|
|||
// it either passes or fails
|
|||
.then(($div) => { |
|||
expect($div, 'text content').to.have.text('Introduction') |
|||
}) |
|||
}) |
|||
|
|||
it('can throw any error', () => { |
|||
cy.get('.docs-header') |
|||
.find('div') |
|||
.should(($div) => { |
|||
if ($div.length !== 1) { |
|||
// you can throw your own errors
|
|||
throw new Error('Did not find 1 element') |
|||
} |
|||
|
|||
const className = $div[0].className |
|||
|
|||
if (!className.match(/heading-/)) { |
|||
throw new Error(`Could not find class "heading-" in ${className}`) |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
it('matches unknown text between two elements', () => { |
|||
/** |
|||
* Text from the first element. |
|||
* @type {string} |
|||
*/ |
|||
let text |
|||
|
|||
/** |
|||
* Normalizes passed text, |
|||
* useful before comparing text with spaces and different capitalization. |
|||
* @param {string} s Text to normalize |
|||
*/ |
|||
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase() |
|||
|
|||
cy.get('.two-elements') |
|||
.find('.first') |
|||
.then(($first) => { |
|||
// save text from the first element
|
|||
text = normalizeText($first.text()) |
|||
}) |
|||
|
|||
cy.get('.two-elements') |
|||
.find('.second') |
|||
.should(($div) => { |
|||
// we can massage text before comparing
|
|||
const secondText = normalizeText($div.text()) |
|||
|
|||
expect(secondText, 'second text').to.equal(text) |
|||
}) |
|||
}) |
|||
|
|||
it('assert - assert shape of an object', () => { |
|||
const person = { |
|||
name: 'Joe', |
|||
age: 20, |
|||
} |
|||
|
|||
assert.isObject(person, 'value is object') |
|||
}) |
|||
|
|||
it('retries the should callback until assertions pass', () => { |
|||
cy.get('#random-number') |
|||
.should(($div) => { |
|||
const n = parseFloat($div.text()) |
|||
|
|||
expect(n).to.be.gte(1).and.be.lte(10) |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,97 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Connectors', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/connectors') |
|||
}) |
|||
|
|||
it('.each() - iterate over an array of elements', () => { |
|||
// https://on.cypress.io/each
|
|||
cy.get('.connectors-each-ul>li') |
|||
.each(($el, index, $list) => { |
|||
console.log($el, index, $list) |
|||
}) |
|||
}) |
|||
|
|||
it('.its() - get properties on the current subject', () => { |
|||
// https://on.cypress.io/its
|
|||
cy.get('.connectors-its-ul>li') |
|||
// calls the 'length' property yielding that value
|
|||
.its('length') |
|||
.should('be.gt', 2) |
|||
}) |
|||
|
|||
it('.invoke() - invoke a function on the current subject', () => { |
|||
// our div is hidden in our script.js
|
|||
// $('.connectors-div').hide()
|
|||
|
|||
// https://on.cypress.io/invoke
|
|||
cy.get('.connectors-div').should('be.hidden') |
|||
// call the jquery method 'show' on the 'div.container'
|
|||
.invoke('show') |
|||
.should('be.visible') |
|||
}) |
|||
|
|||
it('.spread() - spread an array as individual args to callback function', () => { |
|||
// https://on.cypress.io/spread
|
|||
const arr = ['foo', 'bar', 'baz'] |
|||
|
|||
cy.wrap(arr).spread((foo, bar, baz) => { |
|||
expect(foo).to.eq('foo') |
|||
expect(bar).to.eq('bar') |
|||
expect(baz).to.eq('baz') |
|||
}) |
|||
}) |
|||
|
|||
describe('.then()', () => { |
|||
it('invokes a callback function with the current subject', () => { |
|||
// https://on.cypress.io/then
|
|||
cy.get('.connectors-list > li') |
|||
.then(($lis) => { |
|||
expect($lis, '3 items').to.have.length(3) |
|||
expect($lis.eq(0), 'first item').to.contain('Walk the dog') |
|||
expect($lis.eq(1), 'second item').to.contain('Feed the cat') |
|||
expect($lis.eq(2), 'third item').to.contain('Write JavaScript') |
|||
}) |
|||
}) |
|||
|
|||
it('yields the returned value to the next command', () => { |
|||
cy.wrap(1) |
|||
.then((num) => { |
|||
expect(num).to.equal(1) |
|||
|
|||
return 2 |
|||
}) |
|||
.then((num) => { |
|||
expect(num).to.equal(2) |
|||
}) |
|||
}) |
|||
|
|||
it('yields the original subject without return', () => { |
|||
cy.wrap(1) |
|||
.then((num) => { |
|||
expect(num).to.equal(1) |
|||
// note that nothing is returned from this callback
|
|||
}) |
|||
.then((num) => { |
|||
// this callback receives the original unchanged value 1
|
|||
expect(num).to.equal(1) |
|||
}) |
|||
}) |
|||
|
|||
it('yields the value yielded by the last Cypress command inside', () => { |
|||
cy.wrap(1) |
|||
.then((num) => { |
|||
expect(num).to.equal(1) |
|||
// note how we run a Cypress command
|
|||
// the result yielded by this Cypress command
|
|||
// will be passed to the second ".then"
|
|||
cy.wrap(2) |
|||
}) |
|||
.then((num) => { |
|||
// this callback receives the value yielded by "cy.wrap(2)"
|
|||
expect(num).to.equal(2) |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,77 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Cookies', () => { |
|||
beforeEach(() => { |
|||
Cypress.Cookies.debug(true) |
|||
|
|||
cy.visit('https://example.cypress.io/commands/cookies') |
|||
|
|||
// clear cookies again after visiting to remove
|
|||
// any 3rd party cookies picked up such as cloudflare
|
|||
cy.clearCookies() |
|||
}) |
|||
|
|||
it('cy.getCookie() - get a browser cookie', () => { |
|||
// https://on.cypress.io/getcookie
|
|||
cy.get('#getCookie .set-a-cookie').click() |
|||
|
|||
// cy.getCookie() yields a cookie object
|
|||
cy.getCookie('token').should('have.property', 'value', '123ABC') |
|||
}) |
|||
|
|||
it('cy.getCookies() - get browser cookies', () => { |
|||
// https://on.cypress.io/getcookies
|
|||
cy.getCookies().should('be.empty') |
|||
|
|||
cy.get('#getCookies .set-a-cookie').click() |
|||
|
|||
// cy.getCookies() yields an array of cookies
|
|||
cy.getCookies().should('have.length', 1).should((cookies) => { |
|||
// each cookie has these properties
|
|||
expect(cookies[0]).to.have.property('name', 'token') |
|||
expect(cookies[0]).to.have.property('value', '123ABC') |
|||
expect(cookies[0]).to.have.property('httpOnly', false) |
|||
expect(cookies[0]).to.have.property('secure', false) |
|||
expect(cookies[0]).to.have.property('domain') |
|||
expect(cookies[0]).to.have.property('path') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.setCookie() - set a browser cookie', () => { |
|||
// https://on.cypress.io/setcookie
|
|||
cy.getCookies().should('be.empty') |
|||
|
|||
cy.setCookie('foo', 'bar') |
|||
|
|||
// cy.getCookie() yields a cookie object
|
|||
cy.getCookie('foo').should('have.property', 'value', 'bar') |
|||
}) |
|||
|
|||
it('cy.clearCookie() - clear a browser cookie', () => { |
|||
// https://on.cypress.io/clearcookie
|
|||
cy.getCookie('token').should('be.null') |
|||
|
|||
cy.get('#clearCookie .set-a-cookie').click() |
|||
|
|||
cy.getCookie('token').should('have.property', 'value', '123ABC') |
|||
|
|||
// cy.clearCookies() yields null
|
|||
cy.clearCookie('token').should('be.null') |
|||
|
|||
cy.getCookie('token').should('be.null') |
|||
}) |
|||
|
|||
it('cy.clearCookies() - clear browser cookies', () => { |
|||
// https://on.cypress.io/clearcookies
|
|||
cy.getCookies().should('be.empty') |
|||
|
|||
cy.get('#clearCookies .set-a-cookie').click() |
|||
|
|||
cy.getCookies().should('have.length', 1) |
|||
|
|||
// cy.clearCookies() yields null
|
|||
cy.clearCookies() |
|||
|
|||
cy.getCookies().should('be.empty') |
|||
}) |
|||
}) |
|||
@ -1,222 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Cypress.Commands', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
// https://on.cypress.io/custom-commands
|
|||
|
|||
it('.add() - create a custom command', () => { |
|||
Cypress.Commands.add('console', { |
|||
prevSubject: true, |
|||
}, (subject, method) => { |
|||
// the previous subject is automatically received
|
|||
// and the commands arguments are shifted
|
|||
|
|||
// allow us to change the console method used
|
|||
method = method || 'log' |
|||
|
|||
// log the subject to the console
|
|||
// @ts-ignore TS7017
|
|||
console[method]('The subject is', subject) |
|||
|
|||
// whatever we return becomes the new subject
|
|||
// we don't want to change the subject so
|
|||
// we return whatever was passed in
|
|||
return subject |
|||
}) |
|||
|
|||
// @ts-ignore TS2339
|
|||
cy.get('button').console('info').then(($button) => { |
|||
// subject is still $button
|
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.Cookies', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
// https://on.cypress.io/cookies
|
|||
it('.debug() - enable or disable debugging', () => { |
|||
Cypress.Cookies.debug(true) |
|||
|
|||
// Cypress will now log in the console when
|
|||
// cookies are set or cleared
|
|||
cy.setCookie('fakeCookie', '123ABC') |
|||
cy.clearCookie('fakeCookie') |
|||
cy.setCookie('fakeCookie', '123ABC') |
|||
cy.clearCookie('fakeCookie') |
|||
cy.setCookie('fakeCookie', '123ABC') |
|||
}) |
|||
|
|||
it('.preserveOnce() - preserve cookies by key', () => { |
|||
// normally cookies are reset after each test
|
|||
cy.getCookie('fakeCookie').should('not.be.ok') |
|||
|
|||
// preserving a cookie will not clear it when
|
|||
// the next test starts
|
|||
cy.setCookie('lastCookie', '789XYZ') |
|||
Cypress.Cookies.preserveOnce('lastCookie') |
|||
}) |
|||
|
|||
it('.defaults() - set defaults for all cookies', () => { |
|||
if (Number(Cypress.version.charAt(0)) < 5) return |
|||
|
|||
// now any cookie with the name 'session_id' will
|
|||
// not be cleared before each new test runs
|
|||
Cypress.Cookies.defaults({ |
|||
// @ts-ignore
|
|||
preserve: 'session_id', |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.Server', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
// Permanently override server options for
|
|||
// all instances of cy.server()
|
|||
|
|||
// https://on.cypress.io/cypress-server
|
|||
it('.defaults() - change default config of server', () => { |
|||
Cypress.Server.defaults({ |
|||
delay: 0, |
|||
force404: false, |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.arch', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Get CPU architecture name of underlying OS', () => { |
|||
// https://on.cypress.io/arch
|
|||
expect(Cypress.arch).to.exist |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.config()', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Get and set configuration options', () => { |
|||
// https://on.cypress.io/config
|
|||
let myConfig = Cypress.config() |
|||
|
|||
expect(myConfig).to.have.property('animationDistanceThreshold', 5) |
|||
expect(myConfig).to.have.property('baseUrl', null) |
|||
expect(myConfig).to.have.property('defaultCommandTimeout', 4000) |
|||
expect(myConfig).to.have.property('requestTimeout', 5000) |
|||
expect(myConfig).to.have.property('responseTimeout', 30000) |
|||
expect(myConfig).to.have.property('viewportHeight', 660) |
|||
expect(myConfig).to.have.property('viewportWidth', 1000) |
|||
expect(myConfig).to.have.property('pageLoadTimeout', 60000) |
|||
expect(myConfig).to.have.property('waitForAnimations', true) |
|||
|
|||
expect(Cypress.config('pageLoadTimeout')).to.eq(60000) |
|||
|
|||
// this will change the config for the rest of your tests!
|
|||
Cypress.config('pageLoadTimeout', 20000) |
|||
|
|||
expect(Cypress.config('pageLoadTimeout')).to.eq(20000) |
|||
|
|||
Cypress.config('pageLoadTimeout', 60000) |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.dom', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
// https://on.cypress.io/dom
|
|||
it('.isHidden() - determine if a DOM element is hidden', () => { |
|||
let hiddenP = Cypress.$('.dom-p p.hidden').get(0) |
|||
let visibleP = Cypress.$('.dom-p p.visible').get(0) |
|||
|
|||
// our first paragraph has css class 'hidden'
|
|||
expect(Cypress.dom.isHidden(hiddenP)).to.be.true |
|||
expect(Cypress.dom.isHidden(visibleP)).to.be.false |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.env()', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
// We can set environment variables for highly dynamic values
|
|||
|
|||
// https://on.cypress.io/environment-variables
|
|||
it('Get environment variables', () => { |
|||
// https://on.cypress.io/env
|
|||
// set multiple environment variables
|
|||
Cypress.env({ |
|||
host: 'veronica.dev.local', |
|||
api_server: 'http://localhost:8888/v1/', |
|||
}) |
|||
|
|||
// get environment variable
|
|||
expect(Cypress.env('host')).to.eq('veronica.dev.local') |
|||
|
|||
// set environment variable
|
|||
Cypress.env('api_server', 'http://localhost:8888/v2/') |
|||
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/') |
|||
|
|||
// get all environment variable
|
|||
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local') |
|||
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/') |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.log', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Control what is printed to the Command Log', () => { |
|||
// https://on.cypress.io/cypress-log
|
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.platform', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Get underlying OS name', () => { |
|||
// https://on.cypress.io/platform
|
|||
expect(Cypress.platform).to.be.exist |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.version', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Get current version of Cypress being run', () => { |
|||
// https://on.cypress.io/version
|
|||
expect(Cypress.version).to.be.exist |
|||
}) |
|||
}) |
|||
|
|||
context('Cypress.spec', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/cypress-api') |
|||
}) |
|||
|
|||
it('Get current spec information', () => { |
|||
// https://on.cypress.io/spec
|
|||
// wrap the object so we can inspect it easily by clicking in the command log
|
|||
cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute']) |
|||
}) |
|||
}) |
|||
@ -1,115 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
/// JSON fixture file can be loaded directly using
|
|||
// the built-in JavaScript bundler
|
|||
// @ts-ignore
|
|||
const requiredExample = require('../../fixtures/example') |
|||
|
|||
context('Files', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/files') |
|||
}) |
|||
|
|||
beforeEach(() => { |
|||
// load example.json fixture file and store
|
|||
// in the test context object
|
|||
cy.fixture('example.json').as('example') |
|||
}) |
|||
|
|||
it('cy.fixture() - load a fixture', () => { |
|||
// https://on.cypress.io/fixture
|
|||
|
|||
// Instead of writing a response inline you can
|
|||
// use a fixture file's content.
|
|||
|
|||
cy.server() |
|||
cy.fixture('example.json').as('comment') |
|||
// when application makes an Ajax request matching "GET comments/*"
|
|||
// Cypress will intercept it and reply with object
|
|||
// from the "comment" alias
|
|||
cy.route('GET', 'comments/*', '@comment').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.fixture-btn').click() |
|||
|
|||
cy.wait('@getComment').its('responseBody') |
|||
.should('have.property', 'name') |
|||
.and('include', 'Using fixtures to represent data') |
|||
|
|||
// you can also just write the fixture in the route
|
|||
cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.fixture-btn').click() |
|||
|
|||
cy.wait('@getComment').its('responseBody') |
|||
.should('have.property', 'name') |
|||
.and('include', 'Using fixtures to represent data') |
|||
|
|||
// or write fx to represent fixture
|
|||
// by default it assumes it's .json
|
|||
cy.route('GET', 'comments/*', 'fx:example').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.fixture-btn').click() |
|||
|
|||
cy.wait('@getComment').its('responseBody') |
|||
.should('have.property', 'name') |
|||
.and('include', 'Using fixtures to represent data') |
|||
}) |
|||
|
|||
it('cy.fixture() or require - load a fixture', function () { |
|||
// we are inside the "function () { ... }"
|
|||
// callback and can use test context object "this"
|
|||
// "this.example" was loaded in "beforeEach" function callback
|
|||
expect(this.example, 'fixture in the test context') |
|||
.to.deep.equal(requiredExample) |
|||
|
|||
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
|
|||
// @ts-ignore
|
|||
cy.wrap(this.example, 'fixture vs require') |
|||
.should('deep.equal', requiredExample) |
|||
}) |
|||
|
|||
it('cy.readFile() - read file contents', () => { |
|||
// https://on.cypress.io/readfile
|
|||
|
|||
// You can read a file and yield its contents
|
|||
// The filePath is relative to your project's root.
|
|||
cy.readFile('cypress.json').then((json) => { |
|||
expect(json).to.be.an('object') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.writeFile() - write to a file', () => { |
|||
// https://on.cypress.io/writefile
|
|||
|
|||
// You can write to a file
|
|||
|
|||
// Use a response from a request to automatically
|
|||
// generate a fixture file for use later
|
|||
cy.request('https://jsonplaceholder.cypress.io/users') |
|||
.then((response) => { |
|||
cy.writeFile('cypress/fixtures/users.json', response.body) |
|||
}) |
|||
|
|||
cy.fixture('users').should((users) => { |
|||
expect(users[0].name).to.exist |
|||
}) |
|||
|
|||
// JavaScript arrays and objects are stringified
|
|||
// and formatted into text.
|
|||
cy.writeFile('cypress/fixtures/profile.json', { |
|||
id: 8739, |
|||
name: 'Jane', |
|||
email: 'jane@example.com', |
|||
}) |
|||
|
|||
cy.fixture('profile').should((profile) => { |
|||
expect(profile.name).to.eq('Jane') |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,52 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Local Storage', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/local-storage') |
|||
}) |
|||
// Although local storage is automatically cleared
|
|||
// in between tests to maintain a clean state
|
|||
// sometimes we need to clear the local storage manually
|
|||
|
|||
it('cy.clearLocalStorage() - clear all data in local storage', () => { |
|||
// https://on.cypress.io/clearlocalstorage
|
|||
cy.get('.ls-btn').click().should(() => { |
|||
expect(localStorage.getItem('prop1')).to.eq('red') |
|||
expect(localStorage.getItem('prop2')).to.eq('blue') |
|||
expect(localStorage.getItem('prop3')).to.eq('magenta') |
|||
}) |
|||
|
|||
// clearLocalStorage() yields the localStorage object
|
|||
cy.clearLocalStorage().should((ls) => { |
|||
expect(ls.getItem('prop1')).to.be.null |
|||
expect(ls.getItem('prop2')).to.be.null |
|||
expect(ls.getItem('prop3')).to.be.null |
|||
}) |
|||
|
|||
// Clear key matching string in Local Storage
|
|||
cy.get('.ls-btn').click().should(() => { |
|||
expect(localStorage.getItem('prop1')).to.eq('red') |
|||
expect(localStorage.getItem('prop2')).to.eq('blue') |
|||
expect(localStorage.getItem('prop3')).to.eq('magenta') |
|||
}) |
|||
|
|||
cy.clearLocalStorage('prop1').should((ls) => { |
|||
expect(ls.getItem('prop1')).to.be.null |
|||
expect(ls.getItem('prop2')).to.eq('blue') |
|||
expect(ls.getItem('prop3')).to.eq('magenta') |
|||
}) |
|||
|
|||
// Clear keys matching regex in Local Storage
|
|||
cy.get('.ls-btn').click().should(() => { |
|||
expect(localStorage.getItem('prop1')).to.eq('red') |
|||
expect(localStorage.getItem('prop2')).to.eq('blue') |
|||
expect(localStorage.getItem('prop3')).to.eq('magenta') |
|||
}) |
|||
|
|||
cy.clearLocalStorage(/prop1|2/).should((ls) => { |
|||
expect(ls.getItem('prop1')).to.be.null |
|||
expect(ls.getItem('prop2')).to.be.null |
|||
expect(ls.getItem('prop3')).to.eq('magenta') |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,32 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Location', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/location') |
|||
}) |
|||
|
|||
it('cy.hash() - get the current URL hash', () => { |
|||
// https://on.cypress.io/hash
|
|||
cy.hash().should('be.empty') |
|||
}) |
|||
|
|||
it('cy.location() - get window.location', () => { |
|||
// https://on.cypress.io/location
|
|||
cy.location().should((location) => { |
|||
expect(location.hash).to.be.empty |
|||
expect(location.href).to.eq('https://example.cypress.io/commands/location') |
|||
expect(location.host).to.eq('example.cypress.io') |
|||
expect(location.hostname).to.eq('example.cypress.io') |
|||
expect(location.origin).to.eq('https://example.cypress.io') |
|||
expect(location.pathname).to.eq('/commands/location') |
|||
expect(location.port).to.eq('') |
|||
expect(location.protocol).to.eq('https:') |
|||
expect(location.search).to.be.empty |
|||
}) |
|||
}) |
|||
|
|||
it('cy.url() - get the current URL', () => { |
|||
// https://on.cypress.io/url
|
|||
cy.url().should('eq', 'https://example.cypress.io/commands/location') |
|||
}) |
|||
}) |
|||
@ -1,104 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Misc', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/misc') |
|||
}) |
|||
|
|||
it('.end() - end the command chain', () => { |
|||
// https://on.cypress.io/end
|
|||
|
|||
// cy.end is useful when you want to end a chain of commands
|
|||
// and force Cypress to re-query from the root element
|
|||
cy.get('.misc-table').within(() => { |
|||
// ends the current chain and yields null
|
|||
cy.contains('Cheryl').click().end() |
|||
|
|||
// queries the entire table again
|
|||
cy.contains('Charles').click() |
|||
}) |
|||
}) |
|||
|
|||
it('cy.exec() - execute a system command', () => { |
|||
// execute a system command.
|
|||
// so you can take actions necessary for
|
|||
// your test outside the scope of Cypress.
|
|||
// https://on.cypress.io/exec
|
|||
|
|||
// we can use Cypress.platform string to
|
|||
// select appropriate command
|
|||
// https://on.cypress/io/platform
|
|||
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`) |
|||
|
|||
// on CircleCI Windows build machines we have a failure to run bash shell
|
|||
// https://github.com/cypress-io/cypress/issues/5169
|
|||
// so skip some of the tests by passing flag "--env circle=true"
|
|||
const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle') |
|||
|
|||
if (isCircleOnWindows) { |
|||
cy.log('Skipping test on CircleCI') |
|||
|
|||
return |
|||
} |
|||
|
|||
// cy.exec problem on Shippable CI
|
|||
// https://github.com/cypress-io/cypress/issues/6718
|
|||
const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable') |
|||
|
|||
if (isShippable) { |
|||
cy.log('Skipping test on ShippableCI') |
|||
|
|||
return |
|||
} |
|||
|
|||
cy.exec('echo Jane Lane') |
|||
.its('stdout').should('contain', 'Jane Lane') |
|||
|
|||
if (Cypress.platform === 'win32') { |
|||
cy.exec('print cypress.json') |
|||
.its('stderr').should('be.empty') |
|||
} else { |
|||
cy.exec('cat cypress.json') |
|||
.its('stderr').should('be.empty') |
|||
|
|||
cy.exec('pwd') |
|||
.its('code').should('eq', 0) |
|||
} |
|||
}) |
|||
|
|||
it('cy.focused() - get the DOM element that has focus', () => { |
|||
// https://on.cypress.io/focused
|
|||
cy.get('.misc-form').find('#name').click() |
|||
cy.focused().should('have.id', 'name') |
|||
|
|||
cy.get('.misc-form').find('#description').click() |
|||
cy.focused().should('have.id', 'description') |
|||
}) |
|||
|
|||
context('Cypress.Screenshot', function () { |
|||
it('cy.screenshot() - take a screenshot', () => { |
|||
// https://on.cypress.io/screenshot
|
|||
cy.screenshot('my-image') |
|||
}) |
|||
|
|||
it('Cypress.Screenshot.defaults() - change default config of screenshots', function () { |
|||
Cypress.Screenshot.defaults({ |
|||
blackout: ['.foo'], |
|||
capture: 'viewport', |
|||
clip: { x: 0, y: 0, width: 200, height: 200 }, |
|||
scale: false, |
|||
disableTimersAndAnimations: true, |
|||
screenshotOnRunFailure: true, |
|||
onBeforeScreenshot () { }, |
|||
onAfterScreenshot () { }, |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
it('cy.wrap() - wrap an object', () => { |
|||
// https://on.cypress.io/wrap
|
|||
cy.wrap({ foo: 'bar' }) |
|||
.should('have.property', 'foo') |
|||
.and('include', 'bar') |
|||
}) |
|||
}) |
|||
@ -1,56 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Navigation', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io') |
|||
cy.get('.navbar-nav').contains('Commands').click() |
|||
cy.get('.dropdown-menu').contains('Navigation').click() |
|||
}) |
|||
|
|||
it('cy.go() - go back or forward in the browser\'s history', () => { |
|||
// https://on.cypress.io/go
|
|||
|
|||
cy.location('pathname').should('include', 'navigation') |
|||
|
|||
cy.go('back') |
|||
cy.location('pathname').should('not.include', 'navigation') |
|||
|
|||
cy.go('forward') |
|||
cy.location('pathname').should('include', 'navigation') |
|||
|
|||
// clicking back
|
|||
cy.go(-1) |
|||
cy.location('pathname').should('not.include', 'navigation') |
|||
|
|||
// clicking forward
|
|||
cy.go(1) |
|||
cy.location('pathname').should('include', 'navigation') |
|||
}) |
|||
|
|||
it('cy.reload() - reload the page', () => { |
|||
// https://on.cypress.io/reload
|
|||
cy.reload() |
|||
|
|||
// reload the page without using the cache
|
|||
cy.reload(true) |
|||
}) |
|||
|
|||
it('cy.visit() - visit a remote url', () => { |
|||
// https://on.cypress.io/visit
|
|||
|
|||
// Visit any sub-domain of your current domain
|
|||
|
|||
// Pass options to the visit
|
|||
cy.visit('https://example.cypress.io/commands/navigation', { |
|||
timeout: 50000, // increase total time for the visit to resolve
|
|||
onBeforeLoad (contentWindow) { |
|||
// contentWindow is the remote page's window object
|
|||
expect(typeof contentWindow === 'object').to.be.true |
|||
}, |
|||
onLoad (contentWindow) { |
|||
// contentWindow is the remote page's window object
|
|||
expect(typeof contentWindow === 'object').to.be.true |
|||
}, |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,205 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Network Requests', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/network-requests') |
|||
}) |
|||
|
|||
// Manage AJAX / XHR requests in your app
|
|||
|
|||
it('cy.server() - control behavior of network requests and responses', () => { |
|||
// https://on.cypress.io/server
|
|||
|
|||
cy.server().should((server) => { |
|||
// the default options on server
|
|||
// you can override any of these options
|
|||
expect(server.delay).to.eq(0) |
|||
expect(server.method).to.eq('GET') |
|||
expect(server.status).to.eq(200) |
|||
expect(server.headers).to.be.null |
|||
expect(server.response).to.be.null |
|||
expect(server.onRequest).to.be.undefined |
|||
expect(server.onResponse).to.be.undefined |
|||
expect(server.onAbort).to.be.undefined |
|||
|
|||
// These options control the server behavior
|
|||
// affecting all requests
|
|||
|
|||
// pass false to disable existing route stubs
|
|||
expect(server.enable).to.be.true |
|||
// forces requests that don't match your routes to 404
|
|||
expect(server.force404).to.be.false |
|||
|
|||
if (Number(Cypress.version.charAt(0)) >= 5) { |
|||
// ignores requests from ever being logged or stubbed
|
|||
// @ts-ignore
|
|||
expect(server.ignore).to.be.a('function') |
|||
} |
|||
}) |
|||
|
|||
cy.server({ |
|||
method: 'POST', |
|||
delay: 1000, |
|||
status: 422, |
|||
response: {}, |
|||
}) |
|||
|
|||
// any route commands will now inherit the above options
|
|||
// from the server. anything we pass specifically
|
|||
// to route will override the defaults though.
|
|||
}) |
|||
|
|||
it('cy.request() - make an XHR request', () => { |
|||
// https://on.cypress.io/request
|
|||
cy.request('https://jsonplaceholder.cypress.io/comments') |
|||
.should((response) => { |
|||
expect(response.status).to.eq(200) |
|||
// the server sometimes gets an extra comment posted from another machine
|
|||
// which gets returned as 1 extra object
|
|||
expect(response.body).to.have.property('length').and.be.oneOf([500, 501]) |
|||
expect(response).to.have.property('headers') |
|||
expect(response).to.have.property('duration') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.request() - verify response using BDD syntax', () => { |
|||
cy.request('https://jsonplaceholder.cypress.io/comments') |
|||
.then((response) => { |
|||
// https://on.cypress.io/assertions
|
|||
expect(response).property('status').to.equal(200) |
|||
expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501]) |
|||
expect(response).to.include.keys('headers', 'duration') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.request() with query parameters', () => { |
|||
// will execute request
|
|||
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
|
|||
cy.request({ |
|||
url: 'https://jsonplaceholder.cypress.io/comments', |
|||
qs: { |
|||
postId: 1, |
|||
id: 3, |
|||
}, |
|||
}) |
|||
.its('body') |
|||
.should('be.an', 'array') |
|||
.and('have.length', 1) |
|||
.its('0') // yields first element of the array
|
|||
.should('contain', { |
|||
postId: 1, |
|||
id: 3, |
|||
}) |
|||
}) |
|||
|
|||
it('cy.request() - pass result to the second request', () => { |
|||
// first, let's find out the userId of the first user we have
|
|||
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') |
|||
.its('body') // yields the response object
|
|||
.its('0') // yields the first element of the returned list
|
|||
// the above two commands its('body').its('0')
|
|||
// can be written as its('body.0')
|
|||
// if you do not care about TypeScript checks
|
|||
.then((user) => { |
|||
expect(user).property('id').to.be.a('number') |
|||
// make a new post on behalf of the user
|
|||
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { |
|||
userId: user.id, |
|||
title: 'Cypress Test Runner', |
|||
body: 'Fast, easy and reliable testing for anything that runs in a browser.', |
|||
}) |
|||
}) |
|||
// note that the value here is the returned value of the 2nd request
|
|||
// which is the new post object
|
|||
.then((response) => { |
|||
expect(response).property('status').to.equal(201) // new entity created
|
|||
expect(response).property('body').to.contain({ |
|||
title: 'Cypress Test Runner', |
|||
}) |
|||
|
|||
// we don't know the exact post id - only that it will be > 100
|
|||
// since JSONPlaceholder has built-in 100 posts
|
|||
expect(response.body).property('id').to.be.a('number') |
|||
.and.to.be.gt(100) |
|||
|
|||
// we don't know the user id here - since it was in above closure
|
|||
// so in this test just confirm that the property is there
|
|||
expect(response.body).property('userId').to.be.a('number') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.request() - save response in the shared test context', () => { |
|||
// https://on.cypress.io/variables-and-aliases
|
|||
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') |
|||
.its('body').its('0') // yields the first element of the returned list
|
|||
.as('user') // saves the object in the test context
|
|||
.then(function () { |
|||
// NOTE 👀
|
|||
// By the time this callback runs the "as('user')" command
|
|||
// has saved the user object in the test context.
|
|||
// To access the test context we need to use
|
|||
// the "function () { ... }" callback form,
|
|||
// otherwise "this" points at a wrong or undefined object!
|
|||
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { |
|||
userId: this.user.id, |
|||
title: 'Cypress Test Runner', |
|||
body: 'Fast, easy and reliable testing for anything that runs in a browser.', |
|||
}) |
|||
.its('body').as('post') // save the new post from the response
|
|||
}) |
|||
.then(function () { |
|||
// When this callback runs, both "cy.request" API commands have finished
|
|||
// and the test context has "user" and "post" objects set.
|
|||
// Let's verify them.
|
|||
expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id) |
|||
}) |
|||
}) |
|||
|
|||
it('cy.route() - route responses to matching requests', () => { |
|||
// https://on.cypress.io/route
|
|||
|
|||
let message = 'whoa, this comment does not exist' |
|||
|
|||
cy.server() |
|||
|
|||
// Listen to GET to comments/1
|
|||
cy.route('GET', 'comments/*').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.network-btn').click() |
|||
|
|||
// https://on.cypress.io/wait
|
|||
cy.wait('@getComment').its('status').should('eq', 200) |
|||
|
|||
// Listen to POST to comments
|
|||
cy.route('POST', '/comments').as('postComment') |
|||
|
|||
// we have code that posts a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.network-post').click() |
|||
cy.wait('@postComment').should((xhr) => { |
|||
expect(xhr.requestBody).to.include('email') |
|||
expect(xhr.requestHeaders).to.have.property('Content-Type') |
|||
expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()') |
|||
}) |
|||
|
|||
// Stub a response to PUT comments/ ****
|
|||
cy.route({ |
|||
method: 'PUT', |
|||
url: 'comments/*', |
|||
status: 404, |
|||
response: { error: message }, |
|||
delay: 500, |
|||
}).as('putComment') |
|||
|
|||
// we have code that puts a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.network-put').click() |
|||
|
|||
cy.wait('@putComment') |
|||
|
|||
// our 404 statusCode logic in scripts.js executed
|
|||
cy.get('.network-put-comment').should('contain', message) |
|||
}) |
|||
}) |
|||
@ -1,114 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Querying', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/querying') |
|||
}) |
|||
|
|||
// The most commonly used query is 'cy.get()', you can
|
|||
// think of this like the '$' in jQuery
|
|||
|
|||
it('cy.get() - query DOM elements', () => { |
|||
// https://on.cypress.io/get
|
|||
|
|||
cy.get('#query-btn').should('contain', 'Button') |
|||
|
|||
cy.get('.query-btn').should('contain', 'Button') |
|||
|
|||
cy.get('#querying .well>button:first').should('contain', 'Button') |
|||
// ↲
|
|||
// Use CSS selectors just like jQuery
|
|||
|
|||
cy.get('[data-test-id="test-example"]').should('have.class', 'example') |
|||
|
|||
// 'cy.get()' yields jQuery object, you can get its attribute
|
|||
// by invoking `.attr()` method
|
|||
cy.get('[data-test-id="test-example"]') |
|||
.invoke('attr', 'data-test-id') |
|||
.should('equal', 'test-example') |
|||
|
|||
// or you can get element's CSS property
|
|||
cy.get('[data-test-id="test-example"]') |
|||
.invoke('css', 'position') |
|||
.should('equal', 'static') |
|||
|
|||
// or use assertions directly during 'cy.get()'
|
|||
// https://on.cypress.io/assertions
|
|||
cy.get('[data-test-id="test-example"]') |
|||
.should('have.attr', 'data-test-id', 'test-example') |
|||
.and('have.css', 'position', 'static') |
|||
}) |
|||
|
|||
it('cy.contains() - query DOM elements with matching content', () => { |
|||
// https://on.cypress.io/contains
|
|||
cy.get('.query-list') |
|||
.contains('bananas') |
|||
.should('have.class', 'third') |
|||
|
|||
// we can pass a regexp to `.contains()`
|
|||
cy.get('.query-list') |
|||
.contains(/^b\w+/) |
|||
.should('have.class', 'third') |
|||
|
|||
cy.get('.query-list') |
|||
.contains('apples') |
|||
.should('have.class', 'first') |
|||
|
|||
// passing a selector to contains will
|
|||
// yield the selector containing the text
|
|||
cy.get('#querying') |
|||
.contains('ul', 'oranges') |
|||
.should('have.class', 'query-list') |
|||
|
|||
cy.get('.query-button') |
|||
.contains('Save Form') |
|||
.should('have.class', 'btn') |
|||
}) |
|||
|
|||
it('.within() - query DOM elements within a specific element', () => { |
|||
// https://on.cypress.io/within
|
|||
cy.get('.query-form').within(() => { |
|||
cy.get('input:first').should('have.attr', 'placeholder', 'Email') |
|||
cy.get('input:last').should('have.attr', 'placeholder', 'Password') |
|||
}) |
|||
}) |
|||
|
|||
it('cy.root() - query the root DOM element', () => { |
|||
// https://on.cypress.io/root
|
|||
|
|||
// By default, root is the document
|
|||
cy.root().should('match', 'html') |
|||
|
|||
cy.get('.query-ul').within(() => { |
|||
// In this within, the root is now the ul DOM element
|
|||
cy.root().should('have.class', 'query-ul') |
|||
}) |
|||
}) |
|||
|
|||
it('best practices - selecting elements', () => { |
|||
// https://on.cypress.io/best-practices#Selecting-Elements
|
|||
cy.get('[data-cy=best-practices-selecting-elements]').within(() => { |
|||
// Worst - too generic, no context
|
|||
cy.get('button').click() |
|||
|
|||
// Bad. Coupled to styling. Highly subject to change.
|
|||
cy.get('.btn.btn-large').click() |
|||
|
|||
// Average. Coupled to the `name` attribute which has HTML semantics.
|
|||
cy.get('[name=submission]').click() |
|||
|
|||
// Better. But still coupled to styling or JS event listeners.
|
|||
cy.get('#main').click() |
|||
|
|||
// Slightly better. Uses an ID but also ensures the element
|
|||
// has an ARIA role attribute
|
|||
cy.get('#main[role=button]').click() |
|||
|
|||
// Much better. But still coupled to text content that may change.
|
|||
cy.contains('Submit').click() |
|||
|
|||
// Best. Insulated from all changes.
|
|||
cy.get('[data-cy=submit]').click() |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,205 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
// remove no check once Cypress.sinon is typed
|
|||
// https://github.com/cypress-io/cypress/issues/6720
|
|||
|
|||
context('Spies, Stubs, and Clock', () => { |
|||
it('cy.spy() - wrap a method in a spy', () => { |
|||
// https://on.cypress.io/spy
|
|||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') |
|||
|
|||
const obj = { |
|||
foo () {}, |
|||
} |
|||
|
|||
const spy = cy.spy(obj, 'foo').as('anyArgs') |
|||
|
|||
obj.foo() |
|||
|
|||
expect(spy).to.be.called |
|||
}) |
|||
|
|||
it('cy.spy() retries until assertions pass', () => { |
|||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') |
|||
|
|||
const obj = { |
|||
/** |
|||
* Prints the argument passed |
|||
* @param x {any} |
|||
*/ |
|||
foo (x) { |
|||
console.log('obj.foo called with', x) |
|||
}, |
|||
} |
|||
|
|||
cy.spy(obj, 'foo').as('foo') |
|||
|
|||
setTimeout(() => { |
|||
obj.foo('first') |
|||
}, 500) |
|||
|
|||
setTimeout(() => { |
|||
obj.foo('second') |
|||
}, 2500) |
|||
|
|||
cy.get('@foo').should('have.been.calledTwice') |
|||
}) |
|||
|
|||
it('cy.stub() - create a stub and/or replace a function with stub', () => { |
|||
// https://on.cypress.io/stub
|
|||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') |
|||
|
|||
const obj = { |
|||
/** |
|||
* prints both arguments to the console |
|||
* @param a {string} |
|||
* @param b {string} |
|||
*/ |
|||
foo (a, b) { |
|||
console.log('a', a, 'b', b) |
|||
}, |
|||
} |
|||
|
|||
const stub = cy.stub(obj, 'foo').as('foo') |
|||
|
|||
obj.foo('foo', 'bar') |
|||
|
|||
expect(stub).to.be.called |
|||
}) |
|||
|
|||
it('cy.clock() - control time in the browser', () => { |
|||
// https://on.cypress.io/clock
|
|||
|
|||
// create the date in UTC so its always the same
|
|||
// no matter what local timezone the browser is running in
|
|||
const now = new Date(Date.UTC(2017, 2, 14)).getTime() |
|||
|
|||
cy.clock(now) |
|||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') |
|||
cy.get('#clock-div').click() |
|||
.should('have.text', '1489449600') |
|||
}) |
|||
|
|||
it('cy.tick() - move time in the browser', () => { |
|||
// https://on.cypress.io/tick
|
|||
|
|||
// create the date in UTC so its always the same
|
|||
// no matter what local timezone the browser is running in
|
|||
const now = new Date(Date.UTC(2017, 2, 14)).getTime() |
|||
|
|||
cy.clock(now) |
|||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') |
|||
cy.get('#tick-div').click() |
|||
.should('have.text', '1489449600') |
|||
|
|||
cy.tick(10000) // 10 seconds passed
|
|||
cy.get('#tick-div').click() |
|||
.should('have.text', '1489449610') |
|||
}) |
|||
|
|||
it('cy.stub() matches depending on arguments', () => { |
|||
// see all possible matchers at
|
|||
// https://sinonjs.org/releases/latest/matchers/
|
|||
const greeter = { |
|||
/** |
|||
* Greets a person |
|||
* @param {string} name |
|||
*/ |
|||
greet (name) { |
|||
return `Hello, ${name}!` |
|||
}, |
|||
} |
|||
|
|||
cy.stub(greeter, 'greet') |
|||
.callThrough() // if you want non-matched calls to call the real method
|
|||
.withArgs(Cypress.sinon.match.string).returns('Hi') |
|||
.withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name')) |
|||
|
|||
expect(greeter.greet('World')).to.equal('Hi') |
|||
// @ts-ignore
|
|||
expect(() => greeter.greet(42)).to.throw('Invalid name') |
|||
expect(greeter.greet).to.have.been.calledTwice |
|||
|
|||
// non-matched calls goes the actual method
|
|||
// @ts-ignore
|
|||
expect(greeter.greet()).to.equal('Hello, undefined!') |
|||
}) |
|||
|
|||
it('matches call arguments using Sinon matchers', () => { |
|||
// see all possible matchers at
|
|||
// https://sinonjs.org/releases/latest/matchers/
|
|||
const calculator = { |
|||
/** |
|||
* returns the sum of two arguments |
|||
* @param a {number} |
|||
* @param b {number} |
|||
*/ |
|||
add (a, b) { |
|||
return a + b |
|||
}, |
|||
} |
|||
|
|||
const spy = cy.spy(calculator, 'add').as('add') |
|||
|
|||
expect(calculator.add(2, 3)).to.equal(5) |
|||
|
|||
// if we want to assert the exact values used during the call
|
|||
expect(spy).to.be.calledWith(2, 3) |
|||
|
|||
// let's confirm "add" method was called with two numbers
|
|||
expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number) |
|||
|
|||
// alternatively, provide the value to match
|
|||
expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)) |
|||
|
|||
// match any value
|
|||
expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3) |
|||
|
|||
// match any value from a list
|
|||
expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3) |
|||
|
|||
/** |
|||
* Returns true if the given number is event |
|||
* @param {number} x |
|||
*/ |
|||
const isEven = (x) => x % 2 === 0 |
|||
|
|||
// expect the value to pass a custom predicate function
|
|||
// the second argument to "sinon.match(predicate, message)" is
|
|||
// shown if the predicate does not pass and assertion fails
|
|||
expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3) |
|||
|
|||
/** |
|||
* Returns a function that checks if a given number is larger than the limit |
|||
* @param {number} limit |
|||
* @returns {(x: number) => boolean} |
|||
*/ |
|||
const isGreaterThan = (limit) => (x) => x > limit |
|||
|
|||
/** |
|||
* Returns a function that checks if a given number is less than the limit |
|||
* @param {number} limit |
|||
* @returns {(x: number) => boolean} |
|||
*/ |
|||
const isLessThan = (limit) => (x) => x < limit |
|||
|
|||
// you can combine several matchers using "and", "or"
|
|||
expect(spy).to.be.calledWith( |
|||
Cypress.sinon.match.number, |
|||
Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')), |
|||
) |
|||
|
|||
expect(spy).to.be.calledWith( |
|||
Cypress.sinon.match.number, |
|||
Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)), |
|||
) |
|||
|
|||
// matchers can be used from BDD assertions
|
|||
cy.get('@add').should('have.been.calledWith', |
|||
Cypress.sinon.match.number, Cypress.sinon.match(3)) |
|||
|
|||
// you can alias matchers for shorter test code
|
|||
const { match: M } = Cypress.sinon |
|||
|
|||
cy.get('@add').should('have.been.calledWith', M.number, M(3)) |
|||
}) |
|||
}) |
|||
@ -1,121 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Traversal', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/traversal') |
|||
}) |
|||
|
|||
it('.children() - get child DOM elements', () => { |
|||
// https://on.cypress.io/children
|
|||
cy.get('.traversal-breadcrumb') |
|||
.children('.active') |
|||
.should('contain', 'Data') |
|||
}) |
|||
|
|||
it('.closest() - get closest ancestor DOM element', () => { |
|||
// https://on.cypress.io/closest
|
|||
cy.get('.traversal-badge') |
|||
.closest('ul') |
|||
.should('have.class', 'list-group') |
|||
}) |
|||
|
|||
it('.eq() - get a DOM element at a specific index', () => { |
|||
// https://on.cypress.io/eq
|
|||
cy.get('.traversal-list>li') |
|||
.eq(1).should('contain', 'siamese') |
|||
}) |
|||
|
|||
it('.filter() - get DOM elements that match the selector', () => { |
|||
// https://on.cypress.io/filter
|
|||
cy.get('.traversal-nav>li') |
|||
.filter('.active').should('contain', 'About') |
|||
}) |
|||
|
|||
it('.find() - get descendant DOM elements of the selector', () => { |
|||
// https://on.cypress.io/find
|
|||
cy.get('.traversal-pagination') |
|||
.find('li').find('a') |
|||
.should('have.length', 7) |
|||
}) |
|||
|
|||
it('.first() - get first DOM element', () => { |
|||
// https://on.cypress.io/first
|
|||
cy.get('.traversal-table td') |
|||
.first().should('contain', '1') |
|||
}) |
|||
|
|||
it('.last() - get last DOM element', () => { |
|||
// https://on.cypress.io/last
|
|||
cy.get('.traversal-buttons .btn') |
|||
.last().should('contain', 'Submit') |
|||
}) |
|||
|
|||
it('.next() - get next sibling DOM element', () => { |
|||
// https://on.cypress.io/next
|
|||
cy.get('.traversal-ul') |
|||
.contains('apples').next().should('contain', 'oranges') |
|||
}) |
|||
|
|||
it('.nextAll() - get all next sibling DOM elements', () => { |
|||
// https://on.cypress.io/nextall
|
|||
cy.get('.traversal-next-all') |
|||
.contains('oranges') |
|||
.nextAll().should('have.length', 3) |
|||
}) |
|||
|
|||
it('.nextUntil() - get next sibling DOM elements until next el', () => { |
|||
// https://on.cypress.io/nextuntil
|
|||
cy.get('#veggies') |
|||
.nextUntil('#nuts').should('have.length', 3) |
|||
}) |
|||
|
|||
it('.not() - remove DOM elements from set of DOM elements', () => { |
|||
// https://on.cypress.io/not
|
|||
cy.get('.traversal-disabled .btn') |
|||
.not('[disabled]').should('not.contain', 'Disabled') |
|||
}) |
|||
|
|||
it('.parent() - get parent DOM element from DOM elements', () => { |
|||
// https://on.cypress.io/parent
|
|||
cy.get('.traversal-mark') |
|||
.parent().should('contain', 'Morbi leo risus') |
|||
}) |
|||
|
|||
it('.parents() - get parent DOM elements from DOM elements', () => { |
|||
// https://on.cypress.io/parents
|
|||
cy.get('.traversal-cite') |
|||
.parents().should('match', 'blockquote') |
|||
}) |
|||
|
|||
it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => { |
|||
// https://on.cypress.io/parentsuntil
|
|||
cy.get('.clothes-nav') |
|||
.find('.active') |
|||
.parentsUntil('.clothes-nav') |
|||
.should('have.length', 2) |
|||
}) |
|||
|
|||
it('.prev() - get previous sibling DOM element', () => { |
|||
// https://on.cypress.io/prev
|
|||
cy.get('.birds').find('.active') |
|||
.prev().should('contain', 'Lorikeets') |
|||
}) |
|||
|
|||
it('.prevAll() - get all previous sibling DOM elements', () => { |
|||
// https://on.cypress.io/prevAll
|
|||
cy.get('.fruits-list').find('.third') |
|||
.prevAll().should('have.length', 2) |
|||
}) |
|||
|
|||
it('.prevUntil() - get all previous sibling DOM elements until el', () => { |
|||
// https://on.cypress.io/prevUntil
|
|||
cy.get('.foods-list').find('#nuts') |
|||
.prevUntil('#veggies').should('have.length', 3) |
|||
}) |
|||
|
|||
it('.siblings() - get all sibling DOM elements', () => { |
|||
// https://on.cypress.io/siblings
|
|||
cy.get('.traversal-pills .active') |
|||
.siblings().should('have.length', 2) |
|||
}) |
|||
}) |
|||
@ -1,136 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Utilities', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/utilities') |
|||
}) |
|||
|
|||
it('Cypress._ - call a lodash method', () => { |
|||
// https://on.cypress.io/_
|
|||
cy.request('https://jsonplaceholder.cypress.io/users') |
|||
.then((response) => { |
|||
let ids = Cypress._.chain(response.body).map('id').take(3).value() |
|||
|
|||
expect(ids).to.deep.eq([1, 2, 3]) |
|||
}) |
|||
}) |
|||
|
|||
it('Cypress.$ - call a jQuery method', () => { |
|||
// https://on.cypress.io/$
|
|||
let $li = Cypress.$('.utility-jquery li:first') |
|||
|
|||
cy.wrap($li) |
|||
.should('not.have.class', 'active') |
|||
.click() |
|||
.should('have.class', 'active') |
|||
}) |
|||
|
|||
it('Cypress.Blob - blob utilities and base64 string conversion', () => { |
|||
// https://on.cypress.io/blob
|
|||
cy.get('.utility-blob').then(($div) => { |
|||
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL
|
|||
// get the dataUrl string for the javascript-logo
|
|||
return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous') |
|||
.then((dataUrl) => { |
|||
// create an <img> element and set its src to the dataUrl
|
|||
let img = Cypress.$('<img />', { src: dataUrl }) |
|||
|
|||
// need to explicitly return cy here since we are initially returning
|
|||
// the Cypress.Blob.imgSrcToDataURL promise to our test
|
|||
// append the image
|
|||
$div.append(img) |
|||
|
|||
cy.get('.utility-blob img').click() |
|||
.should('have.attr', 'src', dataUrl) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
it('Cypress.minimatch - test out glob patterns against strings', () => { |
|||
// https://on.cypress.io/minimatch
|
|||
let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', { |
|||
matchBase: true, |
|||
}) |
|||
|
|||
expect(matching, 'matching wildcard').to.be.true |
|||
|
|||
matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', { |
|||
matchBase: true, |
|||
}) |
|||
|
|||
expect(matching, 'comments').to.be.false |
|||
|
|||
// ** matches against all downstream path segments
|
|||
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', { |
|||
matchBase: true, |
|||
}) |
|||
|
|||
expect(matching, 'comments').to.be.true |
|||
|
|||
// whereas * matches only the next path segment
|
|||
|
|||
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', { |
|||
matchBase: false, |
|||
}) |
|||
|
|||
expect(matching, 'comments').to.be.false |
|||
}) |
|||
|
|||
it('Cypress.moment() - format or parse dates using a moment method', () => { |
|||
// https://on.cypress.io/moment
|
|||
const time = Cypress.moment('2014-04-25T19:38:53.196Z').utc().format('h:mm A') |
|||
|
|||
expect(time).to.be.a('string') |
|||
|
|||
cy.get('.utility-moment').contains('3:38 PM') |
|||
.should('have.class', 'badge') |
|||
|
|||
// the time in the element should be between 3pm and 5pm
|
|||
const start = Cypress.moment('3:00 PM', 'LT') |
|||
const end = Cypress.moment('5:00 PM', 'LT') |
|||
|
|||
cy.get('.utility-moment .badge') |
|||
.should(($el) => { |
|||
// parse American time like "3:38 PM"
|
|||
const m = Cypress.moment($el.text().trim(), 'LT') |
|||
|
|||
// display hours + minutes + AM|PM
|
|||
const f = 'h:mm A' |
|||
|
|||
expect(m.isBetween(start, end), |
|||
`${m.format(f)} should be between ${start.format(f)} and ${end.format(f)}`).to.be.true |
|||
}) |
|||
}) |
|||
|
|||
it('Cypress.Promise - instantiate a bluebird promise', () => { |
|||
// https://on.cypress.io/promise
|
|||
let waited = false |
|||
|
|||
/** |
|||
* @return Bluebird<string> |
|||
*/ |
|||
function waitOneSecond () { |
|||
// return a promise that resolves after 1 second
|
|||
// @ts-ignore TS2351 (new Cypress.Promise)
|
|||
return new Cypress.Promise((resolve, reject) => { |
|||
setTimeout(() => { |
|||
// set waited to true
|
|||
waited = true |
|||
|
|||
// resolve with 'foo' string
|
|||
resolve('foo') |
|||
}, 1000) |
|||
}) |
|||
} |
|||
|
|||
cy.then(() => { |
|||
// return a promise to cy.then() that
|
|||
// is awaited until it resolves
|
|||
// @ts-ignore TS7006
|
|||
return waitOneSecond().then((str) => { |
|||
expect(str).to.eq('foo') |
|||
expect(waited).to.be.true |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,59 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Viewport', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/viewport') |
|||
}) |
|||
|
|||
it('cy.viewport() - set the viewport size and dimension', () => { |
|||
// https://on.cypress.io/viewport
|
|||
|
|||
cy.get('#navbar').should('be.visible') |
|||
cy.viewport(320, 480) |
|||
|
|||
// the navbar should have collapse since our screen is smaller
|
|||
cy.get('#navbar').should('not.be.visible') |
|||
cy.get('.navbar-toggle').should('be.visible').click() |
|||
cy.get('.nav').find('a').should('be.visible') |
|||
|
|||
// lets see what our app looks like on a super large screen
|
|||
cy.viewport(2999, 2999) |
|||
|
|||
// cy.viewport() accepts a set of preset sizes
|
|||
// to easily set the screen to a device's width and height
|
|||
|
|||
// We added a cy.wait() between each viewport change so you can see
|
|||
// the change otherwise it is a little too fast to see :)
|
|||
|
|||
cy.viewport('macbook-15') |
|||
cy.wait(200) |
|||
cy.viewport('macbook-13') |
|||
cy.wait(200) |
|||
cy.viewport('macbook-11') |
|||
cy.wait(200) |
|||
cy.viewport('ipad-2') |
|||
cy.wait(200) |
|||
cy.viewport('ipad-mini') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-6+') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-6') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-5') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-4') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-3') |
|||
cy.wait(200) |
|||
|
|||
// cy.viewport() accepts an orientation for all presets
|
|||
// the default orientation is 'portrait'
|
|||
cy.viewport('ipad-2', 'portrait') |
|||
cy.wait(200) |
|||
cy.viewport('iphone-4', 'landscape') |
|||
cy.wait(200) |
|||
|
|||
// The viewport will be reset back to the default dimensions
|
|||
// in between tests (the default can be set in cypress.json)
|
|||
}) |
|||
}) |
|||
@ -1,33 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Waiting', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/waiting') |
|||
}) |
|||
// BE CAREFUL of adding unnecessary wait times.
|
|||
// https://on.cypress.io/best-practices#Unnecessary-Waiting
|
|||
|
|||
// https://on.cypress.io/wait
|
|||
it('cy.wait() - wait for a specific amount of time', () => { |
|||
cy.get('.wait-input1').type('Wait 1000ms after typing') |
|||
cy.wait(1000) |
|||
cy.get('.wait-input2').type('Wait 1000ms after typing') |
|||
cy.wait(1000) |
|||
cy.get('.wait-input3').type('Wait 1000ms after typing') |
|||
cy.wait(1000) |
|||
}) |
|||
|
|||
it('cy.wait() - wait for a specific route', () => { |
|||
cy.server() |
|||
|
|||
// Listen to GET to comments/1
|
|||
cy.route('GET', 'comments/*').as('getComment') |
|||
|
|||
// we have code that gets a comment when
|
|||
// the button is clicked in scripts.js
|
|||
cy.get('.network-btn').click() |
|||
|
|||
// wait for GET comments/1
|
|||
cy.wait('@getComment').its('status').should('eq', 200) |
|||
}) |
|||
}) |
|||
@ -1,22 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
|
|||
context('Window', () => { |
|||
beforeEach(() => { |
|||
cy.visit('https://example.cypress.io/commands/window') |
|||
}) |
|||
|
|||
it('cy.window() - get the global window object', () => { |
|||
// https://on.cypress.io/window
|
|||
cy.window().should('have.property', 'top') |
|||
}) |
|||
|
|||
it('cy.document() - get the document object', () => { |
|||
// https://on.cypress.io/document
|
|||
cy.document().should('have.property', 'charset').and('eq', 'UTF-8') |
|||
}) |
|||
|
|||
it('cy.title() - get the title', () => { |
|||
// https://on.cypress.io/title
|
|||
cy.title().should('include', 'Kitchen Sink') |
|||
}) |
|||
}) |
|||
@ -1,5 +0,0 @@ |
|||
{ |
|||
"name": "Using fixtures to represent data", |
|||
"email": "hello@cypress.io", |
|||
"body": "Fixtures are a great way to mock data for responses to routes" |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
// https://docs.cypress.io/api/introduction/api.html
|
|||
|
|||
describe('Homepage', () => { |
|||
it('Shows correct text', () => { |
|||
cy.visit('/') |
|||
|
|||
cy.contains('h1', 'Hello Vue 3 + Vite + Tailwind CSS') |
|||
}) |
|||
}) |
|||
@ -1,21 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
// ***********************************************************
|
|||
// This example plugins/index.js can be used to load plugins
|
|||
//
|
|||
// You can change the location of this file or turn off loading
|
|||
// the plugins file with the 'pluginsFile' configuration option.
|
|||
//
|
|||
// You can read more here:
|
|||
// https://on.cypress.io/plugins-guide
|
|||
// ***********************************************************
|
|||
|
|||
// This function is called when a project is opened or re-opened (e.g. due to
|
|||
// the project's config changing)
|
|||
|
|||
/** |
|||
* @type {Cypress.PluginConfig} |
|||
*/ |
|||
module.exports = (on, config) => { |
|||
// `on` is used to hook into various events Cypress emits
|
|||
// `config` is the resolved Cypress config
|
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ***********************************************
|
|||
// This example commands.js shows you how to
|
|||
// create various custom commands and overwrite
|
|||
// existing commands.
|
|||
//
|
|||
// For more comprehensive examples of custom
|
|||
// commands please read more here:
|
|||
// https://on.cypress.io/custom-commands
|
|||
// ***********************************************
|
|||
//
|
|||
//
|
|||
// -- This is a parent command --
|
|||
// Cypress.Commands.add("login", (email, password) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This is a child command --
|
|||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This is a dual command --
|
|||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This will overwrite an existing command --
|
|||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|||
@ -1,34 +0,0 @@ |
|||
/// <reference types="cypress" />
|
|||
// ***********************************************************
|
|||
// This example support/index.js is processed and
|
|||
// loaded automatically before your test files.
|
|||
//
|
|||
// This is a great place to put global configuration and
|
|||
// behavior that modifies Cypress.
|
|||
//
|
|||
// You can change the location of this file or turn off
|
|||
// automatically serving support files with the
|
|||
// 'supportFile' configuration option.
|
|||
//
|
|||
// You can read more here:
|
|||
// https://on.cypress.io/configuration
|
|||
// ***********************************************************
|
|||
|
|||
// Import commands.js using ES2015 syntax:
|
|||
import './commands' |
|||
|
|||
Cypress.on('window:before:load', (win) => { |
|||
win.handleFromCypress = function (request) { |
|||
return fetch(request.url, { |
|||
method: request.method, |
|||
headers: request.requestHeaders, |
|||
body: request.requestBody, |
|||
}).then((res) => { |
|||
const content = |
|||
res.headers.map['content-type'] === 'application/json' ? res.json() : res.text() |
|||
return new Promise((resolve) => { |
|||
content.then((body) => resolve([res.status, res.headers, body])) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
@ -1,16 +1,47 @@ |
|||
import * as path from 'path' |
|||
import { defineConfig } from 'vite' |
|||
import { defineConfig, loadEnv } from 'vite' |
|||
import vue from '@vitejs/plugin-vue' |
|||
import pkg from './package.json' |
|||
|
|||
export default defineConfig({ |
|||
export default ({ mode }) => { |
|||
return defineConfig({ |
|||
plugins: [vue()], |
|||
server: { |
|||
open: true, |
|||
proxy: { |
|||
// '^/getaway': {
|
|||
// target: loadEnv(mode, process.cwd()).VITE_API_URL,
|
|||
// changeOrigin: true,
|
|||
// rewrite: (path) => path.replace(/^\/getaway/, ''),
|
|||
// },
|
|||
'/api': { |
|||
target: 'http://jsonplaceholder.typicode.com', |
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/api/, ''), |
|||
}, |
|||
'/getaway': { |
|||
target: 'https://test.tall.wiki/gateway', |
|||
changeOrigin: true, |
|||
rewrite: (path) => { |
|||
console.log('path: ', path) |
|||
path.replace(/^\/getaway/, '') |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
'@': path.resolve(__dirname, './src'), |
|||
comp: path.resolve(__dirname, './src/components'), |
|||
views: path.resolve(__dirname, './src/views'), |
|||
api: path.resolve(__dirname, './src/api'), |
|||
utils: path.resolve(__dirname, './src/utils'), |
|||
assets: path.resolve(__dirname, './src/assets'), |
|||
store: path.resolve(__dirname, './src/store'), |
|||
}, |
|||
}, |
|||
define: { |
|||
_APP_VERSION: JSON.stringify(pkg.version), |
|||
}, |
|||
}) |
|||
} |
|||
|
|||
File diff suppressed because it is too large
Loading…
Reference in new issue