Commit 24c0bbe72df0d1e9611993ade850bf97b0e1f495

Authored by 刘淇
1 parent 53012a16

登录账号密码 加密存储

package-lock.json
... ... @@ -6,6 +6,7 @@
6 6 "": {
7 7 "dependencies": {
8 8 "clipboard": "^2.0.11",
  9 + "crypto-js": "^4.2.0",
9 10 "dayjs": "^1.11.19",
10 11 "pinia-plugin-persistedstate": "^4.7.1"
11 12 },
... ... @@ -85,6 +86,12 @@
85 86 "dev": true,
86 87 "license": "MIT"
87 88 },
  89 + "node_modules/crypto-js": {
  90 + "version": "4.2.0",
  91 + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
  92 + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
  93 + "license": "MIT"
  94 + },
88 95 "node_modules/dayjs": {
89 96 "version": "1.11.19",
90 97 "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz",
... ...
package.json
1 1 {
2 2 "dependencies": {
3 3 "clipboard": "^2.0.11",
  4 + "crypto-js": "^4.2.0",
4 5 "dayjs": "^1.11.19",
5 6 "pinia-plugin-persistedstate": "^4.7.1"
6 7 },
... ...
pages-sub/daily/maintain-manage/road-detail-list.vue
... ... @@ -54,7 +54,7 @@
54 54 <!-- 自定义标题区域 -->
55 55 <template #head>
56 56 <view class="card-header">
57   - <view class="common-card-title common-name">{{ item.planName || '无计划名称' }}{{ item.planName || '无计划名称' }}</view>
  57 + <view class="common-card-title common-name">{{ item.planName || '无计划名称' }}</view>
58 58 <!-- 已失效标识 -->
59 59 <view v-show="item.finishState == 3 " class="common-invalid-tag">已失效</view>
60 60 <view v-show="item.finishState == 2 " class="common-finish-tag">已完成</view>
... ...
pages-sub/data/tree-archive/addTree.vue
1 1 <template>
2   - <view class="container">
  2 + <view class="page-container">
3 3 <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom>
4 4 <up-form-item label="名称" prop="treetype" required>
5 5 <up-input v-model.trim="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/>
... ... @@ -452,7 +452,7 @@ const submit = async () =&gt; {
452 452 </script>
453 453  
454 454 <style scoped lang="scss">
455   -.container {
  455 +.page-container {
456 456 padding: 15px;
457 457 box-sizing: border-box;
458 458 background: #fff;
... ...
pages-sub/data/tree-archive/logDetail.vue
1 1 <template>
2   - <view class="container">
  2 + <view class="page-container">
3 3 <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom>
4 4 <up-form-item label="名称" prop="treetype" required>
5 5 <up-input v-model="formData.treetype" placeholder="请输入名称" maxlength="30" border="none" readonly/>
... ... @@ -205,7 +205,7 @@ onReady(() =&gt; {});
205 205 </script>
206 206  
207 207 <style scoped lang="scss">
208   -.container {
  208 +.page-container {
209 209 padding: 25rpx;
210 210 box-sizing: border-box;
211 211 background: #fff;
... ...
pages-sub/data/tree-archive/treeRecord.vue
1 1 <template>
2 2 <view class="container">
  3 +
  4 + <!-- 内容区域 - 集成z-paging分页 -->
  5 + <z-paging
  6 + ref="pagingRef"
  7 + v-model="rows"
  8 + @query="fetchData"
  9 + :auto-show-system-loading="true"
  10 + >
  11 + <!-- 空数据提示 -->
  12 + <template #empty>
  13 + <empty-view/>
  14 + </template>
3 15 <!-- 空数据组件 -->
4   - <up-empty v-if="rows.length === 0" text="暂无数据" marginTop="100"></up-empty>
  16 +<!-- <up-empty v-if="rows.length === 0" text="暂无数据" marginTop="100"></up-empty>-->
5 17  
6 18 <!-- 树木列表:up-card重构 + 保留原始背景图写法 核心满足你的要求 -->
7   - <view class="record-wrap" v-else>
  19 + <view class="record-wrap" >
8 20 <up-card
9 21 v-for="i in rows"
10   - :key="i.id"
  22 + :key="i.treenumber"
11 23 :border="false"
12 24 :show-head="false"
13 25 class="tree-card"
... ... @@ -16,7 +28,6 @@
16 28 >
17 29 <template #body>
18 30 <view class="card-body-inner">
19   - <!-- ✅ 保留你的 原生背景图写法 完全没动 核心要求满足 -->
20 31 <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view>
21 32  
22 33 <view class="record-list-right">
... ... @@ -36,15 +47,9 @@
36 47 树木编号:{{ i.treenumber }}
37 48 </view>
38 49 </template>
39   -
40   -<!-- <template #foot>-->
41   -<!-- <view class="treenumber-no">-->
42   -<!-- 树木编号:{{ i.treenumber }}-->
43   -<!-- </view>-->
44   -<!-- </template>-->
45 50 </up-card>
46 51 </view>
47   -
  52 + </z-paging>
48 53 <!-- 底部新增按钮 -->
49 54 <view class="fixed-bottom-btn-wrap">
50 55 <up-button
... ... @@ -82,7 +87,8 @@ onLoad((options) =&gt; {
82 87 })
83 88  
84 89 onShow(() => {
85   - treeRoadQuery()
  90 + // 初始化分页数据
  91 + pagingRef.value?.reload()
86 92 })
87 93  
88 94  
... ... @@ -99,10 +105,12 @@ const toAddTreePage = () =&gt; {
99 105 })
100 106 }
101 107  
102   -const treeRoadQuery = async () => {
103   - const res = await treeRoadReq( {road: roadId.value})
  108 +const pagingRef = ref(null) // z-paging实例
  109 +const fetchData = async (pageNo, pageSize) => {
  110 + const res = await treeRoadReq( {road: roadId.value,pageNo, pageSize})
104 111 console.log(res)
105   - rows.value = res.list
  112 + // rows.value = res.list
  113 + pagingRef.value?.complete(res?.list || [], res?.total)
106 114 }
107 115 </script>
108 116  
... ...
pages/index/index.vue
... ... @@ -88,7 +88,7 @@
88 88 <view class="task-item__footer u-flex common-item-center common-justify-between"
89 89 style="font-size: 13px; margin-top: 5px;">
90 90 <view class="urgency-tag" >
91   - 紧急程度: {{ item.pressingType }}
  91 + 紧急程度: {{ item.pressingType||'--' }}
92 92 </view>
93 93 <view style="font-size: 13px;color: #333">{{
94 94 timeFormat(item.busiDateTime, 'yyyy-mm-dd hh:MM:ss')
... ... @@ -371,7 +371,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量
371 371 .user-info-bar {
372 372 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
373 373 background-size: 100% 100%;
374   - padding: 90px $spacing-lg 135px;
  374 + padding: 80px $spacing-lg 135px;
375 375 display: flex;
376 376 justify-content: space-between;
377 377 align-items: center;
... ...
pages/login/index.vue
1 1 <template>
2   - <view class="login-page">
  2 + <view class="page-container">
3 3 <!-- 顶部标题区 -->
4 4 <view class="top-title">
5 5 <text class="welcome-text">你好,欢迎光临</text>
... ... @@ -112,27 +112,76 @@
112 112 import { ref, reactive, onMounted, nextTick } from 'vue';
113 113 import { useUserStore } from '@/pinia/user';
114 114 import globalConfig from '@/common/config/global';
  115 +import CryptoJS from 'crypto-js';
  116 +
  117 +// ========== 加密工具函数(建议抽离到 @/utils/encrypt.js) ==========
  118 +// 密钥配置:生产环境建议从后端接口获取,不要硬编码
  119 +const CRYPTO_CONFIG = {
  120 + key: CryptoJS.enc.Utf8.parse('jcsscrypto123abc'), // 16位key(AES-128)
  121 + iv: CryptoJS.enc.Utf8.parse('abc123jcsscrypto'), // 16位iv
  122 + mode: CryptoJS.mode.CBC,
  123 + padding: CryptoJS.pad.Pkcs7
  124 +};
115 125  
116   -// 全局实例 & 基础状态
  126 +/**
  127 + * AES加密
  128 + * @param {string} text 待加密文本
  129 + * @returns {string} 加密后的字符串
  130 + */
  131 +const aesEncrypt = (text) => {
  132 + if (!text) return '';
  133 + try {
  134 + return CryptoJS.AES.encrypt(text, CRYPTO_CONFIG.key, {
  135 + iv: CRYPTO_CONFIG.iv,
  136 + mode: CRYPTO_CONFIG.mode,
  137 + padding: CRYPTO_CONFIG.padding
  138 + }).toString();
  139 + } catch (err) {
  140 + console.error('AES加密失败:', err);
  141 + return '';
  142 + }
  143 +};
  144 +
  145 +/**
  146 + * AES解密
  147 + * @param {string} encryptedText 加密后的字符串
  148 + * @returns {string} 解密后的原始文本
  149 + */
  150 +const aesDecrypt = (encryptedText) => {
  151 + if (!encryptedText) return '';
  152 + try {
  153 + const decryptObj = CryptoJS.AES.decrypt(encryptedText, CRYPTO_CONFIG.key, {
  154 + iv: CRYPTO_CONFIG.iv,
  155 + mode: CRYPTO_CONFIG.mode,
  156 + padding: CRYPTO_CONFIG.padding
  157 + });
  158 + return decryptObj.toString(CryptoJS.enc.Utf8);
  159 + } catch (err) {
  160 + console.error('AES解密失败:', err);
  161 + return '';
  162 + }
  163 +};
  164 +
  165 +// ========== 业务逻辑 ==========
117 166 const userStore = useUserStore();
118 167 const loginFormRef = ref(null);
119 168 const isLoading = ref(false);
120 169  
121   -// Tabs配置(name直接存储显示文本)
  170 +// Tabs配置
122 171 const tabList = ref([
123 172 { name: '手机号登录' },
124 173 { name: '账号登录' }
125 174 ]);
126   -const loginType = ref('手机号登录'); // 登录类型标识
  175 +const loginType = ref('手机号登录');
127 176  
128   -// 记住密码(默认选中)
  177 +// 记住密码
129 178 const rememberPwd = ref(true);
130 179  
131 180 // 表单数据
132 181 const form = reactive({
133   - account: '', // 账号
134   - mobile: '', // 手机号
135   - password: '' // 密码
  182 + account: '',
  183 + mobile: '',
  184 + password: ''
136 185 });
137 186  
138 187 // 表单校验规则
... ... @@ -152,56 +201,19 @@ const loginFormRules = reactive({
152 201 });
153 202  
154 203 // Tabs切换事件
155   -const handleTabChange = ({name}) => {
156   - console.log(name)
  204 +const handleTabChange = ({ name }) => {
157 205 if (isLoading.value) return;
158 206 loginType.value = name;
159   -
160   - // 切换时清空另一类输入框
161 207 if (name === '手机号登录') {
162 208 form.account = '';
163 209 } else {
164 210 form.mobile = '';
165 211 }
166   -
167   - // 清空校验状态
168 212 nextTick(() => {
169 213 loginFormRef.value?.clearValidate();
170 214 });
171 215 };
172 216  
173   -// 生命周期
174   -onMounted(() => {
175   - // 检查登录态
176   - checkLoginStatus();
177   -
178   - // 初始化表单校验规则
179   - nextTick(() => {
180   - loginFormRef.value?.setRules(loginFormRules);
181   - });
182   -
183   - // 读取缓存的账号/密码
184   - if (rememberPwd.value) {
185   - try {
186   - const savedAccount = uni.getStorageSync('login_account') || '';
187   - const savedMobile = uni.getStorageSync('login_mobile') || '';
188   - const savedPwd = uni.getStorageSync('login_password') || '';
189   -
190   - if (savedAccount) {
191   - form.account = savedAccount;
192   - loginType.value = '账号登录';
193   -
194   - } else if (savedMobile) {
195   - form.mobile = savedMobile;
196   - loginType.value = '手机号登录';
197   - }
198   - form.password = savedPwd;
199   - } catch (err) {
200   - console.warn('读取缓存密码失败:', err);
201   - }
202   - }
203   -});
204   -
205 217 // 检查登录状态
206 218 const checkLoginStatus = () => {
207 219 try {
... ... @@ -212,21 +224,19 @@ const checkLoginStatus = () =&gt; {
212 224 });
213 225 }
214 226 } catch (err) {
215   - console.warn('检查登录状态失败', err);
  227 + console.warn('检查登录状态失败:', err);
216 228 }
217 229 };
218 230  
219 231 // 登录方法
220 232 const handleLogin = async () => {
221 233 try {
222   - // 表单校验
223 234 await loginFormRef.value.validate();
224 235 isLoading.value = true;
225 236  
226 237 // 组装登录参数
227 238 const loginParams = { password: form.password };
228 239 if (loginType.value === '手机号登录') {
229   - // loginParams.mobile = form.mobile;
230 240 loginParams.username = form.mobile;
231 241 } else {
232 242 loginParams.username = form.account;
... ... @@ -235,19 +245,20 @@ const handleLogin = async () =&gt; {
235 245 // 执行登录
236 246 await userStore.login(loginParams);
237 247  
238   - // 保存记住密码
  248 + // 保存数据:账号/密码 均用AES加密
239 249 if (rememberPwd.value) {
240 250 try {
241 251 if (loginType.value === '手机号登录') {
242   - uni.setStorageSync('login_mobile', form.mobile);
  252 + uni.setStorageSync('login_mobile', aesEncrypt(form.mobile));
243 253 uni.removeStorageSync('login_account');
244 254 } else {
245   - uni.setStorageSync('login_account', form.account);
  255 + uni.setStorageSync('login_account', aesEncrypt(form.account));
246 256 uni.removeStorageSync('login_mobile');
247 257 }
248   - uni.setStorageSync('login_password', form.password);
  258 + // 密码加密存储(可解密还原)
  259 + uni.setStorageSync('login_password', aesEncrypt(form.password));
249 260 } catch (err) {
250   - console.warn('保存密码失败:', err);
  261 + console.warn('保存登录信息失败:', err);
251 262 }
252 263 } else {
253 264 // 清除缓存
... ... @@ -256,7 +267,7 @@ const handleLogin = async () =&gt; {
256 267 uni.removeStorageSync('login_password');
257 268 }
258 269  
259   - // 登录成功提示+跳转
  270 + // 登录成功跳转
260 271 uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 });
261 272 setTimeout(() => {
262 273 uni.switchTab({
... ... @@ -265,9 +276,8 @@ const handleLogin = async () =&gt; {
265 276 });
266 277 }, 1000);
267 278 } catch (err) {
268   - // 错误处理
269 279 if (!Array.isArray(err)) {
270   - console.error('登录失败', err);
  280 + console.error('登录失败:', err);
271 281 uni.showToast({
272 282 title: err.msg || '账号或密码错误,请重试',
273 283 icon: 'none',
... ... @@ -278,20 +288,39 @@ const handleLogin = async () =&gt; {
278 288 isLoading.value = false;
279 289 }
280 290 };
  291 +
  292 +// 生命周期
  293 +onMounted(() => {
  294 + checkLoginStatus();
  295 + // 初始化表单规则
  296 + nextTick(() => {
  297 + loginFormRef.value?.setRules(loginFormRules);
  298 + });
  299 + // 读取缓存并解密填充
  300 + if (rememberPwd.value) {
  301 + try {
  302 + const savedAccount = aesDecrypt(uni.getStorageSync('login_account') || '');
  303 + const savedMobile = aesDecrypt(uni.getStorageSync('login_mobile') || '');
  304 + const savedPwd = aesDecrypt(uni.getStorageSync('login_password') || '');
  305 +
  306 + if (savedAccount) {
  307 + form.account = savedAccount;
  308 + loginType.value = '账号登录';
  309 + } else if (savedMobile) {
  310 + form.mobile = savedMobile;
  311 + loginType.value = '手机号登录';
  312 + }
  313 + // 密码解密后填充到输入框(可展示)
  314 + form.password = savedPwd;
  315 + } catch (err) {
  316 + console.warn('读取缓存失败:', err);
  317 + }
  318 + }
  319 +});
281 320 </script>
282 321  
283 322 <style scoped lang="scss">
284   -// 核心布局
285   -.login-page {
286   - min-height: 100vh;
287   - padding: 0;
288   - box-sizing: border-box;
289   - overflow: hidden;
290   - position: relative;
291   - background: #f5f7fa;
292   -}
293 323  
294   -// 顶部样式
295 324 .top-title {
296 325 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
297 326 background-size: 100% 100%;
... ... @@ -327,7 +356,6 @@ const handleLogin = async () =&gt; {
327 356 box-sizing: border-box;
328 357 }
329 358  
330   -// Tabs样式适配
331 359 :deep(.u-tabs) {
332 360 margin-bottom: 15px;
333 361 .u-tabs__content {
... ... @@ -338,29 +366,24 @@ const handleLogin = async () =&gt; {
338 366 }
339 367 }
340 368  
341   -// 表单间距
342 369 :deep(.u-form-item) {
343 370 margin-bottom: 10px;
344 371 position: relative;
345 372 }
346 373  
347   -// 密码项样式
348 374 .password-item {
349 375 position: relative;
350 376 margin-bottom: 5px !important;
351 377 }
352 378  
353   -// ✅ 核心优化:记住密码完全居右对齐
354 379 .remember-wrap {
355   - //text-align: right;
356   - padding: 0; // 移除多余内边距
  380 + padding: 0;
357 381 margin: 10px 0 20px;
358 382  
359 383 :deep(.u-checkbox) {
360 384 font-size: 12px;
361 385 color: #666;
362 386 justify-content: flex-end;
363   - // 移除复选框的默认左边距,确保居右紧凑
364 387 margin-left: 0 !important;
365 388  
366 389 .u-checkbox__label {
... ... @@ -369,7 +392,6 @@ const handleLogin = async () =&gt; {
369 392 }
370 393 }
371 394  
372   -// 登录按钮
373 395 .login-btn {
374 396 margin-top: 10px;
375 397 width: 100%;
... ... @@ -382,7 +404,6 @@ const handleLogin = async () =&gt; {
382 404 border-radius: 23px;
383 405 }
384 406  
385   -// 版权信息
386 407 .copyright {
387 408 width: 100%;
388 409 text-align: center;
... ...
pages/mine/index.vue
... ... @@ -13,7 +13,7 @@
13 13 <view class="user-info-content">
14 14 <view class="user-name">{{ userStore.isLogin ? userInfo.username : '未登录' }}</view>
15 15 <!-- <view class="user-phone">{{ userStore.isLogin ? userInfo.nickname : '&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;' }}</view>-->
16   - <view class="user-phone">上次登录时间{{ timeFormat(userInfo.loginDate)}}</view>
  16 + <view class="user-phone">上次登录时间 {{ timeFormat(userInfo.loginDate)}}</view>
17 17  
18 18 </view>
19 19  
... ... @@ -123,7 +123,7 @@ export default {
123 123 top: 0;
124 124 left: 0;
125 125 width: 100%;
126   - height: 230px;
  126 + height: 220px;
127 127 //height: 120px;
128 128 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
129 129 background-size: 100% 100%;
... ... @@ -160,17 +160,15 @@ export default {
160 160 }
161 161 // 退出登录按钮
162 162 .logout-btn-wrap {
163   - margin: 100px 10px 10px;
  163 + margin: 80px 15px 0;
164 164 }
165 165 // 登录按钮
166 166 .login-btn {
167   - margin-top: 20px;
168 167 width: 100%;
169 168 height: 44px;
170 169 line-height: 44px;
171 170 border-radius: 4px;
172 171 font-size: 16px;
173   -
174 172 background: #0A86F4;
175 173 box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5);
176 174 border-radius: 23px;
... ...
pages/workbench/index.vue
... ... @@ -166,7 +166,7 @@ const handleMenuClick = (item: MenuItem) =&gt; {
166 166 top: 0;
167 167 left: 0;
168 168 width: 100%;
169   - padding: 80px 15px 125px;
  169 + padding: 80px 15px 115px;
170 170 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
171 171 background-size: 100% 100%;
172 172 z-index: 1;
... ... @@ -176,7 +176,7 @@ const handleMenuClick = (item: MenuItem) =&gt; {
176 176 .welcome-text {
177 177 font-size: 16px;
178 178 display: block;
179   - margin-bottom: 10px;
  179 + margin-bottom: 4px;
180 180 font-weight: 500;
181 181 }
182 182  
... ... @@ -191,7 +191,7 @@ const handleMenuClick = (item: MenuItem) =&gt; {
191 191 .content-wrap {
192 192 position: relative;
193 193 z-index: 2;
194   - padding: 135px 0 0;
  194 + padding: 125px 0 0;
195 195 display: flex;
196 196 flex-direction: column;
197 197 gap: 10px;
... ...