RuHu.Xu 1 anno fa
parent
commit
d117665dac

+ 148 - 37
src/components/Dialog/src/Dialog.vue

@@ -1,6 +1,8 @@
 <script lang="ts" setup>
 import { propTypes } from '@/utils/propTypes'
 import { isNumber } from '@/utils/is'
+import { Close } from '@element-plus/icons-vue'
+
 defineOptions({ name: 'Dialog' })
 
 const slots = useSlots()
@@ -58,40 +60,24 @@ const dialogStyle = computed(() => {
 </script>
 
 <template>
-  <ElDialog
-    v-bind="getBindValue"
-    :close-on-click-modal="true"
-    :fullscreen="isFullscreen"
-    :width="width"
-    destroy-on-close
-    lock-scroll
-    draggable
-    class="com-dialog"
-    :show-close="false"
-  >
+  <ElDialog v-bind="getBindValue" :close-on-click-modal="true" :fullscreen="isFullscreen" :width="width"
+    destroy-on-close lock-scroll draggable class="dialog" :show-close="false">
     <template #header="{ close }">
-      <div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
-        <slot name="title">
-          {{ title }}
-        </slot>
-        <div
-          class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between"
-        >
-          <Icon
-            v-if="fullscreen"
-            class="is-hover mr-10px cursor-pointer"
-            :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
-            color="var(--el-color-info)"
-            hover-color="var(--el-color-primary)"
-            @click="toggleFull"
-          />
-          <Icon
-            class="is-hover cursor-pointer"
-            icon="ep:close"
-            hover-color="var(--el-color-primary)"
-            color="var(--el-color-info)"
-            @click="close"
-          />
+      <div class="my-header">
+        <div class="my-header-left">
+          <slot name="title">
+            {{ title }}
+          </slot>
+        </div>
+        <div class="my-header-right">
+          <span @click="toggleFull">
+            <Icon :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" />
+          </span>
+          <span @click="close">
+            <el-icon>
+              <Close />
+            </el-icon>
+          </span>
         </div>
       </div>
     </template>
@@ -105,15 +91,139 @@ const dialogStyle = computed(() => {
     </template>
   </ElDialog>
 </template>
+<style type="text/css">
+.el-dialog__body {
+  padding: unset;
+}
+
+.dialog .el-card__body {
+  padding: 0 20px 20px 0;
+}
+
+.dialog .el-dialog__headerbtn {
+  top: 0px;
+  line-height: 60px;
+}
+
+.dialog .el-dialog__headerbtn:hover {
+  background-color: #ef6b6b;
+}
+
+.dialog .el-dialog__headerbtn:hover .el-dialog__close {
+  color: #fff;
+}
+
+.dialog .el-dialog__header {
+  padding: 0;
+  margin: 0
+}
+
+.el-tabs--left .el-tabs__item.is-left {
+  justify-content: center;
+}
+
+.el-tabs--left .el-tabs__header.is-left {
+  margin-right: 0;
+  width: 100%;
+  text-align: center;
+
+}
+
+.el-tabs__nav {
+  width: 100%;
+  justify-content: center;
+  /* justify-content:unset */
+}
+
+.el-tabs--left .el-tabs__nav-wrap.is-left {
+  margin: 0
+}
+</style>
+
+<style lang="scss" scoped>
+.left {
+  width: 106px;
+  float: left
+}
 
-<style lang="scss">
+.child-tabs {
+  border-top: 2px solid #e4e7ef;
+  margin-top: -7px;
+}
+
+
+
+::v-deep .child-tabs .el-tabs__item {
+  width: 106px;
+  justify-content: center;
+}
+
+
+.right {
+  padding: 10px 0 60px;
+  border-left: 2px solid #e4e7ee;
+  margin-left: -2px;
+  float: left;
+  width: calc(100% - 120px);
+  padding-left: 10px;
+  max-height: 600px;
+  min-height: 600px;
+  overflow: auto;
+  position: relative;
+}
+
+.my-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 2px solid #e4e7ee;
+
+  &-left {
+    // font-weight: bold;
+    // font-size: 1rem;
+    padding: 1rem;
+    // padding-bottom: 10px
+  }
+
+  &-right {
+    span {
+      width: 55px;
+      height: 55px;
+      display: inline-block;
+      line-height: 55px;
+      text-align: center;
+      cursor: pointer;
+    }
+
+    span:first-child:hover {
+      background-color: #f6f6f6;
+      // color: white
+    }
+
+    span:last-child:hover {
+      background-color: #ef6b6b;
+      color: white
+    }
+  }
+}
+
+.footer {
+  display: flex;
+  justify-content: flex-end;
+  border-top: 1px solid var(--el-border-color);
+  padding: 10px;
+  div {
+    margin-right: 10px;
+  }
+}
+</style>
+<!-- <style lang="scss">
 .com-dialog {
   .#{$elNamespace}-overlay-dialog {
     display: flex;
     justify-content: center;
     align-items: center;
   }
-
   .#{$elNamespace}-dialog {
     margin: 0 !important;
 
@@ -125,10 +235,11 @@ const dialogStyle = computed(() => {
     }
 
     &__body {
-      padding: 15px !important;
+      // padding: 15px !important;
     }
 
     &__footer {
+      padding: 10px;
       border-top: 1px solid var(--el-border-color);
     }
 
@@ -137,4 +248,4 @@ const dialogStyle = computed(() => {
     }
   }
 }
-</style>
+</style> -->

+ 14 - 11
src/layout/components/UserInfo/src/UserInfo.vue

@@ -6,6 +6,10 @@ import { useDesign } from '@/hooks/web/useDesign'
 import avatarImg from '@/assets/imgs/avatar.gif'
 import { useUserStore } from '@/store/modules/user'
 import { useTagsViewStore } from '@/store/modules/tagsView'
+import { useAppStore } from '@/store/modules/app'
+import ChangeAllInfo from '@/views/Profile/components/ChangeAllInfo.vue'
+const appStore = useAppStore()
+const mobile = computed(() => appStore.getMobile)
 
 defineOptions({ name: 'UserInfo' })
 
@@ -42,12 +46,14 @@ const loginOut = () => {
     })
     .catch(() => {})
 }
+const ChangeAllInfoRef = ref()
 const toProfile = async () => {
-  push('/user/profile')
-  
-}
-const toDocument = () => {
-  window.open('https://doc.iocoder.cn/')
+  // 如果是手机端,则跳转到移动端的页面
+  if(mobile.value){
+    push('/user/profile')
+  }else{
+    ChangeAllInfoRef.value?.open()
+  }
 }
 </script>
 
@@ -60,15 +66,11 @@ const toDocument = () => {
       </span>
     </div>
     <template #dropdown>
-      <ElDropdownMenu>
+      <ElDropdownMenu @click="toProfile">
         <ElDropdownItem>
           <Icon icon="ep:tools" />
-          <div @click="toProfile">{{ t('common.profile') }}</div>
+          <div >{{ t('common.profile') }}</div>
         </ElDropdownItem>
-        <!-- <ElDropdownItem>
-          <Icon icon="ep:menu" />
-          <div @click="toDocument">{{ t('common.document') }}</div>
-        </ElDropdownItem> -->
         <ElDropdownItem divided @click="loginOut">
           <Icon icon="ep:switch-button" />
           <div>{{ t('common.loginOut') }}</div>
@@ -76,4 +78,5 @@ const toDocument = () => {
       </ElDropdownMenu>
     </template>
   </ElDropdown>
+  <ChangeAllInfo ref="ChangeAllInfoRef"  />
 </template>

+ 1 - 1
src/router/modules/remaining.ts

@@ -80,7 +80,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
     children: [
       {
         path: 'profile',
-        component: () => import('@/views/Profile/Index.vue'),
+        component: () => import('@/views/Profile/MobileIndex.vue'),
         name: 'Profile',
         meta: {
           canTo: true,

+ 83 - 34
src/views/Profile/Index.vue

@@ -1,52 +1,101 @@
 <template>
-  <div class="flex">
-    <el-card class="user w-1/3" shadow="hover">
-      <template #header>
-        <div class="card-header">
-          <span>{{ t('profile.user.title') }}</span>
-        </div>
-      </template>
-      <ProfileUser />
-    </el-card>
-     <el-card class="user ml-3 w-2/3" shadow="hover">
-      <template #header>
-        <div class="card-header">
-          <span>{{ t('profile.info.title') }}</span>
-        </div>
-      </template>
-      <div>
-        <el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
-          <el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
-            <BasicInfo />
-          </el-tab-pane>
-          <el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
-            <ResetPwd />
-          </el-tab-pane>
-          <!-- <el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
-            <UserSocial v-model:activeName="activeName" />
-          </el-tab-pane> -->
-        </el-tabs>
+  <Dialog v-model="dialogVisible" title="个人空间">
+    <el-card class="user" shadow="hover" style="border: none;">
+      <div @click.stop="change" class="setting">
+        <el-icon size="20" color="rgb(220 223 231)" class="mt-5px">
+          <Setting />
+        </el-icon>
       </div>
+      <ProfileUser />
     </el-card>
-  </div>
-  
+  </Dialog>
+  <ChangeAllInfo ref="ChangeAllInfoRef"  />
+
 </template>
 <script lang="ts" setup>
-import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
+import {  ProfileUser } from './components'
+import ChangeAllInfo from './components/ChangeAllInfo.vue'
+import { Setting } from '@element-plus/icons-vue'
+const ChangeAllInfoRef = ref()
+
 const dialogVisible = ref(false) // 弹窗的是否展示
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const { t } = useI18n()
 defineOptions({ name: 'Profile' })
-const activeName = ref('basicInfo')
-/** 打开弹窗 */
+/** 被父组件打开弹窗 */
 const open = async () => {
   dialogVisible.value = true
 }
+// 打开子组件弹窗
+const change = () => {
+  ChangeAllInfoRef.value?.open()
+
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+
 </script>
 <style scoped>
+/* 卡片 */
+.mobile-card {
+  background-color: white;
+  margin: 1rem;
+  padding: 0 1rem;
+  border-radius: 0.5rem;
+
+  & ::v-deep .el-row:last-child .el-col:last-child .el-form-item {
+    border-bottom: none;
+  }
+
+  & ::v-deep .el-form {
+    border-bottom: 1px solid #ebeef5;
+  }
+
+  & ::v-deep .el-form:last-child {
+    border-bottom: none;
+  }
+
+  .upload-box {
+    display: flex;
+    align-items: center;
+    padding: .5rem 0;
+  }
+
+  .mobile-card-title {
+    font-size: 1rem;
+    font-weight: 600;
+    padding: 1rem 0;
+    border-bottom: 1px solid #ebeef5;
+  }
+}
+
 .user {
+  width: 100%;
   max-height: 960px;
-  padding: 15px 20px 20px;
+  position: relative;
+
+  /* padding: 15px 20px 20px; */
+  &:hover {
+    .setting {
+      display: block;
+    }
+  }
+}
+
+.setting {
+  width: 30px;
+  top: 0px;
+  right: 0px;
+  position: absolute;
+  cursor: pointer;
+  height: 30px;
+  line-height: 30px;
+  text-align: center;
+  display: none;
+}
+
+.setting:hover {
+  background: #666
 }
 
 .card-header {
@@ -59,7 +108,7 @@ const open = async () => {
   padding: 15px !important;
 }
 
-.profile-tabs > .el-tabs__content {
+.profile-tabs>.el-tabs__content {
   padding: 32px;
   font-weight: 600;
   color: #6b778c;

+ 114 - 0
src/views/Profile/MobileIndex.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="flex">
+    <el-card class="user mobile-card" shadow="hover">
+        
+      <div class="mobile-card-title"><Icon  icon="ep:user" />{{ t('profile.user.title') }}</div>
+      <ProfileUser />
+    </el-card>
+     <!-- <el-card class="user ml-3 w-2/3" shadow="hover">
+      <template #header>
+        <div class="card-header">
+          <span>{{ t('profile.info.title') }}</span>
+        </div>
+      </template>
+      <div>
+        <el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
+          <el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
+            <BasicInfo />
+          </el-tab-pane>
+          <el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
+            <ResetPwd />
+          </el-tab-pane>
+           <el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
+            <UserSocial v-model:activeName="activeName" />
+          </el-tab-pane> 
+        </el-tabs>
+      </div>
+    </el-card> -->
+  </div>
+  
+</template>
+<script lang="ts" setup>
+import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const { t } = useI18n()
+defineOptions({ name: 'Profile' })
+const activeName = ref('basicInfo')
+/** 打开弹窗 */
+const open = async () => {
+  dialogVisible.value = true
+}
+onMounted(() => {
+  // 当子组件挂载时,改变父组件的样式
+  document.getElementsByTagName('section')[1].style.padding = '0px';
+  document.getElementsByTagName('section')[1].style.width = '100%';
+  document.getElementsByTagName('section')[1].querySelector('div').style.border = 'none';
+  document.getElementsByTagName('section')[1].style.background = '#f5f7f9';
+});
+onUnmounted(() => {
+  // 当子组件卸载时,恢复父组件的原始样式
+  document.getElementsByTagName('section')[1].style.width = 'auto';
+  document.getElementsByTagName('section')[1].style.padding = '20px';
+  document.getElementsByTagName('section')[1].style.background = 'var(--el-color-white)';
+});
+
+</script>
+<style scoped>
+/* 卡片 */
+.mobile-card{
+  background-color: white; 
+  margin: 1rem;
+  padding: 0 1rem;
+  border-radius: 0.5rem;
+  & ::v-deep .el-row:last-child .el-col:last-child .el-form-item{
+    border-bottom: none;
+  }
+  & ::v-deep .el-form{
+    border-bottom: 1px solid #ebeef5;
+  }
+  & ::v-deep .el-form:last-child{
+    border-bottom: none;
+  }
+  .upload-box{
+    display: flex;
+    align-items: center;
+    padding: .5rem 0;
+  }
+  .mobile-card-title{
+    font-size: 1rem;
+    font-weight: 600;
+    padding: 1rem 0;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    align-items: center;
+  }
+}
+.user {
+  width: 100%;
+  max-height: 960px;
+  /* padding: 15px 20px 20px; */
+}
+.user ::v-deep .el-card__body{
+  padding: unset;
+}
+.card-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+:deep(.el-card .el-card__header, .el-card .el-card__body) {
+  padding: 15px !important;
+}
+
+.profile-tabs > .el-tabs__content {
+  padding: 32px;
+  font-weight: 600;
+  color: #6b778c;
+}
+
+.el-tabs--left .el-tabs__content {
+  height: 100%;
+}
+</style>

+ 236 - 41
src/views/Profile/components/BasicInfo.vue

@@ -1,33 +1,135 @@
 <template>
-  <Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
-    <template #sex="form">
+  <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" :disabled="!isDetail"
+    :class="{ 'view': !isDetail }">
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="formData.username" placeholder="请输入用户名" class="w-80!" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="昵称" prop="nickname">
+          <el-input v-model="formData.nickname" placeholder="请输入昵称" class="w-80!" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="性别" prop="sex">
+          <el-radio-group v-model="formData.sex">
+            <el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
+            <el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="手机号" prop="mobile">
+          <el-input v-model="formData.mobile" placeholder="请输入手机号" class="w-80!" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="所在地" prop="address">
+          <el-input v-model="formData.address" placeholder="请输入所在地" class="w-80!" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="邮箱" prop="email">
+          <el-input v-model="formData.email" placeholder="请输入邮箱" class="w-80!" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <div v-if="!isDetail">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="状态" prop="status">
+            <el-select v-model="formData.status">
+              <el-option label="停用" :value="0" >停用</el-option>
+              <el-option label="在用" :value="1" >在用</el-option>
+            </el-select>
+          </el-form-item>
+
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker v-model="formData.createTime" type="datetime" format="YYYY-MM-DD HH:mm" placeholder="选择日期时间" class="w-80!" />
+            <!-- {{ formatDate(formData.createTime, 'YYYY-MM-DD HH:mm') }} -->
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="直推人" prop="brandId" />
+
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="身价" prop="brandId" />
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="积分" prop="brandId" />
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="最后登录时间" prop="brandId">
+            <el-date-picker v-model="formData.loginDate" type="datetime" format="YYYY-MM-DD HH:mm" placeholder="选择日期时间" class="w-80!" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </div>
+  </el-form>
+  <!-- <Form ref="formRef" :labelWidth="120" :rules="rules" :schema="schema">
+    <template #status="form">
+      <el-select v-model="form['status']">
+        <el-option label="在用" :value="1" >在用</el-option>
+        <el-option label="停用" :value="0" >停用</el-option>
+      </el-select>
+    </template>
+<template #sex="form">
       <el-radio-group v-model="form['sex']">
         <el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
         <el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
       </el-radio-group>
     </template>
-  </Form>
-  <div style="text-align: center">
+</Form> -->
+  <!-- <div style="text-align: center">
     <XButton :title="t('common.save')" type="primary" @click="submit()" />
     <XButton :title="t('common.reset')" type="danger" @click="init()" />
-  </div>
+  </div> -->
 </template>
 <script lang="ts" setup>
 import type { FormRules } from 'element-plus'
 import { FormSchema } from '@/types/form'
+
 import type { FormExpose } from '@/components/Form'
 import {
   getUserProfile,
   updateUserProfile,
   UserProfileUpdateReqVO
 } from '@/api/system/user/profile'
+import { formatDate } from '@/utils/formatTime'
 
 defineOptions({ name: 'BasicInfo' })
-
+const props = defineProps({
+  change: {
+    type: Boolean,
+    default: false
+  }
+})
+const isDetail = ref(false)
+// console.log(props.change)
+watch(props, (newValue, oldValue) => {
+  // console.log(newValue.change)
+  isDetail.value = newValue.change
+}, { deep: true })
+const formData = ref({});
 const { t } = useI18n()
 const message = useMessage() // 消息弹窗
 // 表单校验
 const rules = reactive<FormRules>({
+  username: [{ required: true, message: t('profile.rules.username'), trigger: 'blur' }],
   nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
   email: [
     { required: true, message: t('profile.rules.mail'), trigger: 'blur' },
@@ -46,47 +148,140 @@ const rules = reactive<FormRules>({
     }
   ]
 })
-const schema = reactive<FormSchema[]>([
-  {
-    field: 'nickname',
-    label: t('profile.user.nickname'),
-    component: 'Input'
-  },
-  {
-    field: 'mobile',
-    label: t('profile.user.mobile'),
-    component: 'Input'
-  },
-  {
-    field: 'email',
-    label: t('profile.user.email'),
-    component: 'Input'
-  },
-  {
-    field: 'sex',
-    label: t('profile.user.sex'),
-    component: 'InputNumber',
-    value: 0
-  }
-])
+
 const formRef = ref<FormExpose>() // 表单 Ref
-const submit = () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      const data = unref(formRef)?.formModel as UserProfileUpdateReqVO
-      await updateUserProfile(data)
-      message.success(t('common.updateSuccess'))
-      await init()
-    }
-  })
+
+const submit = async() => {
+  const data = unref(formData.value)
+  console.log(data)
+  await updateUserProfile(data)
+  message.success(t('common.updateSuccess'))
+  await init()
 }
 const init = async () => {
   const res = await getUserProfile()
-  unref(formRef)?.setValues(res)
+  formData.value = res
+  // console.log(formData.value)
+
 }
+defineExpose({
+  init, submit
+})
 onMounted(async () => {
   await init()
 })
 </script>
+<style lang="scss" scoped>
+/* 如果是查看状态隐藏星号 */
+.view ::v-deep .el-form-item__label::before {
+  display: none;
+}
+
+/* 查看状态下 label跟text的颜色对调 */
+.view ::v-deep .el-form-item__label {
+  color: var(--el-disabled-text-color);
+}
+
+.view ::v-deep .el-input.is-disabled .el-input__inner {
+  color: var(--el-text-color-regular);
+  -webkit-text-fill-color: var(--el-text-color-regular);
+}
+
+/* 默认状态input不显示边框 只有鼠标经过或者选中时才显示 */
+::v-deep .el-input__wrapper {
+  box-shadow: none
+}
+
+::v-deep .el-input__wrapper:hover {
+  box-shadow: 0 0 0 1px var(--el-input-hover-border-color) inset;
+}
+
+::v-deep .el-input__wrapper.is-focus {
+  box-shadow: 0 0 0 1px var(--el-input-focus-border-color, var(--el-border-color)) inset
+}
+
+/* 同理text area区域也是 */
+::v-deep .el-textarea__inner {
+  box-shadow: none
+}
+
+::v-deep .el-textarea__inner:hover {
+  box-shadow: 0 0 0 1px var(--el-input-hover-border-color) inset;
+}
+
+::v-deep .el-textarea__inner:focus {
+  box-shadow: 0 0 0 1px var(--el-input-focus-border-color) inset;
+}
+
+::v-deep .el-row {
+  border: 1px solid #ebeef5;
+  border-bottom: unset;
+}
+
+::v-deep .el-row:last-child {
+  border-bottom: 1px solid #ebeef5;
+}
+
+::v-deep .el-col {
+  border-right: 1px solid #ebeef5;
+}
+
+.el-form-item {
+  margin-bottom: 0;
+  height: 100%;
+}
+
+::v-deep(.el-form-item--default .el-form-item__label) {
+  height: auto;
+  min-height: 47px;
+  display: flex;
+  align-items: center;
+  border-right: 1px solid #ebeef5;
+}
+
+.noBorder ::v-deep(.el-form-item .el-form-item__label) {
+  border: unset;
+}
+
+::v-deep(.el-form-item--default .el-form-item__content) {
+  line-height: 47px;
+  padding: 10px;
+  word-break: break-word;
+}
+
+.mobile-elform {
+  border: 1px solid #ebeef5;
+  margin: 1rem;
+  padding-top: .5rem;
+}
+
+::v-deep .el-input.is-disabled .el-input__wrapper {
+  background-color: unset;
+  box-shadow: none;
+}
+
+::v-deep .el-form-item__error {
+  top: auto;
+  bottom: 0;
+  left: 11px;
+  font-size: 10px;
+}
+
+@media screen and (max-width: 768px) {
+  .sliderPicUrls ::v-deep .el-form-item__content {
+    margin: 0 !important;
+
+    div {
+      margin: 0 auto;
+    }
+  }
+
+  .description ::v-deep .el-form-item__content {
+    margin: 0 !important;
+  }
+}
+
+.view ::v-deep .el-upload {
+  display: none;
+}
+</style>

+ 134 - 0
src/views/Profile/components/ChangeAllInfo.vue

@@ -0,0 +1,134 @@
+<template>
+  <Dialog v-model="dialogVisible" title="个人空间">
+    
+    <div class="left">
+      <UserAvatar :img="formData.avatar"/>
+      <!-- <SPuUploadImg v-model="formData.avatar" :disabled="!basicInfoChange" /> -->
+      <el-tabs v-model="activeName" class="child-tabs"  tab-position="left"  @tab-click="handleClick">
+        <el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo" />
+        <el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd"/>
+      </el-tabs>
+    </div>
+    
+    <div class="right">
+      <BasicInfo ref="BasicInfoRef" v-if="activeName == 'basicInfo'" :change="basicInfoChange"/>
+      <ResetPwd ref="ResetPwdRef" v-if="activeName == 'resetPwd'"/>
+    </div>
+    <div style="clear: both;"></div>
+    <!-- 底部对话框操作按钮 -->
+    <template #footer>
+      <el-button  v-show="activeName == 'basicInfo' && !basicInfoChange" @click="baseInfoChange()">变动</el-button>
+      <el-button type="primary" v-show="activeName == 'basicInfo' && basicInfoChange" @click="baseInfoChange()">取消</el-button>
+      <XButton :title="t('common.save')"  v-if="activeName == 'basicInfo' && basicInfoChange"  type="primary" @click="basicInfoSubmit()" />
+      <XButton :title="t('common.reset')"  v-if="activeName == 'basicInfo' && basicInfoChange"  type="danger" @click="basicInfoInit()" />
+
+      <XButton :title="t('common.save')" v-if="activeName == 'resetPwd'" type="primary" @click="resetPwdSubmit()" />
+      <XButton :title="t('common.reset')" v-if="activeName == 'resetPwd'" type="danger" @click="resetPwdReset()" />
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import UserAvatar from './UserAvatar.vue'
+
+import { BasicInfo, ResetPwd } from './'
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
+const message = useMessage() // 消息弹窗
+const formData = ref({})
+const getUserInfo = async () => {
+  const data = await getUserProfile()
+  formData.value = data
+}
+const handleClick = (tab) => {
+  basicInfoChange.value = false
+}
+
+const basicInfoChange = ref(false)
+const BasicInfoRef = ref()
+const ResetPwdRef = ref()
+// 调用基本资料里面的函数
+const baseInfoChange = () => {
+  basicInfoChange.value = !basicInfoChange.value
+}
+const basicInfoSubmit = () => {
+  BasicInfoRef.value.submit()
+  basicInfoChange.value = false
+}
+const basicInfoInit = () => {
+  BasicInfoRef.value.init()
+}
+// 调用修改密码里面的函数和数据
+const resetPwdSubmit = () => {
+  ResetPwdRef.value.submit(ResetPwdRef.value.formRef)
+}
+const resetPwdReset = () => {
+  ResetPwdRef.value.reset(ResetPwdRef.value.formRef)
+}
+onMounted(async () => {
+  await getUserInfo()
+})
+watch(() => formData.value.avatar, (newVal, oldVal) => {
+  console.log(newVal);
+}, { deep: true });
+const { t } = useI18n()
+defineOptions({ name: 'ChangeAllInfo' })
+
+const activeName = ref('basicInfo')
+/** 打开弹窗 */
+const open = async () => {
+  dialogVisible.value = true
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+</script>
+<style scoped>
+
+.left {
+	width: 106px;
+	float: left;
+}
+.child-tabs {
+		border-top: 2px solid #e4e7ef;
+		margin-top: -4px;
+	}
+	::v-deep .child-tabs .el-tabs__item {
+		width: 106px;
+		justify-content: center;
+	}
+.right {
+	padding: 10px 0 60px 10px;
+	border-left: 2px solid #e4e7ee;
+	margin-left: -2px;
+	float: left;
+	width: calc(100% - 120px);
+}
+
+.user {
+  width: 100%;
+  max-height: 960px;
+  /* padding: 15px 20px 20px; */
+}
+.user ::v-deep .el-card__body{
+  padding: unset;
+}
+.card-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+:deep(.el-card .el-card__header, .el-card .el-card__body) {
+  padding: 15px !important;
+}
+
+.profile-tabs > .el-tabs__content {
+  padding: 32px;
+  font-weight: 600;
+  color: #6b778c;
+}
+
+.el-tabs--left .el-tabs__content {
+  height: 100%;
+}
+</style>

+ 14 - 5
src/views/Profile/components/ProfileUser.vue

@@ -1,9 +1,13 @@
 <template>
   <div>
-    <div class="text-center">
-      <UserAvatar :img="userInfo?.avatar" />
+    <div class="text-center" v-if="!mobile">
+      <UserAvatar :img="userInfo?.avatar"/>
     </div>
-    <ul class="list-group list-group-striped">
+    <div v-else class="text-center mt-7px">
+      <img :src="userInfo?.avatar" style="width: 8rem;height: 8rem;border-radius: 50%;border: 1px solid #ebeef5;"/>
+    </div>
+    <BasicInfo />
+    <!-- <ul class="list-group list-group-striped">
       <li class="list-group-item">
         <Icon class="mr-5px" icon="ep:user" />
         {{ t('profile.user.username') }}
@@ -43,14 +47,18 @@
         {{ t('profile.user.createTime') }}
         <div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
       </li>
-    </ul>
+    </ul> -->
   </div>
 </template>
 <script lang="ts" setup>
 import { formatDate } from '@/utils/formatTime'
 import UserAvatar from './UserAvatar.vue'
+import BasicInfo from './BasicInfo.vue'
 
 import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
+import { useAppStore } from '@/store/modules/app'
+const appStore = useAppStore()
+const mobile = computed(() => appStore.getMobile)
 
 defineOptions({ name: 'ProfileUser' })
 
@@ -90,10 +98,11 @@ onMounted(async () => {
   margin-bottom: -1px;
   font-size: 13px;
   border-top: 1px solid #e7eaec;
-  border-bottom: 1px solid #e7eaec;
+  /* border-bottom: 1px solid #e7eaec; */
 }
 
 .pull-right {
   float: right !important;
 }
 </style>
+<style src="@/views/MobileTab.css"  lang="scss" scoped> </style>

+ 22 - 7
src/views/Profile/components/ResetPwd.vue

@@ -9,18 +9,23 @@
     <el-form-item :label="t('profile.password.confirmPassword')" prop="confirmPassword">
       <InputPassword v-model="password.confirmPassword" strength />
     </el-form-item>
-    <el-form-item>
+    <!-- <el-form-item>
       <XButton :title="t('common.save')" type="primary" @click="submit(formRef)" />
       <XButton :title="t('common.reset')" type="danger" @click="reset(formRef)" />
-    </el-form-item>
+    </el-form-item> -->
   </el-form>
 </template>
 <script lang="ts" setup>
 import type { FormInstance, FormRules } from 'element-plus'
-
+import { useUserStore } from '@/store/modules/user'
+import { useTagsViewStore } from '@/store/modules/tagsView'
 import { InputPassword } from '@/components/InputPassword'
 import { updateUserPassword } from '@/api/system/user/profile'
+const { push, replace } = useRouter()
 
+const userStore = useUserStore()
+
+const tagsViewStore = useTagsViewStore()
 defineOptions({ name: 'ResetPwd' })
 
 const { t } = useI18n()
@@ -42,10 +47,10 @@ const equalToPassword = (_rule, value, callback) => {
 }
 
 const rules = reactive<FormRules>({
-  // oldPassword: [
-  //   { required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
-  //   { min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
-  // ],
+  oldPassword: [
+    { required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
+    { min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
+  ],
   newPassword: [
     { required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },
     { min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
@@ -60,8 +65,14 @@ const submit = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   formEl.validate(async (valid) => {
     if (valid) {
+      // 改密码
       await updateUserPassword(password.oldPassword, password.newPassword)
       message.success(t('common.updateSuccess'))
+
+      // 退出登录
+      await userStore.loginOut()
+      tagsViewStore.delAllViews()
+      replace('/login?redirect=/index')
     }
   })
 }
@@ -70,4 +81,8 @@ const reset = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   formEl.resetFields()
 }
+
+defineExpose({
+  reset,submit,formRef
+})
 </script>

+ 19 - 2
src/views/Profile/components/UserAvatar.vue

@@ -32,8 +32,25 @@ const handelUpload = async ({ data }) => {
 .change-avatar {
   img {
     display: block;
-    margin-bottom: 15px;
-    border-radius: 50%;
+    height: 100%;
+    width: auto;
+    // margin-bottom: 15px;
+    // border-radius: 50%;
+  }
+  ::v-deep .el-avatar>img{
+    height: 100%;
+    width: auto;
+  }
+  ::v-deep .user-info-head,::v-deep .user-info-head>span{
+    width: 104px ;
+    height: 60px;
+    border-radius: 0%;
+    background: white;
+  }
+  ::v-deep .user-info-head:hover::after{
+    text-align: center;
+    border-radius: 0%;
+    line-height: 60px;
   }
 }
 </style>

+ 7 - 2
src/views/mall/product/spu/form/SkuForm.vue

@@ -18,7 +18,7 @@
       </el-radio-group>
     </el-form-item>
     <!-- 多规格添加-->
-    <el-form-item v-if="!formData.specType">
+    <el-form-item v-if="!formData.specType" class="skulist">
       <SkuList ref="skuListRef" :prop-form-data="formData" :property-list="propertyList" :rule-config="ruleConfig" />
     </el-form-item>
     <el-form-item v-if="formData.specType" label="商品属性">
@@ -218,5 +218,10 @@ const generateSkus = (propertyList) => {
   skuListRef.value.generateTableData(propertyList)
 }
 </script>
-
+<style lang="scss" scoped>
+  
+  .skulist ::v-deep .el-form-item__content{
+    margin-left: 40px !important;
+  }
+</style>
 <style src="./SpuComponents.css"  lang="scss" scoped></style>

+ 0 - 1
src/views/mall/trade/order/index.vue

@@ -150,7 +150,6 @@
 							<p>{{ getDictObj(DICT_TYPE.TRADE_ORDER_STATUS, o.status)?.label }}</p>
 							<p>{{ o.user.nickname }}</p>
 							<p><span class="year">{{ timestampToY(o.createTime) }}</span>{{ timestampToMDHM(o.createTime) }}</p>
-
 						</div>
 					</div>
 				</el-card>