Vue.js 从Vuex存储的本地副本更新数据

Vue.js 从Vuex存储的本地副本更新数据,vue.js,vuejs2,vuex,Vue.js,Vuejs2,Vuex,我正在实现一个用户配置文件编辑页面,该页面最初由从vuex存储加载的数据组成。然后,用户可以自由编辑其数据,并最终将其存储在存储区中 由于用户也可以单击“取消”按钮恢复到其原始状态,因此我决定创建从存储中获取的用户数据的“本地”视图副本。这些数据将保存在视图中,一旦用户按下save,它们将保存在存储中 视图如下所示: <template class="user-profile"> <v-form> <template v-if="profile.avat

我正在实现一个用户配置文件编辑页面,该页面最初由从
vuex
存储加载的数据组成。然后,用户可以自由编辑其数据,并最终将其存储在存储区中

由于用户也可以单击“取消”按钮恢复到其原始状态,因此我决定创建从存储中获取的用户数据的“本地”视图副本。这些数据将保存在视图中,一旦用户按下save,它们将保存在存储中

视图如下所示:

<template class="user-profile">
  <v-form>
    <template v-if="profile.avatar">
      <div class="text-center">
        <v-avatar width="120" height="120">
          <img
            :src="profile.avatar"
            :alt="profile.firstname"
          >
        </v-avatar>
      </div>
    </template>
    <div class="text-center mt-4">
      <v-btn
        color="primary"
        dark
        @click.stop="showImageDialog=true"
      >
        Change Image
      </v-btn>
    </div>
    <v-row>
      <v-col>
        <v-text-field
          label="First name"
          single-line
          disabled
          v-model="profile.firstname"
        ></v-text-field>
      </v-col>
      <v-col>
        <v-text-field
          label="Last name"
          single-line
          disabled
          v-model="profile.lastname"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-text-field
      label="Email"
      single-line
      v-model="profile.email"
    ></v-text-field>
    <v-text-field
      id="title"
      label="Title"
      single-line
      v-model="profile.title"
    ></v-text-field>
    <v-textarea
      no-resize
      clearable
      label="Biography"
      v-model="profile.bio"
    ></v-textarea>
    <v-dialog
      max-width="500"
      v-model="showImageDialog"
    >
      <v-card>
        <v-card-title>
          Update your profile picture
        </v-card-title>
        <v-card-text>
          <v-file-input @change="setImage" accept="image/*"></v-file-input>
          <template v-if="userAvatarExists">
            <vue-cropper
              ref="cropper"
              :aspect-ratio="16 / 9"
              :src="profile.avatar"
            />
          </template>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="green darken-1"
            text
            @click="showImageDialog=false"
          >
            Cancel
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="uploadImage"
          >
            Upload
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <div class="mt-8">
      <v-btn @click="onUpdateUser">Update</v-btn>
    </div>
  </v-form>
</template>

<script>
  import { mapGetters, mapActions } from 'vuex'
  import VueCropper from 'vue-cropperjs';
  import 'cropperjs/dist/cropper.css';

  export default {
    components: { VueCropper},

    mounted() {
      this.profile = this.getUserProfile ? this.getUserProfile : {}
    },
    data() {
      return {
        profile: {},
        avatar: null,
        userAvatarExists: false,
        showImageDialog: false,
      }
    },
    watch: {
      getUserProfile(newData){
        this.profile = newData;
      },
      deep: true
    },
    computed: {
      ...mapGetters({
        getUserProfile: 'user/me',
      })
    },
    methods: {
      ...mapActions({
        storeAvatar: 'user/storeAvatar',
        updateUser: 'user/update'
      }),
      onUpdateUser() {
        const data = {
          id: this.profile.id,
          email: this.profile.email,
          title: this.profile.title,
          bio: this.profile.bio,
          avatar: this.profile.avatar,
        }
        this.updateUser(data)
      },
      uploadImage() {
        this.$refs.cropper.getCroppedCanvas().toBlob((blob => {
          this.storeAvatar(blob).then((filename => {
            this.profile.avatar = filename.data
            this.$refs.cropper.reset()
          }));
          this.showImageDialog = false
        }));
      },
      setImage(file) {
        this.userAvatarExists = true;
        if (file.type.indexOf('image/') === -1) {
          alert('Please select an image file');
          return;
        }
        if (typeof FileReader === 'function') {
          const reader = new FileReader();
          reader.onload = (event) => {
            this.$refs.cropper.replace(event.target.result);
          };
          reader.readAsDataURL(file);
        } else {
          alert('Sorry, FileReader API not supported');
        }
      }
    }
  }
</script>

改变形象
更新您的个人资料图片
取消
上传
更新
从“vuex”导入{MapGetter,mapActions}
从“vue cropperjs”导入VueCropper;
导入“croperjs/dist/croper.css”;
导出默认值{
组件:{VueCropper},
安装的(){
this.profile=this.getUserProfile?this.getUserProfile:{}
},
数据(){
返回{
档案:{},
阿凡达:空,
用户:错,
showImageDialog:false,
}
},
观察:{
getUserProfile(新数据){
this.profile=newData;
},
深:是的
},
计算:{
…地图绘制者({
getUserProfile:'user/me',
})
},
方法:{
…映射操作({
storeAvatar:'用户/storeAvatar',
updateUser:'用户/更新'
}),
onUpdate用户(){
常数数据={
id:this.profile.id,
电子邮件:this.profile.email,
标题:this.profile.title,
bio:this.profile.bio,
阿凡达:这个.profile.avatar,
}
this.updateUser(数据)
},
上传图像(){
这是.$refs.crapper.getcrappedcanvas().toBlob((blob=>{
这个.storeAvatar(blob).then((文件名=>{
this.profile.avatar=filename.data
这是。$refs.crapper.reset()
}));
this.showImageDialog=false
}));
},
setImage(文件){
this.userAvatarExists=true;
if(file.type.indexOf('image/')=-1){
警报(“请选择图像文件”);
返回;
}
if(文件读取器的类型=='function'){
const reader=new FileReader();
reader.onload=(事件)=>{
这是.refs.croper.replace(event.target.result);
};
reader.readAsDataURL(文件);
}否则{
警报(“对不起,不支持FileReader API”);
}
}
}
}
议题/问题

  • 从代码中可以看出,在用户更改其配置文件后
    图片,图像应基于
    v-if=“profile.avatar”
    。问题是
    profile.avatar
    是在
    uploadImage
    功能中设置的 模板未看到此更改,并且未渲染任何图像。 但是,如果我更改代码,使
    profile.avatar
    变为 只要
    avatar
    (它不再在
    配置文件
    对象中) 模板开始查看更改并渲染图像 正确地为什么会这样?这和做一个决定有关系吗 是否在
    监视功能中从存储中复制
  • 一般来说,将个人资料作为本地文件保存是一种好方法吗 查看状态,还是应该将其存储在vuex存储中,即使 它只是一个临时数据
  • 正如您在
    mounted
    功能中看到的,我正在设置
    配置文件
    基于
    getUserProfile
    getter的值。这是因为
    watch
    功能在切换时似乎不会再次调用 路线。还有别的办法吗

  • 问题在于数据属性的反应性

    您已经使用了配置文件作为对象,默认情况下,它没有任何属性,如头像或名字,它只是空的

    在vue js中,如果您要声明一个对象,那么声明中提到的任何键都只是反应性的一部分。一旦概要文件中的键发生更改,它将重新渲染模板

    但仍然可以使用$set向数据属性对象添加新属性

    让我们假设在您声明的数据中 配置文件:{}

    如果您想在运行时将化身设置为新的被动属性,请使用

    this.$set(this.profile, key, value)
    
    那是

    this.$set(this.profile, avatar, imageData)
    
    在上面的代码中,setIuploadImage函数

    uploadImage() {
            var self = this;
            self.$refs.cropper.getCroppedCanvas().toBlob((blob => {
              self.storeAvatar(blob).then((filename => {
                self.$set(self.profile, "avatar", filename.data)
                self.$refs.cropper.reset()
              }));
              self.showImageDialog = false
            }));
          },
    
    这在vuejs中的arrow函数中不起作用,所以只需将this保存在另一个变量“self”中,并在arrow函数中使用

    同样在mounted函数中,如果this.getUserProfile返回空对象,则根据javascript,空对象总是真实的,直接将对象分配给概要文件不会使对象反应

    mounted() {
          this.profile = this.getUserProfile ? this.getUserProfile : {}
        },
    
    上面的代码可以写成


    “阿凡达”道具是否存在于您调用getUserProfile时得到的配置文件中?是的,它确实存在,似乎可以工作,谢谢您的解释!:)最后一件事,您认为在“挂载”功能中更改路由时,这是设置配置文件的正确位置吗?有不同的方法更新您的数据。这完全取决于你的要求
    mounted() {
      if (this.getUserProfile && Object.keys(this.getUserProfile).length) {
        var self = this;
        Object.keys(this.getUserProfile).map(key => {
          self.$set(self.profile, key, self.getUserProfile[key])
        });
      } else {
        this.profile = {};
      }
    }