m0_70156489 4 주 전
부모
커밋
2c69848004

+ 55 - 21
application/index/controller/Index.php

@@ -3827,7 +3827,8 @@ class Index extends Frontend
     }
 
     /**
-     * 普通用户修改密码(POST:old_password、new_password、renew_password)
+     * 修改密码(POST:old_password、new_password、renew_password)
+     * 支持供应商 customer 与手机端管理员 admin
      */
     public function mprocChangePwd()
     {
@@ -3838,14 +3839,6 @@ class Index extends Frontend
         if (!$user) {
             $this->error('请先登录', url('index/index/login'));
         }
-        $user = $this->mprocSyncSessionCustomerUser($user);
-        if (!empty($user['is_admin'])) {
-            $this->error('当前账号不支持修改密码');
-        }
-        $cu = $this->mprocResolveCustomerUserForSession($user);
-        if (!$cu) {
-            $this->error('账号不存在或已禁用');
-        }
         $oldPwd = (string)$this->request->post('old_password', '');
         $newPwd = (string)$this->request->post('new_password', '');
         $renewPwd = (string)$this->request->post('renew_password', '');
@@ -3862,19 +3855,60 @@ class Index extends Frontend
         if ($oldPwd === $newPwd) {
             $this->error('新密码不能与旧密码相同');
         }
-        $cuId = (int)($cu['id'] ?? 0);
-        if (!$this->mprocVerifyCustomerUserPassword($cu, $oldPwd)) {
-            $this->error('原密码不正确');
-        }
-        $data = [
-            'password'   => $this->mprocHashCustomerUserPassword($newPwd),
-            'updatetime' => date('Y-m-d H:i:s'),
-        ];
-        try {
-            Db::table('customer')->where('id', $cuId)->update($data);
-        } catch (\Throwable $e) {
-            $this->error('修改失败:' . $e->getMessage());
+
+        if (!empty($user['is_admin'])) {
+            $adminId = (int)($user['admin_id'] ?? 0);
+            if ($adminId <= 0) {
+                $this->error('账号不存在或已禁用');
+            }
+            $row = null;
+            try {
+                $row = Db::name('admin')
+                    ->field('id,username,password,salt,status')
+                    ->where('id', $adminId)
+                    ->find();
+            } catch (\Throwable $e) {
+                $row = null;
+            }
+            if (!$row || !is_array($row) || (($row['status'] ?? '') === 'hidden')) {
+                $this->error('账号不存在或已禁用');
+            }
+            $salt = (string)($row['salt'] ?? '');
+            $hashStored = (string)($row['password'] ?? '');
+            $hashOld = md5(md5($oldPwd) . $salt);
+            if ($hashStored === '' || $hashOld !== $hashStored) {
+                $this->error('原密码不正确');
+            }
+            $hashNew = md5(md5($newPwd) . $salt);
+            try {
+                Db::name('admin')->where('id', $adminId)->update([
+                    'password'   => $hashNew,
+                    'updatetime' => time(),
+                ]);
+            } catch (\Throwable $e) {
+                $this->error('修改失败:' . $e->getMessage());
+            }
+        } else {
+            $user = $this->mprocSyncSessionCustomerUser($user);
+            $cu = $this->mprocResolveCustomerUserForSession($user);
+            if (!$cu) {
+                $this->error('账号不存在或已禁用');
+            }
+            $cuId = (int)($cu['id'] ?? 0);
+            if (!$this->mprocVerifyCustomerUserPassword($cu, $oldPwd)) {
+                $this->error('原密码不正确');
+            }
+            $data = [
+                'password'   => $this->mprocHashCustomerUserPassword($newPwd),
+                'updatetime' => date('Y-m-d H:i:s'),
+            ];
+            try {
+                Db::table('customer')->where('id', $cuId)->update($data);
+            } catch (\Throwable $e) {
+                $this->error('修改失败:' . $e->getMessage());
+            }
         }
+
         // 改密后强制重新登录
         $token = Session::get('mproc_token');
         if ($token === null || $token === '') {

+ 183 - 9
application/index/view/index/deliveryscore.html

@@ -24,10 +24,18 @@
             position: fixed; top: 0; left: 0; right: 0; z-index: 50;
             height: var(--bar-h); padding: 0 14px;
             background: #3c8dbc; color: #fff;
-            display: flex; align-items: center; justify-content: space-between;
+            display: flex; align-items: center; justify-content: center;
+        }
+        .bar h1 {
+            position: absolute; left: 0; right: 0;
+            margin: 0; font-size: 1.05rem; font-weight: 600;
+            text-align: center; pointer-events: none;
+        }
+        .bar a {
+            margin-left: auto; position: relative; z-index: 1;
+            color: #fff; text-decoration: none; font-size: 13px;
+            padding: 5px 10px; border: 1px solid rgba(255,255,255,.6); border-radius: 6px;
         }
-        .bar h1 { font-size: 1.05rem; margin: 0; font-weight: 600; }
-        .bar a { color: #fff; text-decoration: none; font-size: 13px; padding: 5px 10px; border: 1px solid rgba(255,255,255,.6); border-radius: 6px; }
         body.layout-orders .bar-logout { display: none !important; }
         #pane-orders {
             position: fixed; left: 0; right: 0; z-index: 10;
@@ -107,12 +115,39 @@
         .me-row { font-size: 14px; line-height: 1.7; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
         .me-row:last-child { border-bottom: none; }
         .me-row span { display: inline-block; min-width: 4.5em; color: #888; }
-        .btn-me-logout {
+        .btn-me-pwd {
             display: block; width: 100%; margin-top: 16px; padding: 12px;
-            border: 1px solid #e74c3c; border-radius: 8px; background: #fff;
-            color: #e74c3c; font-size: 15px; font-weight: 600; text-align: center;
-            text-decoration: none; font-family: inherit;
+            border: 1px solid #3c8dbc; border-radius: 8px; background: #fff;
+            color: #3c8dbc; font-size: 15px; font-weight: 600; text-align: center;
+            cursor: pointer; font-family: inherit;
         }
+        .btn-me-pwd:active { opacity: .85; }
+        .pwd-mask {
+            display: none; position: fixed; inset: 0; z-index: 2100;
+            background: rgba(0,0,0,.45); align-items: center; justify-content: center;
+            padding: 24px 28px; padding-bottom: calc(24px + var(--safe-bottom));
+        }
+        .pwd-mask.show { display: flex; }
+        .pwd-sheet {
+            width: 100%; max-width: 360px; background: #fff; border-radius: 14px;
+            padding: 18px 18px 20px; box-shadow: 0 8px 32px rgba(0,0,0,.12);
+        }
+        .pwd-sheet .pwd-head { font-size: 16px; font-weight: 600; margin: 0 0 14px; color: #222; }
+        .pwd-field { margin-bottom: 14px; }
+        .pwd-field label { display: block; font-size: 13px; color: #666; margin-bottom: 6px; }
+        .pwd-field input {
+            width: 100%; padding: 11px 12px; border: 1px solid #ddd; border-radius: 8px;
+            font-size: 16px; font-family: inherit; box-sizing: border-box;
+        }
+        .pwd-field input:focus { outline: none; border-color: #3c8dbc; }
+        .pwd-acts { display: flex; gap: 10px; margin-top: 18px; }
+        .pwd-acts button {
+            flex: 1; padding: 12px; border-radius: 8px; font-size: 15px; border: none;
+            cursor: pointer; font-family: inherit;
+        }
+        .pwd-acts .pwd-cancel { background: #f0f0f0; color: #555; }
+        .pwd-acts .pwd-ok { background: #3c8dbc; color: #fff; font-weight: 600; }
+        .pwd-acts .pwd-ok:disabled { opacity: .6; }
         .tabbar {
             position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
             display: flex; background: #fff; border-top: 1px solid #e5e5e5;
@@ -293,7 +328,7 @@
         <div class="me-row"><span>部门</span>{$mprocProfile.department|default=''|htmlentities}</div>
         <div class="me-row"><span>手机号</span>{$mprocProfile.phone|default=''|htmlentities}</div>
         <div class="me-row"><span>邮箱</span>{$mprocProfile.email|default=''|htmlentities}</div>
-        <a class="btn-me-logout" href="{:url('index/index/logout')}">退出登录</a>
+        <button type="button" class="btn-me-pwd" id="btn-me-change-pwd">修改密码</button>
     </div>
 </div>
 
@@ -303,6 +338,27 @@
 </nav>
 
 <div class="toast" id="toast"></div>
+<div class="pwd-mask" id="pwdMask" aria-hidden="true">
+    <div class="pwd-sheet" id="pwdSheet">
+        <p class="pwd-head">修改密码</p>
+        <div class="pwd-field">
+            <label for="inp-old-pwd">原密码</label>
+            <input type="password" id="inp-old-pwd" autocomplete="current-password" maxlength="20">
+        </div>
+        <div class="pwd-field">
+            <label for="inp-new-pwd">新密码</label>
+            <input type="password" id="inp-new-pwd" autocomplete="new-password" maxlength="20" placeholder="8~20位,须含字母和数字">
+        </div>
+        <div class="pwd-field">
+            <label for="inp-renew-pwd">确认新密码</label>
+            <input type="password" id="inp-renew-pwd" autocomplete="new-password" maxlength="20" placeholder="再次输入新密码">
+        </div>
+        <div class="pwd-acts">
+            <button type="button" class="pwd-cancel" id="pwdCancel">取消</button>
+            <button type="button" class="pwd-ok" id="pwdSave">确定</button>
+        </div>
+    </div>
+</div>
 <div class="confirm-mask" id="scoreMask">
     <div class="confirm-box">
         <div class="confirm-box-body">
@@ -339,6 +395,7 @@
     var listUrl = "{:url('index/index/deliveryscorelist')}";
     var saveUrl = "{:url('index/index/deliveryscoresave')}";
     var loginUrl = "{:url('index/index/login')}";
+    var changePwdUrl = "{:url('index/index/mprocChangePwd')}";
     var token = {:json_encode(isset($mprocBootstrapToken) ? $mprocBootstrapToken : '', JSON_UNESCAPED_UNICODE)};
     var keepHours = {$mprocBootstrapKeepHours|default=72};
 
@@ -356,12 +413,129 @@
         } catch (e) {}
     }
 
-    document.querySelectorAll('a.btn-me-logout, a.bar-logout').forEach(function (a) {
+    document.querySelectorAll('a.bar-logout').forEach(function (a) {
         a.addEventListener('click', function () {
             clearLocalLoginToken();
         });
     });
 
+    function passwordHasSequentialRun(pwd, runLen) {
+        runLen = runLen || 4;
+        var lower = String(pwd || '').toLowerCase();
+        var n = lower.length;
+        if (n < runLen) return false;
+        for (var i = 0; i <= n - runLen; i++) {
+            var asc = true, desc = true;
+            for (var j = 1; j < runLen; j++) {
+                var prev = lower.charCodeAt(i + j - 1);
+                var cur = lower.charCodeAt(i + j);
+                if (cur !== prev + 1) asc = false;
+                if (cur !== prev - 1) desc = false;
+                if (!asc && !desc) break;
+            }
+            if (asc || desc) return true;
+        }
+        return false;
+    }
+
+    function validateStrongPassword(pwd) {
+        pwd = String(pwd || '');
+        if (pwd.length < 8 || pwd.length > 20) return '密码长度须为8~20位';
+        if (/\s/.test(pwd)) return '密码不能包含空格';
+        if (/^\d+$/.test(pwd)) return '密码不能为纯数字,请同时包含字母';
+        if (/^[a-zA-Z]+$/.test(pwd)) return '密码不能为纯字母,请同时包含数字';
+        if (!/[a-zA-Z]/.test(pwd) || !/\d/.test(pwd)) return '密码须同时包含字母和数字';
+        if (/(.)\1{3,}/.test(pwd)) return '密码不能包含过多重复字符(如1111、aaaa)';
+        if (passwordHasSequentialRun(pwd, 4)) return '密码不能包含连续字符(如1234、abcd)';
+        var weak = {
+            '12345678': 1, '123456789': 1, '1234567890': 1, '87654321': 1, '01234567': 1,
+            'password': 1, 'password1': 1, 'passw0rd': 1, 'qwerty12': 1, 'qwertyui': 1,
+            'abc12345': 1, 'abcd1234': 1, 'a1b2c3d4': 1, '11111111': 1, '00000000': 1,
+            '88888888': 1, '66666666': 1, '11223344': 1, '1qaz2wsx': 1, 'qazwsxed': 1
+        };
+        if (weak[pwd.toLowerCase()]) return '密码过于简单,请重新设置';
+        return '';
+    }
+
+    (function bindChangePwd() {
+        var pwdMask = document.getElementById('pwdMask');
+        var pwdSheet = document.getElementById('pwdSheet');
+        var btnMePwd = document.getElementById('btn-me-change-pwd');
+        var inpOldPwd = document.getElementById('inp-old-pwd');
+        var inpNewPwd = document.getElementById('inp-new-pwd');
+        var inpRenewPwd = document.getElementById('inp-renew-pwd');
+        var btnPwdSave = document.getElementById('pwdSave');
+        var btnPwdCancel = document.getElementById('pwdCancel');
+        function openPwdModal() {
+            if (inpOldPwd) inpOldPwd.value = '';
+            if (inpNewPwd) inpNewPwd.value = '';
+            if (inpRenewPwd) inpRenewPwd.value = '';
+            if (pwdMask) {
+                pwdMask.classList.add('show');
+                pwdMask.setAttribute('aria-hidden', 'false');
+            }
+        }
+        function closePwdModal() {
+            if (pwdMask) {
+                pwdMask.classList.remove('show');
+                pwdMask.setAttribute('aria-hidden', 'true');
+            }
+        }
+        if (btnMePwd) btnMePwd.addEventListener('click', openPwdModal);
+        if (btnPwdCancel) btnPwdCancel.addEventListener('click', closePwdModal);
+        if (pwdMask) {
+            pwdMask.addEventListener('click', function (e) {
+                if (e.target === pwdMask) closePwdModal();
+            });
+        }
+        if (pwdSheet) pwdSheet.addEventListener('click', function (e) { e.stopPropagation(); });
+        if (btnPwdSave) {
+            btnPwdSave.addEventListener('click', function () {
+                var oldP = inpOldPwd ? inpOldPwd.value : '';
+                var newP = inpNewPwd ? inpNewPwd.value : '';
+                var renP = inpRenewPwd ? inpRenewPwd.value : '';
+                if (!oldP || !newP || !renP) {
+                    showToast('请填写完整', 'err');
+                    return;
+                }
+                var tip = validateStrongPassword(newP);
+                if (tip) {
+                    showToast(tip, 'err');
+                    return;
+                }
+                if (newP !== renP) {
+                    showToast('两次输入的新密码不一致', 'err');
+                    return;
+                }
+                if (oldP === newP) {
+                    showToast('新密码不能与旧密码相同', 'err');
+                    return;
+                }
+                btnPwdSave.disabled = true;
+                postForm(changePwdUrl, {
+                    old_password: oldP,
+                    new_password: newP,
+                    renew_password: renP
+                }).then(function (ret) {
+                    btnPwdSave.disabled = false;
+                    if (ret && (ret.code === 1 || ret.code === '1')) {
+                        closePwdModal();
+                        clearLocalLoginToken();
+                        showToast(ret.msg || '密码已修改,请重新登录', 'ok');
+                        setTimeout(function () {
+                            window.location.href = ret.url || loginUrl;
+                        }, 900);
+                    } else {
+                        showToast((ret && ret.msg) ? ret.msg : '修改失败', 'err');
+                    }
+                }).catch(function () {
+                    btnPwdSave.disabled = false;
+                    showToast('网络错误', 'err');
+                });
+            });
+        }
+    })();
+
     function showToast(msg, type) {
         var el = document.getElementById('toast');
         if (!el) return;

+ 183 - 9
application/index/view/index/inboundscore.html

@@ -24,10 +24,18 @@
             position: fixed; top: 0; left: 0; right: 0; z-index: 50;
             height: var(--bar-h); padding: 0 14px;
             background: #3c8dbc; color: #fff;
-            display: flex; align-items: center; justify-content: space-between;
+            display: flex; align-items: center; justify-content: center;
+        }
+        .bar h1 {
+            position: absolute; left: 0; right: 0;
+            margin: 0; font-size: 1.05rem; font-weight: 600;
+            text-align: center; pointer-events: none;
+        }
+        .bar a {
+            margin-left: auto; position: relative; z-index: 1;
+            color: #fff; text-decoration: none; font-size: 13px;
+            padding: 5px 10px; border: 1px solid rgba(255,255,255,.6); border-radius: 6px;
         }
-        .bar h1 { font-size: 1.05rem; margin: 0; font-weight: 600; }
-        .bar a { color: #fff; text-decoration: none; font-size: 13px; padding: 5px 10px; border: 1px solid rgba(255,255,255,.6); border-radius: 6px; }
         body.layout-orders .bar-logout { display: none !important; }
         #pane-orders {
             position: fixed; left: 0; right: 0; z-index: 10;
@@ -110,12 +118,39 @@
         .me-row { font-size: 14px; line-height: 1.7; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
         .me-row:last-child { border-bottom: none; }
         .me-row span { display: inline-block; min-width: 4.5em; color: #888; }
-        .btn-me-logout {
+        .btn-me-pwd {
             display: block; width: 100%; margin-top: 16px; padding: 12px;
-            border: 1px solid #e74c3c; border-radius: 8px; background: #fff;
-            color: #e74c3c; font-size: 15px; font-weight: 600; text-align: center;
-            text-decoration: none; font-family: inherit;
+            border: 1px solid #3c8dbc; border-radius: 8px; background: #fff;
+            color: #3c8dbc; font-size: 15px; font-weight: 600; text-align: center;
+            cursor: pointer; font-family: inherit;
         }
+        .btn-me-pwd:active { opacity: .85; }
+        .pwd-mask {
+            display: none; position: fixed; inset: 0; z-index: 2100;
+            background: rgba(0,0,0,.45); align-items: center; justify-content: center;
+            padding: 24px 28px; padding-bottom: calc(24px + var(--safe-bottom));
+        }
+        .pwd-mask.show { display: flex; }
+        .pwd-sheet {
+            width: 100%; max-width: 360px; background: #fff; border-radius: 14px;
+            padding: 18px 18px 20px; box-shadow: 0 8px 32px rgba(0,0,0,.12);
+        }
+        .pwd-sheet .pwd-head { font-size: 16px; font-weight: 600; margin: 0 0 14px; color: #222; }
+        .pwd-field { margin-bottom: 14px; }
+        .pwd-field label { display: block; font-size: 13px; color: #666; margin-bottom: 6px; }
+        .pwd-field input {
+            width: 100%; padding: 11px 12px; border: 1px solid #ddd; border-radius: 8px;
+            font-size: 16px; font-family: inherit; box-sizing: border-box;
+        }
+        .pwd-field input:focus { outline: none; border-color: #3c8dbc; }
+        .pwd-acts { display: flex; gap: 10px; margin-top: 18px; }
+        .pwd-acts button {
+            flex: 1; padding: 12px; border-radius: 8px; font-size: 15px; border: none;
+            cursor: pointer; font-family: inherit;
+        }
+        .pwd-acts .pwd-cancel { background: #f0f0f0; color: #555; }
+        .pwd-acts .pwd-ok { background: #3c8dbc; color: #fff; font-weight: 600; }
+        .pwd-acts .pwd-ok:disabled { opacity: .6; }
         .tabbar {
             position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
             display: flex; background: #fff; border-top: 1px solid #e5e5e5;
@@ -279,7 +314,7 @@
         <div class="me-row"><span>部门</span>{$mprocProfile.department|default=''|htmlentities}</div>
         <div class="me-row"><span>手机号</span>{$mprocProfile.phone|default=''|htmlentities}</div>
         <div class="me-row"><span>邮箱</span>{$mprocProfile.email|default=''|htmlentities}</div>
-        <a class="btn-me-logout" href="{:url('index/index/logout')}">退出登录</a>
+        <button type="button" class="btn-me-pwd" id="btn-me-change-pwd">修改密码</button>
     </div>
 </div>
 
@@ -289,6 +324,27 @@
 </nav>
 
 <div class="toast" id="toast"></div>
+<div class="pwd-mask" id="pwdMask" aria-hidden="true">
+    <div class="pwd-sheet" id="pwdSheet">
+        <p class="pwd-head">修改密码</p>
+        <div class="pwd-field">
+            <label for="inp-old-pwd">原密码</label>
+            <input type="password" id="inp-old-pwd" autocomplete="current-password" maxlength="20">
+        </div>
+        <div class="pwd-field">
+            <label for="inp-new-pwd">新密码</label>
+            <input type="password" id="inp-new-pwd" autocomplete="new-password" maxlength="20" placeholder="8~20位,须含字母和数字">
+        </div>
+        <div class="pwd-field">
+            <label for="inp-renew-pwd">确认新密码</label>
+            <input type="password" id="inp-renew-pwd" autocomplete="new-password" maxlength="20" placeholder="再次输入新密码">
+        </div>
+        <div class="pwd-acts">
+            <button type="button" class="pwd-cancel" id="pwdCancel">取消</button>
+            <button type="button" class="pwd-ok" id="pwdSave">确定</button>
+        </div>
+    </div>
+</div>
 <div class="confirm-mask" id="scoreMask">
     <div class="confirm-box">
         <div class="confirm-box-body">
@@ -366,6 +422,7 @@
     var saveUrl = "{:url('index/index/inboundscoresave')}";
     var complaintUrl = "{:url('index/index/inboundscorecomplaint')}";
     var loginUrl = "{:url('index/index/login')}";
+    var changePwdUrl = "{:url('index/index/mprocChangePwd')}";
     var token = {:json_encode(isset($mprocBootstrapToken) ? $mprocBootstrapToken : '', JSON_UNESCAPED_UNICODE)};
     var keepHours = {$mprocBootstrapKeepHours|default=72};
 
@@ -383,12 +440,129 @@
         } catch (e) {}
     }
 
-    document.querySelectorAll('a.btn-me-logout, a.bar-logout').forEach(function (a) {
+    document.querySelectorAll('a.bar-logout').forEach(function (a) {
         a.addEventListener('click', function () {
             clearLocalLoginToken();
         });
     });
 
+    function passwordHasSequentialRun(pwd, runLen) {
+        runLen = runLen || 4;
+        var lower = String(pwd || '').toLowerCase();
+        var n = lower.length;
+        if (n < runLen) return false;
+        for (var i = 0; i <= n - runLen; i++) {
+            var asc = true, desc = true;
+            for (var j = 1; j < runLen; j++) {
+                var prev = lower.charCodeAt(i + j - 1);
+                var cur = lower.charCodeAt(i + j);
+                if (cur !== prev + 1) asc = false;
+                if (cur !== prev - 1) desc = false;
+                if (!asc && !desc) break;
+            }
+            if (asc || desc) return true;
+        }
+        return false;
+    }
+
+    function validateStrongPassword(pwd) {
+        pwd = String(pwd || '');
+        if (pwd.length < 8 || pwd.length > 20) return '密码长度须为8~20位';
+        if (/\s/.test(pwd)) return '密码不能包含空格';
+        if (/^\d+$/.test(pwd)) return '密码不能为纯数字,请同时包含字母';
+        if (/^[a-zA-Z]+$/.test(pwd)) return '密码不能为纯字母,请同时包含数字';
+        if (!/[a-zA-Z]/.test(pwd) || !/\d/.test(pwd)) return '密码须同时包含字母和数字';
+        if (/(.)\1{3,}/.test(pwd)) return '密码不能包含过多重复字符(如1111、aaaa)';
+        if (passwordHasSequentialRun(pwd, 4)) return '密码不能包含连续字符(如1234、abcd)';
+        var weak = {
+            '12345678': 1, '123456789': 1, '1234567890': 1, '87654321': 1, '01234567': 1,
+            'password': 1, 'password1': 1, 'passw0rd': 1, 'qwerty12': 1, 'qwertyui': 1,
+            'abc12345': 1, 'abcd1234': 1, 'a1b2c3d4': 1, '11111111': 1, '00000000': 1,
+            '88888888': 1, '66666666': 1, '11223344': 1, '1qaz2wsx': 1, 'qazwsxed': 1
+        };
+        if (weak[pwd.toLowerCase()]) return '密码过于简单,请重新设置';
+        return '';
+    }
+
+    (function bindChangePwd() {
+        var pwdMask = document.getElementById('pwdMask');
+        var pwdSheet = document.getElementById('pwdSheet');
+        var btnMePwd = document.getElementById('btn-me-change-pwd');
+        var inpOldPwd = document.getElementById('inp-old-pwd');
+        var inpNewPwd = document.getElementById('inp-new-pwd');
+        var inpRenewPwd = document.getElementById('inp-renew-pwd');
+        var btnPwdSave = document.getElementById('pwdSave');
+        var btnPwdCancel = document.getElementById('pwdCancel');
+        function openPwdModal() {
+            if (inpOldPwd) inpOldPwd.value = '';
+            if (inpNewPwd) inpNewPwd.value = '';
+            if (inpRenewPwd) inpRenewPwd.value = '';
+            if (pwdMask) {
+                pwdMask.classList.add('show');
+                pwdMask.setAttribute('aria-hidden', 'false');
+            }
+        }
+        function closePwdModal() {
+            if (pwdMask) {
+                pwdMask.classList.remove('show');
+                pwdMask.setAttribute('aria-hidden', 'true');
+            }
+        }
+        if (btnMePwd) btnMePwd.addEventListener('click', openPwdModal);
+        if (btnPwdCancel) btnPwdCancel.addEventListener('click', closePwdModal);
+        if (pwdMask) {
+            pwdMask.addEventListener('click', function (e) {
+                if (e.target === pwdMask) closePwdModal();
+            });
+        }
+        if (pwdSheet) pwdSheet.addEventListener('click', function (e) { e.stopPropagation(); });
+        if (btnPwdSave) {
+            btnPwdSave.addEventListener('click', function () {
+                var oldP = inpOldPwd ? inpOldPwd.value : '';
+                var newP = inpNewPwd ? inpNewPwd.value : '';
+                var renP = inpRenewPwd ? inpRenewPwd.value : '';
+                if (!oldP || !newP || !renP) {
+                    showToast('请填写完整', 'err');
+                    return;
+                }
+                var tip = validateStrongPassword(newP);
+                if (tip) {
+                    showToast(tip, 'err');
+                    return;
+                }
+                if (newP !== renP) {
+                    showToast('两次输入的新密码不一致', 'err');
+                    return;
+                }
+                if (oldP === newP) {
+                    showToast('新密码不能与旧密码相同', 'err');
+                    return;
+                }
+                btnPwdSave.disabled = true;
+                postForm(changePwdUrl, {
+                    old_password: oldP,
+                    new_password: newP,
+                    renew_password: renP
+                }).then(function (ret) {
+                    btnPwdSave.disabled = false;
+                    if (ret && (ret.code === 1 || ret.code === '1')) {
+                        closePwdModal();
+                        clearLocalLoginToken();
+                        showToast(ret.msg || '密码已修改,请重新登录', 'ok');
+                        setTimeout(function () {
+                            window.location.href = ret.url || loginUrl;
+                        }, 900);
+                    } else {
+                        showToast((ret && ret.msg) ? ret.msg : '修改失败', 'err');
+                    }
+                }).catch(function () {
+                    btnPwdSave.disabled = false;
+                    showToast('网络错误', 'err');
+                });
+            });
+        }
+    })();
+
     function showToast(msg, type) {
         var el = document.getElementById('toast');
         if (!el) return;

+ 29 - 4
application/index/view/index/index.html

@@ -45,10 +45,31 @@
             color: #fff;
             display: flex;
             align-items: center;
-            justify-content: space-between;
+            justify-content: center;
+        }
+        .bar h1 {
+            position: absolute;
+            left: 0;
+            right: 0;
+            margin: 0;
+            font-size: 1.05rem;
+            font-weight: 600;
+            line-height: 1.25;
+            text-align: center;
+            pointer-events: none;
+        }
+        .bar a {
+            margin-left: auto;
+            position: relative;
+            z-index: 1;
+            color: #fff;
+            text-decoration: none;
+            font-size: 14px;
+            line-height: 1.2;
+            padding: 5px 10px;
+            border: 1px solid rgba(255,255,255,.6);
+            border-radius: 6px;
         }
-        .bar h1 { font-size: 1.05rem; margin: 0; font-weight: 600; line-height: 1.25; }
-        .bar a { color: #fff; text-decoration: none; font-size: 14px; line-height: 1.2; padding: 5px 10px; border: 1px solid rgba(255,255,255,.6); border-radius: 6px; }
         /* 订单页不显示退出;「我的」里再退出 */
         body.mproc-layout-orders .mproc-bar-logout { display: none !important; }
         .search-wrap { flex-shrink: 0; padding: 10px 12px; background: #e8f4fc; border-bottom: 1px solid #d2e7f4; width: 100%; }
@@ -506,7 +527,7 @@
         .me-card h2 { font-size: 1rem; margin: 0 0 14px; color: #222; font-weight: 600; }
         .me-row { font-size: 14px; line-height: 1.7; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
         .me-row:last-child { border-bottom: none; }
-        .me-row span { display: inline-block; min-width: 4.5em; color: #888; }
+        .me-row span { display: inline-block; min-width: 5em; color: #888; }
         .me-hint { font-size: 12px; color: #999; margin-top: 14px; line-height: 1.5; }
         .btn-me-pwd {
             display: block;
@@ -850,7 +871,11 @@
     <div class="me-card">
         <div class="me-row"><span>账号</span>{$mprocProfile.account|default=''|htmlentities}</div>
         <div class="me-row"><span>姓名</span>{$mprocProfile.contact_name|default=''|htmlentities}</div>
+        {if $mprocIsAdmin}
         <div class="me-row"><span>部门</span>{$mprocProfile.department|default=''|htmlentities}</div>
+        {else/}
+        <div class="me-row"><span>公司名称</span>{$mprocProfile.company_name|default=''|htmlentities}</div>
+        {/if}
         <div class="me-row"><span>手机号</span>{$mprocProfile.phone|default=''|htmlentities}</div>
         <div class="me-row"><span>邮箱</span>{$mprocProfile.email|default=''|htmlentities}</div>
         {if $mprocCanChangePwd}