whj há 1 semana atrás
pai
commit
03a34ec3e0

+ 29 - 12
.cool/service/index.ts

@@ -2,7 +2,7 @@ import { isDev, ignoreTokens, config } from "@/config";
 import { locale, t } from "../locale";
 import { isNull, isObject, parse, storage } from "../utils";
 import { useStore } from "../store";
-
+import { useUi } from "@/uni_modules/cool-ui";
 // 请求参数类型定义
 export type RequestOptions = {
 	url: string; // 请求地址
@@ -22,7 +22,7 @@ export type Response = {
 	message?: string;
 	data?: any;
 };
-
+const ui = useUi();
 // 请求队列(用于等待token刷新后继续请求)
 let requests: ((token: string) => void)[] = [];
 
@@ -47,13 +47,13 @@ export function request(options: RequestOptions): Promise<any | null> {
 
 	const { user } = useStore();
 
-	
+
 
 	// 拼接基础url
 	if (!url.startsWith("http")) {
 		url = config.baseUrl + url;
 	}
-// 开发环境下打印请求信息
+	// 开发环境下打印请求信息
 	if (isDev) {
 		console.log(`[${method}] ${url}`);
 	}
@@ -80,14 +80,23 @@ export function request(options: RequestOptions): Promise<any | null> {
 				timeout,
 
 				success(res) {
+
 					// 401 无权限
 					if (res.statusCode == 401) {
 						user.logout();
+						ui.showToast({
+							message: "无权限",
+							type: "warn",
+						})
 						reject({ message: t("无权限") } as Response);
 					}
 
 					// 502 服务异常
 					else if (res.statusCode == 502) {
+						ui.showToast({
+							message: "服务异常",
+							type: "warn",
+						})
 						reject({
 							message: t("服务异常")
 						} as Response);
@@ -95,6 +104,10 @@ export function request(options: RequestOptions): Promise<any | null> {
 
 					// 404 未找到
 					else if (res.statusCode == 404) {
+						ui.showToast({
+							message: `[404] ${url}`,
+							type: "warn",
+						})
 						return reject({
 							message: `[404] ${url}`
 						} as Response);
@@ -111,17 +124,21 @@ export function request(options: RequestOptions): Promise<any | null> {
 							const { code, message, data } = parse<Response>(
 								res.data ?? { code: 0 }
 							)!;
-
-							switch (code) {
-								case 1000:
-									resolve(data);
-									break;
-								default:
-									reject({ message, code } as Response);
-									break;
+							if (code == 200 || code == 0) {
+								resolve(data);
+							} else {
+								ui.showToast({
+									message: message ?? t("服务异常"),
+									type: "warn",
+								})
+								reject({ message: message } as Response);
 							}
 						}
 					} else {
+						ui.showToast({
+							message: t("服务异常"),
+							type: "warn",
+						})
 						reject({ message: t("服务异常") } as Response);
 					}
 				},

+ 63 - 111
.cool/store/dict.ts

@@ -1,134 +1,86 @@
 import { reactive } from "vue";
-import { request } from "../service";
-import { forInObject, isNull, parse } from "../utils";
+import { storage, isNull, parse } from "../utils";
+import { type DICT_DATA, dictData } from "@/api/user";
+import { user } from "./user";
 
-// 字典项类型定义
-export type DictItem = {
-	id: number; // 字典项ID
-	typeId: number; // 字典类型ID
-	label: string; // 显示标签
-	name: string; // 可选名称
-	value: any; // 字典项值
-	orderNum: number; // 排序号
-	parentId?: number | null; // 父级ID,可选
-};
-
-// 字典数据类型定义
-export type DictData = {
-	key: string; // 字典key
-	list: DictItem[]; // 字典项列表
-};
 
 // 字典管理类
 export class Dict {
-	private data: DictData[] = reactive([]); // 存储所有字典数据
-
-	constructor() {}
-
-	/**
-	 * 获取指定key的字典数据
-	 * @param key 字典key
-	 * @returns 字典数据
-	 */
-	find(key: string) {
-		return this.data.find((e) => e.key == key);
+	private data: DICT_DATA = reactive({
+		'config:open:children-all': [],
+		'config:open:value': {},
+		'dict:value': {},
+		'dict:code': {},
+		'dict:label': {},
+		'dict:children-all': {},
+		'dict:children': {},
+		'dict:value:obj': {},
+	}); // 存储所有字典数据
+
+	constructor() {
+		const dictData = storage.get("dict");
+		if (isNull(dictData)) {
+			return;
+		}
+		this.data = dictData;
 	}
 
-	/**
-	 * 获取指定key的字典项列表
-	 * @param key 字典key
-	 * @returns 字典项数组
-	 */
-	get(key: string): DictItem[] {
-		return this.find(key)?.list ?? new Array<DictItem>();
+	getConfigValueByType(type: string) {
+		return this.data['config:open:value'][type]
 	}
-
-	/**
-	 * 获取指定key和value的字典项
-	 * @param key 字典key
-	 * @param value 字典项值
-	 * @returns 字典项或null
-	 */
-	getItem(key: string, value: any): DictItem | null {
-		const item = this.get(key).find((e) => e.value == value);
-
-		if (isNull(item)) {
-			return null;
-		}
-
-		return item!;
+	getConfigAll() {
+		return this.data['config:open:children-all']
 	}
-
-	/**
-	 * 获取指定key和多个value的字典项数组
-	 * @param key 字典key
-	 * @param values 字典项值数组
-	 * @returns 字典项数组
-	 */
-	getItems(key: string, values: any[]): DictItem[] {
-		return values.map((e) => this.getItem(key, e)).filter((e) => !isNull(e)) as DictItem[];
+	getChildren(type: string, value: string, childValue: string) {
+		const children = this.data['dict:children'][type]
+		const child = children[value]
+		return child[childValue]
 	}
-
-	/**
-	 * 获取指定key和value的字典项的label
-	 * @param key 字典key
-	 * @param value 字典项值
-	 * @returns 字典项label字符串
-	 */
-	getItemLabel(key: string, value: any): string {
-		const item = this.getItem(key, value);
-
-		if (isNull(item) || isNull(item?.label)) {
-			return "";
-		}
-
-		return item!.label;
+	getChildrenMapByType(type: string, value: string) {
+		const children = this.data['dict:children'][type]
+		return children[value]
+	}
+	getLabelByValue(type: string, value: string) {
+		const child = this.data['dict:value'][type]
+		return child[value]
+	}
+	getObjByValue(type: string, value: string) {
+		const child = this.data['dict:value:obj'][type]
+		return child[value]
+	}
+	getLabelByValueMapByType(type: string) {
+		return this.data['dict:value'][type]
+	}
+	getValueByLabel(type: string, label: string) {
+		const child = this.data['dict:label'][type]
+		return child[label]
+	}
+	getValueByLabelMapByType(type: string) {
+		return this.data['dict:label'][type]
+	}
+	getValueByCode(type: string, code: string) {
+		const child = this.data['dict:code'][type]
+		return child[code]
+	}
+	getValueByCodeMapByType(type: string) {
+		return this.data['dict:code'][type]
+	}
+	getChildrenList(type: string) {
+		return this.data['dict:children-all'][type]
 	}
 
 	/**
 	 * 刷新字典数据
 	 * @param types 可选,指定需要刷新的字典key数组
 	 */
-	async refresh(types?: string[] | null): Promise<void> {
-		const res = await request({
-			url: "/app/dict/info/data",
-			method: "POST",
-			data: { types }
-		});
+	async refresh(): Promise<void> {
+		const res = await dictData();
 
 		if (res == null) {
 			return;
 		}
-
-		// 遍历返回的字典数据
-		forInObject(res, (arr, key) => {
-			let list: DictItem[] = [];
-
-			(arr as UTSJSONObject[]).forEach((e) => {
-				e["label"] = e["name"];
-				const d = parse<DictItem>(e);
-
-				if (d != null) {
-					list.push(d);
-				}
-			});
-
-			const item = this.find(key);
-
-			// 如果不存在则新增,否则更新
-			if (isNull(item)) {
-				this.data.push({
-					key,
-					list
-				});
-			} else {
-				item!.list = list;
-			}
-		});
-
-		// #ifdef H5
-		console.log("[DICT]", this.data);
-		// #endif
+		this.data = res;
+		storage.set("dict", res, 60 * 60 * 24 * 1000);
 	}
 }
 

+ 49 - 48
.cool/store/user.ts

@@ -3,7 +3,8 @@ import { forInObject, isNull, isObject, parse, storage } from "../utils";
 import { router } from "../router";
 import { request } from "../service";
 import type { UserInfo } from "../types";
-
+import { type LoginData, getUsersInfo } from "@/api/user";
+import { dict } from "./dict";
 export type Token = {
 	token: string; // 访问token
 	expire: number; // token过期时间(秒)
@@ -20,7 +21,7 @@ export class User {
 	/**
 	 * 当前token,字符串或null
 	 */
-	token: string | null = 'Basic ZW5kOmVuZA==';
+	token: string | 'Basic ZW5kOmVuZA==';
 
 	constructor() {
 		// 获取本地用户信息
@@ -43,19 +44,16 @@ export class User {
 	 * @returns Promise<void>
 	 */
 	async get() {
-		// if (this.token != null) {
-		// 	await request({
-		// 		url: "/app/user/info/person"
-		// 	})
-		// 		.then((res) => {
-		// 			if (res != null) {
-		// 				this.set(res);
-		// 			}
-		// 		})
-		// 		.catch(() => {
-		// 			// this.logout();
-		// 		});
-		// }
+		if (this.token != 'Basic ZW5kOmVuZA==') {
+			try {
+				const res = await getUsersInfo()
+				if (res != null) {
+					this.set(res);
+				}
+			} catch (err) {
+				this.logout();
+			}
+		}
 	}
 
 	/**
@@ -118,7 +116,7 @@ export class User {
 	clear() {
 		storage.remove("userInfo");
 		storage.remove("token");
-		storage.remove("refreshToken");
+		// storage.remove("refreshToken");
 		this.token = 'Basic ZW5kOmVuZA==';
 		this.remove();
 	}
@@ -135,43 +133,46 @@ export class User {
 	 * 设置token并存储到本地
 	 * @param data Token对象
 	 */
-	setToken(data: Token) {
-		this.token = data.token;
+	setToken(data: LoginData) {
+		this.token = `Bearer ${data?.access_token}`;
 
 		// 访问token,提前5秒过期,防止边界问题
-		storage.set("token", data.token, data.expire - 5);
+		storage.set("token", this.token, 7 * 24 * 60 * 60 - 5);
+		this.get();
+		dict.refresh();
+
 		// 刷新token,提前5秒过期
-		storage.set("refreshToken", data.refreshToken, data.refreshExpire - 5);
+		// storage.set("refreshToken", data.refresh_token, data.refreshExpire - 5);
 	}
 
-	/**
-	 * 刷新token(调用服务端接口,自动更新本地token)
-	 * @returns Promise<string> 新的token
-	 */
-	refreshToken(): Promise<string> {
-		return new Promise((resolve, reject) => {
-			request({
-				url: "/app/user/login/refreshToken",
-				method: "POST",
-				data: {
-					refreshToken: storage.get("refreshToken")
-				}
-			})
-				.then((res) => {
-					if (res != null) {
-						const token = parse<Token>(res);
-
-						if (token != null) {
-							this.setToken(token);
-							resolve(token.token);
-						}
-					}
-				})
-				.catch((err) => {
-					reject(err);
-				});
-		});
-	}
+	// /**
+	//  * 刷新token(调用服务端接口,自动更新本地token)
+	//  * @returns Promise<string> 新的token
+	//  */
+	// refreshToken(): Promise<string> {
+	// 	return new Promise((resolve, reject) => {
+	// 		request({
+	// 			url: "/app/user/login/refreshToken",
+	// 			method: "POST",
+	// 			data: {
+	// 				refreshToken: storage.get("refreshToken")
+	// 			}
+	// 		})
+	// 			.then((res) => {
+	// 				if (res != null) {
+	// 					const token = parse<Token>(res);
+
+	// 					if (token != null) {
+	// 						this.setToken(token);
+	// 						resolve(token.token);
+	// 					}
+	// 				}
+	// 			})
+	// 			.catch((err) => {
+	// 				reject(err);
+	// 			});
+	// 	});
+	// }
 }
 
 /**

+ 8 - 1
.cool/utils/stringify.ts

@@ -1,5 +1,12 @@
 import { isArray, isObject, keys, get } from './comm';
-
+import JsSHA from 'jssha'
+// 密码加密
+export function encryptPassword(password: string): string {
+  const shaObj = new JsSHA('SHA-512', 'TEXT')
+  shaObj.update(password)
+  const hash = shaObj.getHash('HEX')
+  return hash
+}
 export function stringify(obj: any) {
   if (isObject(obj)) {
     return keys(obj)

+ 50 - 3
api/user.ts

@@ -1,7 +1,54 @@
-import { usePost, stringify } from "@/.cool";
+import { usePost, stringify, useGet } from "@/.cool";
+export type LoginData = {
+  access_token: string;
+  refresh_token: string;
+}
+export interface GetInfoResult {
+  permissions: any[]
+  roles: any[]
+  sysRoles: any[]
+  userInfo: any
+}
+export interface CONFIG_OPEN_CHILDREN_ALL {
+  code: string
+  name: string
+  id: string
+  content: string
+  description: string
+  type: number
+  openFlag: boolean
+}
+export interface DICT_CHILDREN_ALL {
+  code: string
+  label: string
+  value: string
+}
+export interface CONFIG_OPEN_VALUE {
+  [key: string]: string
+}
+export interface DICT_DATA {
+  'config:open:children-all': CONFIG_OPEN_CHILDREN_ALL[]
+  'config:open:value': CONFIG_OPEN_VALUE
+  'dict:children': {
+    [key: string]: CONFIG_OPEN_VALUE
+  }
+  'dict:value:obj': {
+    [key: string]: CONFIG_OPEN_VALUE
+  }
+  'dict:value': { [key: string]: CONFIG_OPEN_VALUE }
+  'dict:code': { [key: string]: CONFIG_OPEN_VALUE }
+  'dict:label': { [key: string]: CONFIG_OPEN_VALUE }
+  'dict:children-all': { [key: string]: DICT_CHILDREN_ALL[] }
+}
 export function loginApi(params) {
-  return usePost(`/oauth/token?${stringify(params)}`)
+  return usePost(`/oauth/token?${stringify(params)}`) as Promise<LoginData>
 }
 export function sendSmsCode(params) {
-  return usePost(`/verify/sms/code/send`, params)
+  return usePost(`/verify/sms/code/send`, params) as Promise<string>
+}
+export function getUsersInfo() {
+  return useGet(`/upms/users/info`) as Promise<GetInfoResult>
+}
+export function dictData() {
+  return useGet(`/upms/dicts/data`) as Promise<DICT_DATA>
 }

+ 1 - 0
package.json

@@ -5,6 +5,7 @@
 	"dependencies": {
 		"@types/qs": "^6.15.0",
 		"hammer-touchemulator": "^0.0.2",
+		"jssha": "^3.3.1",
 		"qs": "^6.15.0",
 		"vue": "^3.5.13",
 		"weixin-js-sdk": "^1.6.5"

+ 34 - 0
pages/user/components/password.uvue

@@ -0,0 +1,34 @@
+<template>
+	<view class="flex flex-col w-[40vw]">
+		<view class="mb-3 flex flex-row ">
+			<cl-input class="" :pt="{ className: '!h-[40px] !rounded-xxl !px-4 !w-[40vw]' }" v-model="form.username"
+				placeholder="请输入手机号"></cl-input>
+		</view>
+
+		<view class="relative flex flex-row items-center mb-5">
+			<cl-input :pt="{ className: '!h-[40px] !rounded-xxl !px-4 !w-[40vw]' }" clearable v-model="form.password" password
+				placeholder="请输入密码">
+			</cl-input>
+		</view>
+
+
+	</view>
+</template>
+
+<script setup lang="ts">
+import { type PropType } from "vue";
+import type { LoginForm } from "./types";
+import { useRefs, } from "@/.cool";
+
+const props = defineProps({
+	form: {
+		type: Object as PropType<LoginForm>,
+		default: () => ({})
+	}
+});
+
+const emit = defineEmits(["success"]);
+
+const refs = useRefs();
+
+</script>

+ 3 - 48
pages/user/components/phone.uvue

@@ -10,7 +10,7 @@
 				type="number">
 			</cl-input>
 			<view class="absolute right-0">
-				<sms-btn :ref="refs.set('smsBtn')" :phone="form.username" @success="showCode = true"></sms-btn>
+				<sms-btn :ref="refs.set('smsBtn')" :phone="form.username"></sms-btn>
 			</view>
 		</view>
 
@@ -19,11 +19,10 @@
 </template>
 
 <script setup lang="ts">
-import { computed, inject, ref, type PropType } from "vue";
+import { type PropType } from "vue";
 import type { LoginForm } from "./types";
 import SmsBtn from "./sms-btn.uvue";
-import { isDark, parseClass, request, useRefs, type Response, t } from "@/.cool";
-import { useUi } from "@/uni_modules/cool-ui";
+import { useRefs, } from "@/.cool";
 
 const props = defineProps({
 	form: {
@@ -34,50 +33,6 @@ const props = defineProps({
 
 const emit = defineEmits(["success"]);
 
-const ui = useUi();
 const refs = useRefs();
 
-// 是否同意
-const isAgree = inject("isAgree") as () => boolean;
-
-// 是否显示验证码
-const showCode = ref(false);
-
-// 是否加载中
-const loading = ref(false);
-
-// 是否禁用
-const disabled = computed(() => {
-	return props.form.phone == "" || props.form.smsCode == "";
-});
-
-// 登录
-async function toLogin() {
-	if (!isAgree()) {
-		return;
-	}
-
-	const { phone, smsCode } = props.form;
-
-	loading.value = true;
-
-	await request({
-		url: "/app/user/login/phone",
-		method: "POST",
-		data: {
-			phone,
-			smsCode
-		}
-	})
-		.then((res) => {
-			emit("success", res);
-		})
-		.catch((err) => {
-			ui.showToast({
-				message: (err as Response).message!
-			});
-		});
-
-	loading.value = false;
-}
 </script>

+ 1 - 12
pages/user/components/sms-btn.uvue

@@ -16,8 +16,6 @@ const props = defineProps({
 	phone: String
 });
 
-const emit = defineEmits(["success"]);
-
 const ui = useUi();
 
 type Captcha = {
@@ -46,15 +44,6 @@ const btnText = computed(() =>
 	countdown.value > 0 ? $t("{n}s后重新获取", { n: countdown.value }) : t("获取验证码")
 );
 
-const code = ref("");
-const captchaId = ref("");
-
-// 清空
-function clear() {
-	code.value = "";
-	captchaId.value = "";
-}
-
 
 // 开始倒计时
 function startCountdown() {
@@ -81,7 +70,7 @@ function startCountdown() {
 // 发送短信
 async function send() {
 	captcha.sending = true;
-	await sendSmsCode({
+	const res = await sendSmsCode({
 		mobile: props.phone,
 		randomStr: 12,
 		messageType: 1

+ 33 - 7
pages/user/login.uvue

@@ -8,13 +8,13 @@
 		<view class="group">
 			<cl-image src="/static/home/8.png" width="150px" height="150px" mode="heightFix" />
 			<view class="w-[40vw]">
-				<cl-tabs v-model="val" :list="list" :form="formData"></cl-tabs>
+				<cl-tabs v-model="val" :list="list" :form="formData" @change="tabsChange"></cl-tabs>
 				<cl-form v-model="formData" :pt="{ className: 'mt-1' }">
 					<view v-if="val === 'quickly_login'">
 						<phone :form="formData" />
 					</view>
 					<view v-else>
-						密码登录
+						<password :form="formData" />
 					</view>
 					<cl-button :pt="{ className: '!h-[45px] !rounded-full w-[200px] mx-auto' }" :loading="loading" @tap="toLogin">
 						登录
@@ -29,9 +29,11 @@
 import Back from '@/components/back.uvue'
 import type { LoginForm } from "./components/types";
 import phone from './components/phone.uvue';
+import password from './components/password.uvue';
 import { ref } from 'vue'
 import type { ClTabsItem } from "@/uni_modules/cool-ui";
-import type { user } from '@/.cool';
+import { encryptPassword, user, router } from '@/.cool';
+import { loginApi } from "@/api/user";
 const val = ref('quickly_login')
 const list: ClTabsItem[] = [
 	{
@@ -44,18 +46,42 @@ const list: ClTabsItem[] = [
 	},
 ]
 const formData = ref<LoginForm>({
-	username: '17812345678',
-	password: '',
+	username: 'xiongchao',
+	password: '123456',
 	randomStr: 0,
-	grant_type: 'password',
+	grant_type: 'quickly_login',
 	scope: 'server',
-	loginType: 1,
+	loginType: 99,
 	code: ''
 })
 const loading = ref(false)
+function tabsChange() {
+	if (val.value === 'quickly_login') {
+		formData.value.code = ''
+		formData.value.loginType = 99
+		formData.value.grant_type = 'quickly_login'
+		formData.value.password = ''
+	} else {
+		formData.value.grant_type = 'password'
+		formData.value.password = ''
+		formData.value.loginType = 1
+		formData.value.code = ''
+	}
+}
 function toLogin() {
 	// loading.value = true
 	console.log(formData.value);
+	loginApi({
+		...formData.value,
+		password: formData.value.password && encryptPassword(formData.value.password),
+	}).then(res => {
+		user.setToken(res)
+		uni.showToast({
+			title: '登录成功',
+			icon: 'success'
+		})
+		router.nextLogin();
+	})
 
 }
 </script>

+ 8 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       hammer-touchemulator:
         specifier: ^0.0.2
         version: 0.0.2
+      jssha:
+        specifier: ^3.3.1
+        version: 3.3.1
       qs:
         specifier: ^6.15.0
         version: 6.15.0
@@ -708,6 +711,9 @@ packages:
     resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
     hasBin: true
 
+  jssha@3.3.1:
+    resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==}
+
   lilconfig@3.1.3:
     resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
     engines: {node: '>=14'}
@@ -1603,6 +1609,8 @@ snapshots:
 
   jiti@1.21.7: {}
 
+  jssha@3.3.1: {}
+
   lilconfig@3.1.3: {}
 
   lines-and-columns@1.2.4: {}