Commit 1d34a98e authored by wangjiankun's avatar wangjiankun

init: 项目

parent 5b7b2ef5
# Editor configuration, see http://editorconfig.org
# 表示是最顶层的 EditorConfig 配置文件
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
# 以下变量在`development`被载入
VITE_APP_BASE_API = '/qy-api'
VITE_APP_WX_URL = '/wx'
# 以下变量在`production`被载入
VITE_APP_BASE_API = 'http://117.122.212.102:8092/qy-api/'
VITE_APP_WX_URL = 'https://open.weixin.qq.com'
\ No newline at end of file
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'plugin:vue/essential',
'airbnb-base',
],
globals: {
youngDanStorage: 'writable',
},
parserOptions: {
ecmaVersion: 13,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'vue',
'@typescript-eslint',
],
rules: {
// 下面这四条配置避免eslint报@别名的错误
'import/no-unresolved': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
'import/no-extraneous-dependencies': 'off',
'no-param-reassign': [
'error',
{
props: true,
ignorePropertyModificationsFor: [
'e', // for e.returnvalue
'ctx', // for Koa routing
'req', // for Express requests
'request', // for Express requests
'res', // for Express responses
'response', // for Express responses
'state', // for vuex state
'config',
],
},
],
},
};
node_modules
.DS_Store
dist
dist-ssr
*.local
.idea
.vscode
dist.zip
\ No newline at end of file
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# vite+ts+vue3.0+vant搭建移动端项目
### 介绍
vite+ts+vue3.0+vant搭建移动端项目架构
### 软件架构
### 技术选型
- vue v3.2.25
- typescript v4.4.4
- vite v2.7.2
- vant v3.3.7
- npm v6.14.9
- node v14.13.1
### Git 贡献提交规范
- feat 增加新功能
- fix 修复问题/BUG
- style 代码风格相关无影响运行结果的
- perf 优化/性能提升
- refactor 重构
- revert 撤销修改
- test 测试相关
- docs 文档/注释
- chore 依赖更新/脚手架配置修改等
- workflow 工作流改进
- ci 持续集成
- types 类型定义文件更改
- wip 开发中
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "vite-test-project",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@types/node": "^17.0.2",
"axios": "^0.24.0",
"lib-flexible": "^0.3.2",
"postcss-pxtorem": "^6.0.0",
"qs": "^6.10.3",
"sass": "^1.45.1",
"vant": "^3.3.7",
"vconsole": "^3.14.5",
"vite-plugin-compression": "^0.3.6",
"vite-plugin-style-import": "^1.4.0",
"vue": "^3.2.25",
"vue-router": "^4.0.12",
"vuex": "^4.0.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"@vitejs/plugin-vue": "^2.0.0",
"eslint": "^8.5.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-vue": "^8.2.0",
"typescript": "^4.4.4",
"vite": "^2.7.2",
"vue-tsc": "^0.29.8"
},
"description": "This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.",
"main": "index.js",
"author": "",
"license": "ISC"
}
// 用 vite 创建项目,配置 postcss 需要使用 post.config.js,之前使用的 .postcssrc.js 已经被抛弃
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue({ file }) {
return file.indexOf('vant') !== -1 ? 37.5 : 75;
},
propList: ['*'],
selectorBlackList: ['.norem'], // 过滤掉.norem-开头的class,不进行rem转换
},
},
};
module.exports = {
printWidth: 80, // 单行输出(不折行)的(最大)长度
useTabs: false, // 不使用缩进符,而使用空格
tabWidth: 2, // 每个缩进的空格数
tabs: false, // 使用制表符 (tab) 缩进行而不是空格 (space)
semi: false, // 是否在语句末尾打印分号
singleQuote: true, // 是否使用单引号
// "quoteProps": "as-needed", // 仅在需要时在对象属性周围添加引号
quoteProps: 'consistent',
trailingComma: 'all', // 去除对象最末尾元素跟随的逗号
arrowParens: 'always', // 箭头函数,只有一个参数的时候,也需要括号
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
proseWrap: 'always', // 当超出print width(上面有这个参数)时就折行
endOfLine: 'lf', // 换行符使用 lf
bracketSpacing: true, // 是否在对象属性添加空格
jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
htmlWhitespaceSensitivity: 'ignore', // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
// eslint-disable-next-line no-dupe-keys
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
};
<template>
<!-- ontouchstart 解决ios上点击按钮没有阴影反馈的问题 -->
<div class="main" ontouchstart>
<router-view v-slot="{ Component }">
<keep-alive>
<component
:is="Component"
v-if="$route.meta.keepAlive"
:key="$route.name"
/>
</keep-alive>
<component
:is="Component"
v-if="!$route.meta.keepAlive"
:key="$route.name"
/>
</router-view>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
components: {},
setup() {
return {};
},
});
</script>
<style lang="scss" scoped>
.main {
width: 100%;
height: 100%;
background-color: #f2f2f2;
}
</style>
import exp from 'constants';
import request from './request';
// 获取用户详情
// eslint-disable-next-line import/prefer-default-export
export const GetUserDetail = (params: object) => request({
url: '/getUserDetail',
params,
});
// 获取部门
export const GetDepts = () => request({
method: 'get',
url: '/getDepartment',
});
export const GetDeptUser = (params) => request({
method: 'get',
url: '/getDepartmentUser',
params,
});
export const SendMessage = () => request({
url: '/sendMsg',
method: 'post',
});
export const GetUserInfo = (data) => request({
url: '/userInfo',
method: 'post',
data
})
export const GetWxCode = (path) => {
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww464eb5dddaf95145&redirect_uri=http://wx.91isoft.com/dist/#${path}&response_type=code&scope=snsapi_base#wechat_redirect`
}
import request from './request';
export interface HttpResponse {
status: number
success?: boolean
traceId?: string
data: any
}
export const login = async (data = {}): Promise<HttpResponse> => request('/接口', {
method: 'post',
data,
});
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { Toast } from 'vant';
import Qs from 'qs'
// 错误码
const codeMessage: Record<number, string> = {
400: '请求错误',
401: '登录已过期请重新登录',
403: '访问被禁止。',
404: '发出的请求是不存在的记录',
406: '请求的格式不可得。',
410: '请求的资源被永久删除',
422: '验证错误',
500: '服务器发生错误',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
20032: '该功能暂未对您开放',
10025: '用户不存在',
};
// 创建axios实例
const instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API as any,
timeout: 600000,
});
const { CancelToken } = axios;
const source = CancelToken.source();
// 添加请求拦截器
instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
debugger
if(config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=utf-8') {
config.data = Qs.stringify(config.data, { arrayFormat: 'indices', allowDots: true })
return config
}
// config
// 在发送请求之前做些什么
const headers = {
// 用crm的token发起请求
Authorization: youngDanStorage.get('token') ? youngDanStorage.get('token') : '',
'Content-Type': 'application/json',
};
if (config.headers) {
config.headers = { ...headers, ...config.headers } as any;
} else {
config.headers = headers as any;
}
config.cancelToken = source.token;
return config;
},
(error: AxiosError) => Promise.resolve(error || '服务器异常'),
);
// 添加响应拦截器
instance.interceptors.response.use(
async (response: AxiosResponse) => {
const { code, msg, message } = response.data;
if (code) {
if(code === 200) {
return response.data;
} else {
Toast(msg || message || '网络异常')
}
} else {
return response.data;
}
return response.data;
},
(error: AxiosError) => {
const { response } = error;
if (response && response.status) {
const { status, statusText } = response;
const errorText = codeMessage[status] || statusText;
Toast.fail(errorText);
} else if (!response) {
Toast.fail('您的网络发生异常,无法连接服务器');
}
return Promise.reject(error);
},
);
export default instance;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
position: relative;
width: 100%;
height: 100%;
line-height: 1.5;
font-family: PingFang SC, Source Sans Pro, Hiragino Sans GB, Helvetica Neue, Helvetica, Microsoft Yahei, arial,
sans-serif;
-webkit-tap-highlight-color: transparent;
box-sizing: border-box;
touch-action: manipulation; //禁止ios上面双击缩放页面
}
#app {
height: 100%;
}
@import "./_base.scss";
@import "./_var.scss";
\ No newline at end of file
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
import { createApp } from 'vue';
import 'lib-flexible/flexible';// 解决自动转rem后vant组件变小的问题
import { router, setupRouter } from './router';
import setupRouterGuard from './router/guard';
import setupYoungDanStorage from '@/utils/cache';
import { setupStore } from '@/store';
import App from './App.vue';
import './assets/scss/_index.scss';
import 'vant/lib/index.css';
async function immediately(): Promise<void> {
const app = createApp(App);
setupYoungDanStorage();
// Configure store
setupStore(app);
// Configure routing
setupRouter(app);
// router-guard
setupRouterGuard(router);
// wait router ready
await router.isReady();
app.mount('#app');
}
immediately();
import type { Router } from 'vue-router';
import { Dialog } from 'vant';
import { GetWxCode } from '@/api/auth';
/*
Permission
*/
const whiteRoutes = ['/needAuth'];
function createPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
if (whiteRoutes.includes(to.path)) {
return next();
}
const reg = /(^|&)code=([^&]*)(&|$)/i;
const result = window.location.search.substr(1).match(reg);
if (result) {
const code = decodeURI(result[2]);
window.youngDanStorage.set('wxCOde', code);
next();
} else {
next(false);
Dialog.confirm({
message: '是否确定授权?',
title: '提示',
}).then(async () => {
await GetWxCode(to.path);
// window.location.href = 'http://localhost:8888/?code=t_b_eDAzavIoX8c7uBwWD8Eb06um9xONMsG7nnkQ5Lg&state=#/';
});
}
});
}
export default function setupRouterGuard(router: Router):void {
createPermissionGuard(router);
}
import type { App } from 'vue';
import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import routes from './routes';
// app router
export const router = createRouter({
history: createWebHashHistory(),
routes: routes as unknown as RouteRecordRaw[],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
}
return { top: 0 };
},
});
// config router
export function setupRouter(app: App<Element>) {
app.use(router);
}
import { RouteRecordRaw } from 'vue-router';
import Home from '@/views/home/index.vue';
const HomeRoute: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {
title: '首页',
},
},
];
export default HomeRoute;
import HomeRoute from './homeRoutes';
import LoginRoute from './loginRoutes';
const routes = [
...HomeRoute,
...LoginRoute,
];
export default routes;
import { RouteRecordRaw } from 'vue-router';
import Login from '@/views/login/index.vue';
const LoginRoute: Array<RouteRecordRaw> = [
{
path: '/login',
name: 'Login',
component: Login,
meta: {
title: '登录',
},
},
];
export default LoginRoute;
import type { App } from 'vue';
import { createStore, createLogger } from 'vuex';
const debug = process.env.NODE_ENV !== 'production';
// 自动注入所有 ./modules 下的 vueX 子模块 vite的方法
const modulesFile = import.meta.globEager('./modules/*.ts') as any;
const modules = {} as any;
Object.keys(modulesFile).forEach(async (key: any) => {
const moduleName = key.replace(/^\.\/.*\/(.*)\.\w+$/, '$1');
modules[moduleName] = modulesFile[key].default;
});
export const store = createStore({
modules,
strict: debug,
plugins: debug ? [createLogger()] : [], // 操作vueX => 控制台打印
});
export function setupStore(app: App<Element>) {
app.use(store);
}
import type {
GetterTree, MutationTree, ActionTree, Module,
} from 'vuex';
export interface UserState { }
const state = (): UserState => ({});
export const getters: GetterTree<UserState, any> = {};
export const mutations: MutationTree<UserState> = {};
export const actions: ActionTree<UserState, any> = {};
export default {
namespaced: true,
getters,
mutations,
actions,
state,
} as Module<UserState, any>;
import type {
GetterTree, MutationTree, ActionTree, Module,
} from 'vuex';
export interface UserState { }
const state = (): UserState => ({});
export const getters: GetterTree<UserState, any> = {};
export const mutations: MutationTree<UserState> = {};
export const actions: ActionTree<UserState, any> = {};
export default {
namespaced: true,
getters,
mutations,
actions,
state,
} as Module<UserState, any>;
type StorageType = Storage
interface CreateStorageOptions {
prefix?: string;
safeTime?: number;
once?: boolean
}
interface SetOptions {
safeTime?: number;
once?: boolean;
}
class CustomStorage {
private prefix: string = '';
private safeTime: number = 0;
private once: boolean = false;
private store: StorageType;
constructor(storage: StorageType, config?: CreateStorageOptions) {
this.store = storage;
const { prefix, safeTime, once } = config || {};
this.prefix = prefix || '';
this.safeTime = safeTime || 0;
this.once = once || false;
}
// setItem, 配置保质期和一次性存值 合并全局
set(key: string, value: any, option?: SetOptions) {
const {
prefix, safeTime, once, store,
} = this;
const storeKey:string = `${prefix ? `${prefix}_` : ''}${key}`;
store.set(storeKey, {
time: new Date().getTime(),
once: option?.once ? option.once : once || false,
value: typeof value === 'object' ? JSON.stringify(value) : value,
});
};
get(key) {
const {
prefix, safeTime, once, store,
} = this;
const storeKey:string = `${prefix ? `${prefix}_` : ''}${key}`;
const
}
}
export const SessionStorage = new CustomStorage(sessionStorage, {
prefix: '',
safeTime: 1000 * 3600 * 72,
once: false,
});
import { createStorage as create } from './storage';
export default function setupYoungDanStorage() {
(window as any).youngDanStorage = create({
prefixKey: 'YOUNGDAN__',
});
}
import { isNullOrUnDef, isSymbol } from '@/utils/ts/is';
export interface CreateStorageParams {
prefixKey: string;
mode: 'session' | 'local';
}
/**
* 判断当前值是否能够被JSON.stringify识别
* @param data 需要判断的值
* @returns 前参数是否可以string化
*/
export function hasStringify(data: any): boolean {
if (data === undefined) {
return false;
}
if (data instanceof Function) {
return false;
}
return !isSymbol(data);
}
export const createStorage = ({
prefixKey = '',
mode = 'local',
}: Partial<CreateStorageParams> = {}) => {
if (!window.localStorage) {
throw new Error('当前环境非无法使用localStorage');
}
if (!window.sessionStorage) {
throw new Error('当前环境非无法使用sessionStorage');
}
/**
* Cache class
* 通过mode 设置 sessionStorage, localStorage,
* @class Cache
* @example
*/
const YoungDanStorage = class YoungDanStorage {
private storage: Storage;
private prefixKey?: string;
private getKey(storageKey: string) {
return `${this.prefixKey}${storageKey}`.toUpperCase();
}
/**
*
* @param {*} storage
*/
constructor() {
this.storage = mode === 'local' ? localStorage : sessionStorage;
this.prefixKey = prefixKey;
}
/**
* 设置当前
* @param key 设置当前存储key
* @param value 设置当前存储value
* @expire 过期时间(秒)
*/
set(key: string, value: any) {
if (hasStringify(value)) {
const stringData = {
timestamp: Date.now(),
value,
};
this.storage.setItem(this.getKey(key), JSON.stringify(stringData));
} else {
throw new Error('需要存储的data不支持JSON.stringify方法,请检查当前数据');
}
}
/**
* 获取数据
* @param {string} key 获取当前数据key
* @returns 存储数据
*/
get(key: string, def: any = null): any {
const val = this.storage.getItem(this.getKey(key));
if (!val) return def;
try {
const data = JSON.parse(val);
const { value, expire } = data;
if (isNullOrUnDef(expire) || expire >= new Date().getTime()) {
return value;
}
this.remove(key);
return null;
} catch (err) {
return def;
}
}
/**
* 修改存储数据的内容
* @param key 当前存储key
* @param onChange 修改函数
* @param baseValue 基础数据
*/
// eslint-disable-next-line no-unused-vars
change<T = any>(key: string, onChange: (oldValue: T) => T | null, baseValue?: any): void {
const data = this.get(key);
const newTarget = onChange(data || baseValue);
this.set(key, newTarget);
}
/**
* 移除一条数据
* @param key 移除key
*/
remove(key: string): void {
if (this.has(key)) {
this.storage.removeItem(this.getKey(key));
}
}
/**
* 清除存储中所有数据
*/
clear(): void {
this.storage.clear();
}
/**
* 判断是否存在该属性
* @param key 需要判断的key
*/
has(key: string): boolean {
return Object.prototype.hasOwnProperty.call(this.storage, this.getKey(key));
}
/**
* 返回当前存储库大小
* @returns number
*/
size(): number {
return this.storage.length;
}
/**
* 获取所有key
* @returns 回storage当中所有key集合
*/
getKeys(): Array<string> {
return Object.keys(this.storage);
}
/**
* 获取所有value
* @returns 所有数据集合
*/
getValues(): Array<string> {
return Object.values(this.storage);
}
};
return new YoungDanStorage();
};
import { Toast } from 'vant';
// 快速复制
const fastCopy = (text: any) => {
const input = document.createElement('input');
document.body.appendChild(input);
input.value = text;
input.select(); // 选中文本
document.execCommand('Copy');
Toast('复制成功');
input.remove();
};
// 点击电话调起手机打电话功能 传递进来的参数是电话号码
const makePhoneCall = (tel: number | string) => {
const AElement = document.createElement('a');
AElement.href = `tel:${tel}`;
AElement.click();
AElement.remove();
};
// 格式化数字单位
const formatNumUnit = (num: number | string) => {
const numS = Number(num);
if (numS) {
if (numS <= 10000) {
return numS;
} if (numS < 100000000) {
return `${Number((numS / 10000).toFixed(2))}w`;
}
return `${Number((numS / 100000000).toFixed(2))}亿`;
}
return 0;
};
export {
fastCopy, makePhoneCall, formatNumUnit,
};
const { toString } = Object.prototype;
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 isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
export function isString(val: unknown): val is string {
return is(val, 'String');
}
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 isFunction(val: unknown): val is Function {
return typeof val === 'function';
}
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 isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
}
export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp');
}
export function isSymbol(val: any): boolean {
return typeof val === 'symbol';
}
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);
}
<template>
<div class="public-home">
<div v-if="userJson">
{{ userJson }}
</div>
<Button type="success" size="small" round @click="getUserDetail">
获取用户详情
</Button>
<div v-if="userDetailJson">
{{ userDetailJson }}
</div>
<Button type="primary" size="small" round @click="getDepts">
获取部门列表
</Button>
<div v-if="deptJson">
{{ deptJson }}
</div>
<Button type="danger" size="small" round @click="getDeptUsers">
获取部门下用户信息
</Button>
<div v-if="deptUserJson">
{{ deptUserJson }}
</div>
<Button type="warning" size="small" round @click="sendMessage">
发送消息
</Button>
</div>
</template>
<script lang="ts" setup>
import { Button, Dialog } from 'vant';
import { ref, onMounted } from 'vue';
import {
GetUserDetail, GetDepts, GetDeptUser, SendMessage, GetUserInfo,
} from '@/api/auth';
const userDetailJson = ref('');
const deptJson = ref('');
const deptUserJson = ref('');
const userJson = ref('');
const getUserDetail = () => {
userDetailJson.value = '';
GetUserDetail({
userId: 'LuZhuangZhuang',
}).then((res) => {
const { data } = res;
userDetailJson.value = data && typeof data === 'object' ? JSON.stringify(data) : data;
});
};
const getDepts = () => {
deptJson.value = '';
GetDepts().then((res) => {
const { data } = res;
deptJson.value = data && typeof data === 'object' ? JSON.stringify(data) : data;
});
};
const getDeptUsers = () => {
deptUserJson.value = '';
GetDeptUser({
departmentId: '1',
}).then((res) => {
const { data } = res;
deptJson.value = data && typeof data === 'object' ? JSON.stringify(data) : data;
});
};
const sendMessage = () => {
SendMessage();
};
onMounted(() => {
if (window.youngDanStorage.get('wxCode')) {
Dialog.alert({
message: `${window.youngDanStorage.get('wxCode')}`,
title: 'code',
}).then(() => {
GetUserInfo({
code: `${window.youngDanStorage.get('wxCode')}`
}).then(res => {
userJson.value = res && typeof res === 'object' ? JSON.stringify(res) : res;
})
})
}
});
</script>
<style lang="scss" scoped>
.public-home {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
padding: 0 28px;
& > div {
width: 100%;
word-break: break-all;
font-weight: 500;
font-size: 32px;
}
}
</style>
<template>
<div class="LoginWrapper">
<Button type="success" round block @click="BackHome">
点我返回
</Button>
</div>
</template>
<script lang="ts" setup>
import { Button } from 'vant';
import { useRouter } from 'vue-router';
const router = useRouter();
const BackHome = () => {
router.back();
};
</script>
<style lang="scss" scoped>
.LoginWrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0 30px;
}
</style>
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
// 解析非相对模块名的基准目录
"baseUrl": ".",
// 模块名到基于 baseUrl的路径映射的列表。
"paths": {
"@": ["src"],
"@/*": ["src/*"],
},
// 忽略所有的声明文件( *.d.ts)的类型检查。
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","types/**/*.d.ts"]
}
/* eslint-disable no-shadow */
export {};
/* eslint-disable no-unused-vars */
interface YoungDanStorage {
set(key: string, value: any): void;// 设置缓存
get(key: string, def?: any): any;// 获取缓存
change<T = any>(key: string, onChange: (oldValue: T) => T | null, baseValue?: any): void;// 修改缓存
remove(key: string): void;// 移除缓存
clear(): void;// 清空缓存(所有)
has(key: string): boolean;// 判断是否存在某个缓存
size(): number;// 读取某个缓存的长度
getKeys(): Array<string>;// storage当中所有key集合
getValues(): Array<any>;// 所有数据集合
}
declare global {
declare interface Window {
youngDanStorage: YoungDanStorage;
}
declare const youngDanStorage: YoungDanStorage;// 全局定义youngDanStorage 可直接读取
}
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
import viteCompression from 'vite-plugin-compression';
import styleImport from 'vite-plugin-style-import';
// https://vitejs.dev/config/
const buildTimeStep = new Date().getTime()
export default defineConfig({
plugins: [
vue(),
viteCompression({
ext: '.gz', // gz br
algorithm: 'gzip', // brotliCompress gzip
deleteOriginFile: false, // 打包完成后删除源文件
}),
styleImport({}),
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
base: './', // 打包基础路径 不配置打包后可能会找不到资源
server: {
host: '0.0.0.0',
open: true,
proxy: {
'/qy-api': {
target: 'http://117.122.212.102:8092/qy-api', // 测试服务
changeOrigin: true,
rewrite: (path) => path.replace(/^\/qy-api/, ''),
},
'/wx': {
target: 'https://open.weixin.qq.com', // 测试服务
changeOrigin: true,
rewrite: (path) => path.replace(/^\/wx/, ''),
},
},
port: 8888, // 启动时的默认占用端口
},
build: {
outDir: 'dist',
rollupOptions: {
output: {
entryFileNames: `assets/[name].${buildTimeStep}.js`,
chunkFileNames: `assets/[name].${buildTimeStep}.js`,
assetFileNames: `assets/[name].${buildTimeStep}.[ext]`
}
}
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment