广丰卷烟厂数采质量分析系统
zhuguifei
2026-03-02 80ff784bf60637cd348ae665fc907f7b1e527dd8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<script setup lang="ts">
import { reactive } from 'vue';
import { NButton } from 'naive-ui';
import { useLoading } from '@sa/hooks';
import { fetchUpdateUserPassword, fetchUpdateUserProfile } from '@/service/api/system';
import { useAuthStore } from '@/store/modules/auth';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import OnlineTable from './modules/online-table.vue';
import SocialCard from './modules/social-card.vue';
import UserAvatar from './modules/user-avatar.vue';
 
defineOptions({
  name: 'UserCenter'
});
 
const authStore = useAuthStore();
const { userInfo } = authStore;
 
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
 
const {
  formRef: profileFormRef,
  validate: profileValidate,
  restoreValidation: profileRestoreValidation
} = useNaiveForm();
const {
  formRef: passwordFormRef,
  validate: passwordValidate,
  restoreValidation: passwordRestoreValidation
} = useNaiveForm();
const { createRequiredRule, patternRules } = useFormRules();
 
type ProfileModel = Api.System.UserProfileOperateParams;
type PasswordModel = Api.System.UserPasswordOperateParams & { confirmPassword: string };
 
const profileModel: ProfileModel = reactive(createDefaultProfileModel());
const passwordModel: PasswordModel = reactive(createDefaultPasswordModel());
 
function createDefaultProfileModel(): ProfileModel {
  return {
    nickName: userInfo.user?.nickName || '',
    email: userInfo.user?.email || '',
    phonenumber: userInfo.user?.phonenumber || '',
    sex: userInfo.user?.sex || '0'
  };
}
 
function createDefaultPasswordModel(): PasswordModel {
  return {
    oldPassword: '',
    confirmPassword: '',
    newPassword: ''
  };
}
 
type ProfileRuleKey = Extract<keyof ProfileModel, 'nickName' | 'email' | 'phonenumber' | 'sex'>;
type PasswordRuleKey = Extract<keyof PasswordModel, 'oldPassword' | 'newPassword' | 'confirmPassword'>;
 
const profileRules: Record<ProfileRuleKey, App.Global.FormRule> = {
  nickName: createRequiredRule('昵称不能为空'),
  email: { ...patternRules.email, required: true },
  phonenumber: { ...patternRules.phone, required: true },
  sex: createRequiredRule('性别不能为空')
};
 
const passwordRules: Record<PasswordRuleKey, App.Global.FormRule> = {
  oldPassword: createRequiredRule('旧密码不能为空'),
  confirmPassword: createRequiredRule('确认密码不能为空'),
  newPassword: createRequiredRule('新密码不能为空')
};
 
async function updateProfile() {
  await profileValidate();
  startBtnLoading();
  const { error } = await fetchUpdateUserProfile(profileModel);
  if (!error) {
    window.$message?.success('更新成功');
    // 更新本地用户信息
    if (userInfo.user) {
      Object.assign(userInfo.user, profileModel);
      profileRestoreValidation();
    }
  }
  endBtnLoading();
}
 
async function updatePassword() {
  await passwordValidate();
  if (passwordModel.newPassword !== passwordModel.confirmPassword) {
    window.$message?.error('两次输入的密码不一致');
    return;
  }
  startBtnLoading();
  const { oldPassword, newPassword } = passwordModel;
  const { error } = await fetchUpdateUserPassword({ oldPassword, newPassword });
  if (!error) {
    window.$message?.success('密码修改成功');
    // 清空表单
    Object.assign(passwordModel, createDefaultPasswordModel());
    passwordRestoreValidation();
  }
  endBtnLoading();
}
</script>
 
<template>
  <div class="flex gap-16px">
    <!-- 个人信息卡片 -->
    <NCard title="个人信息" class="w-360px shadow-sm">
      <div class="flex-x-center flex-wrap gap-24px">
        <div class="flex-center flex-col gap-16px">
          <div class="relative">
            <UserAvatar />
          </div>
          <div class="text-18px font-medium">{{ userInfo.user?.nickName }}</div>
          <div class="text-14px text-gray-500">{{ userInfo.user?.userName }}</div>
        </div>
        <NDescriptions :column="1" label-placement="left" label-width="120px">
          <NDescriptionsItem label="手机号码">
            <div class="text-14px">{{ userInfo.user?.phonenumber }}</div>
          </NDescriptionsItem>
          <NDescriptionsItem label="用户邮箱">
            <div class="text-14px">{{ userInfo.user?.email }}</div>
          </NDescriptionsItem>
          <NDescriptionsItem label="所属部门">
            <div class="text-14px">{{ userInfo.user?.deptName }}</div>
          </NDescriptionsItem>
          <NDescriptionsItem label="所属角色">
            <NSpace>
              <NTag v-for="role in userInfo.user?.roles" :key="role.roleId" type="primary" size="small">
                {{ role.roleName }}
              </NTag>
            </NSpace>
          </NDescriptionsItem>
          <NDescriptionsItem label="创建日期">
            <div class="text-14px">{{ userInfo.user?.createTime }}</div>
          </NDescriptionsItem>
        </NDescriptions>
      </div>
    </NCard>
 
    <!-- 基本资料卡片 -->
    <NCard title="基本资料" class="w-full overflow-x-auto shadow-sm">
      <NTabs type="line" animated class="h-full" s>
        <NTabPane name="userInfo" tab="基本资料">
          <NForm
            ref="profileFormRef"
            :model="profileModel"
            :rules="profileRules"
            label-placement="left"
            label-width="100px"
            class="mt-16px max-w-520px"
          >
            <NFormItem label="昵称" path="nickName">
              <NInput v-model:value="profileModel.nickName" placeholder="请输入昵称" />
            </NFormItem>
            <NFormItem label="邮箱" path="email">
              <NInput v-model:value="profileModel.email" placeholder="请输入邮箱" />
            </NFormItem>
            <NFormItem label="手机号" path="phonenumber">
              <NInput v-model:value="profileModel.phonenumber" placeholder="请输入手机号" />
            </NFormItem>
            <NFormItem label="性别" path="sex">
              <NRadioGroup v-model:value="profileModel.sex">
                <NRadio value="0">男</NRadio>
                <NRadio value="1">女</NRadio>
              </NRadioGroup>
            </NFormItem>
            <NFormItem class="flex items-center justify-end">
              <NButton class="ml-20px w-80px" type="primary" :loading="btnLoading" @click="updateProfile">
                <template #icon>
                  <SvgIcon icon="ic:outline-save" class="size-24px" />
                </template>
                保存
              </NButton>
            </NFormItem>
          </NForm>
        </NTabPane>
        <NTabPane name="updatePwd" tab="修改密码">
          <NForm
            ref="passwordFormRef"
            :model="passwordModel"
            :rules="passwordRules"
            label-placement="left"
            label-width="100px"
            class="mt-16px max-w-520px"
          >
            <NFormItem label="旧密码" path="oldPassword">
              <NInput
                v-model:value="passwordModel.oldPassword"
                type="password"
                placeholder="请输入旧密码"
                show-password-on="click"
              />
            </NFormItem>
            <NFormItem label="新密码" path="newPassword">
              <NInput
                v-model:value="passwordModel.newPassword"
                type="password"
                placeholder="请输入新密码"
                show-password-on="click"
              />
            </NFormItem>
            <NFormItem label="确认密码" path="confirmPassword">
              <NInput
                v-model:value="passwordModel.confirmPassword"
                type="password"
                placeholder="请再次输入新密码"
                show-password-on="click"
              />
            </NFormItem>
            <NFormItem class="flex items-center justify-end">
              <NButton class="ml-20px w-120px" type="primary" :loading="btnLoading" @click="updatePassword">
                <template #icon>
                  <SvgIcon icon="ic:outline-key" class="size-24px" />
                </template>
                修改密码
              </NButton>
            </NFormItem>
          </NForm>
        </NTabPane>
        <NTabPane name="social" tab="第三方应用">
          <SocialCard />
        </NTabPane>
        <NTabPane name="online" tab="在线设备">
          <div class="h-full">
            <OnlineTable />
          </div>
        </NTabPane>
      </NTabs>
    </NCard>
  </div>
</template>
 
<style scoped>
.shadow-sm {
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
 
:deep(.n-tabs-pane-wrapper),
:deep(.n-tab-pane) {
  height: 100% !important;
}
</style>