B/S项目设计中应注意的思考

在介绍这篇文章之前,先普及一下B/S项目的基础知识,如你已经熟悉可以略过下面第一段文字。
这里说的B/S项目是指浏览器(Browser)和服务器(server)的软件,通常是基于互联网的项目也有基于局域网的项目,总之软件所依赖的载体是浏览器和服务器。不论是现在谈及的toB项目、还是toC项目或是toVC项目大多都是基于B/S的模式构建的。

通常B/S项目设计采用的的版式都是工字布局,如下图所示,有头部和主体部分,主体部分又分为左右结构,然后左边是一排功能菜单,右边展示菜单跳转的内容,头部通常显示企业logo或其他的一些用户信息。

为何通常采用这种布局模式?我也一时说不上来,不过不可否认的是这种版式是UI设计中最符合人们操作习惯的一种版式,细想也对,我们基本上使用的绝大多数的软件,都是左边一排功能菜单或者工具,右边显示主题内容或者画布,例如Adobe公司的软件PS、AI等,而工字布局的版式我发现与邮箱的版式最为相似。

今天想说的是,在以工字布局版式设计的时候,Tab的应用一定要考虑和应用好产品的需求和技术实现。举个例子,下图是网易邮箱的一个截图

在所有tab栏中,是有固定栏位和活动栏位的区分的,活动栏位是带关闭按钮的,就是tab上的一个小叉叉。通常是显示用户关注的信息,例如某封邮件内容,活动栏位tab可以随着用户操作增减栏位,而且活动栏位信息一定是加载完毕后显示的,只起到切换显示和关闭显示的作用,不会发起请求数据,数据是在显示之前就请求完毕了的,这种数据含量通常小。

而固定栏位的tab在显示页面内容时是会请求数据的,而且栏位的位置不会随着用户操作增加和减少,一定是固定在某个位置,通常是展示左边菜单内容的,这种数据含量大,你可理解它是一个路由,如上图所示,还会看到加载数据的等待动画。

那么在设计的时候,一定要考虑清楚,哪些内容是固定tab栏位显示,哪些是活动 tab栏位显示,这两种不同的栏位,不仅在用户体验上有明显差异,在功能技术实现上也是大相庭径。设计功能的实现,在一开始就要很明确的划分出这两种不同的栏位,如果没有这种预前的思考就等于没有梳理用户需求,不仅不能构建良好的用户体验而且开发起来也容易陷入逻辑怪圈。

网站快速成型工具elementUI是一套设计很优美的产品,也是现在VUE生态下使用最为频繁的一个UI框架,基本上每个组件都是考虑到了全局分辨率的显示效果,因此在使用这套UI组件的时候,是完全可以向下兼容的,最近使用的这套框架实现了一个活动栏位的tab的增减效果。通过父子组件的传参来实现。

elementui表单在vue中的校验

vue使用rule属性来校验输入的内容,rule属性里对应的每一个规则都可以通过validator来设置校验的方法变量。
比如新建一个外部的校验文件validate.js

//判断中文姓名
export function validateName(str) {
  const reg = /^[\u4E00-\u9FA5]{2,4}$/
  return reg.test(str)
}

//判断年龄
export function validateAge(str) {
  const reg = /^1[6-9]$|^[2-9]\d$|^1\d{2}$/
  return reg.test(str)
}

//判断手机号
export function validateAccountNumber(str) {
  const reg = /^1(3|4|5|7|8|9)\d{9}$/
  return reg.test(str)
}

在要使用的校验的SPA里引入这个文件的三个方法名

import { validateAccountNumber,validateName,validateAge } from "../components/validate";

我们要使用elementui的模板如下

<template>
  <div class="app-container">
    <el-tabs v-model="activeName">
        <!-- 表单 -->
        <el-form ref="rulesForm" :rules="formRules" :model="rulesForm">
          <el-form-item label="姓名:" prop="userName">
            <el-input v-model="rulesForm.userName" placeholder="请输入内容" />
          </el-form-item>
          <el-form-item label="年龄:" prop="userAges">
            <el-input v-model="rulesForm.userAges"  maxlength="50"/>
          </el-form-item>
          <el-form-item label="手机号:" prop="accountNumber">
            <el-input v-model="rulesForm.accountNumber" maxlength="50"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="onSubmit('rulesForm')">保存</el-button>
            <el-button @click="cancel">取消</el-button>
          </el-form-item>
        </el-form>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

其他js文件如下,trigger: “blur”是焦点离开时的判断,如果要想用户点击提交按钮判断某条规则,可以直接trigger: “click”就可以了

  const validatorAccountNumber = (rule, value, callback) => {
    if (!value) {
      return callback(new Error("请输手机号"));
    } else {
      if (validateAccountNumber(value)) {
        callback();
      } else {
        return callback(new Error('号码格式不正确'))
      }
    }
  };
  const validatorName = (rule, value, callback) => {
    if (!value) {
      return callback(new Error("请输入姓名"));
    } else {
      if (validateName(value)) {
        callback();
      } else {
        return callback(new Error('姓名格式不对'))
      }
    }
  };
  const validatorAge = (rule, value, callback) => {
    if (!value) {
      return callback(new Error("请输入年龄"));
    } else {
      if (validateAge(value)) {
        callback();
      } else {
        return callback(new Error('年龄格式不对'))
      }
    }
  };
  export default {
    name: "rules",
    data() {
    return {
      activeName: "rulesPane",
      rulesForm: {

      },
      //   表单验证
      formRules: {
        userName: [
          {
            required: true,
            validator: validatorName,
            trigger: "click"
          }
        ],
        userAges: [
          {
            required: true,
            validator: validatorAge,
            trigger: "blur"
          }
        ],
        accountNumber: [
          {
            required: true,
            validator: validatorAccountNumber,
            trigger: "blur"
          }
        ],
      }
    };
  },
  created() {},
  mounted() {},
  methods: {
    handleClick(tab) {

    },
    // 取消
    cancel() {

    },
    // 保存
    onSubmit(formName) {
      this.$refs[formName].validate(valid => {
        if (valid) {
        console.log("success submit!!");
      } else {
        console.log("error submit!!");
        return false;
      }
    });
  }
  }
  };

rate-ball升级1.0.3

支持单页多个流量球引用

安装

$npm install rate-ball

在package.json文件里发现如下图红线所示,表示安装成功。

模板设置,不同的流量球对应到不同的数据

组件引入和数据设置,如果数据是一个数组的话,模板也可以用v-for来实现,只要保证每个id不一样就可以了,其他可以自定义

使用:在mounted周期里调用,把不同的流量球id作为数组传入到组件里,就可以正常使用了。

分享一个自定义流量环

今天抽空做了一个流量环,支持单页多次引用。

可以自定义流量环大小、颜色、字体大小和颜色以及流量值。

安装使用

$npm install rate-hoop

如下图在package.json中发现rate-hoop表示安装成功

模板设置,包括流量环的id和流量环尺寸、字体大小、颜色、流量环背景色等信息。每个值都可以对应到数据里。

引用组件和数据设置,多个流量环的id必须不同,其他都可以自定义,数据的键值都可以自定义,只需要在模板中一一对应就行了。

使用:在mounted周期里应用,多个流量环只需要把id值传进去就可以了,数据都是可以自定义的,只要模板和引用都能一一对应上就可以了。

如果对你有帮助,请关注并收藏本站,后续还会分享更多。

Vuex购物车逻辑实现

父子组件通过vuex来实现购物车的逻辑,购物车商品循环列表是父组件,底部的金额和全选是子组件,选择不同的商品,累加总金额,当选择全部商品后,全选状态也为勾选状态。

在$store里设置全选状态,和总金额状态

商品单选逻辑,当选择的长度和商品总长度一样时,$store里全选状态设置为true

直接设置全选的checked属性为store里状态全选值,当为1时,全选勾选,为0时不勾选

全选逻辑,子组件向父组件传值,通过$emit方法传值checked

在组件里写一个属性方法,让父组件接收传过来的值checked

在methods里写全选的逻辑,当接收过来的值为checked时,所有的项目金额总额和所有项目选中,否则就金额为0,所有项目全不选中。

最新文件已更新到github

分享一个流量球

下午花了点时间做了一个流量球工具,纯css和html和js实现的一个vue小插件,无任何依赖,并向下兼容,点下图可以看效果。

安装$npm install rate-ball,在package.json文件里发现黄色线所示表示安装成功。

使用非常简单,只需要在模板里设置一个id为rate-ball的元素,并且设置好流量值是data-rb-value属性就可以了,参考下图配置,注意红色线条部分,请放在mounted周期里渲染dom

 

vue-checkedbox实现单选、组选和全选

优化了一下组件vue-checkedbox,升级1.0.3
选框UI样式调整为通用型

测试工程单页。单选逻辑:如果是该组最后一个则该组的组选被选中,如果是所有最后一个则全选被选中。取消选中则该组组选和全选都被取消。

组选逻辑:该组的子项全部被选中或者全部取消被选中,如果是所有最后一个组都被选中则全选被选中。取消选中则全选也取消。

全选逻辑是页面内所有项全部选中或全部取消选中。

每个点击操作都会同步修改数据和样式。主要逻辑都在测试工程单页里。

vue-checkedbox的组选功能

优化了一下组件vue-checkedbox,升级1.0.2
实现单选和组选的功能,同时data()数据实现真实对接。
举例:使用以下模板

<template>
  <div id="app">
    <div class="fruits">
      <div class="groupitems">
          <span v-for="item in fruits.groupitems" @click="checked($event)"  class="vuecheckedbox" :data-id="item.id" :data-value="item.value" :name="item.checked" v-model="
          item.checked">
            <input type="checkbox" :checked="item.checked" v-model="item.checked" :value="item.value"/>{{item.value}}
          </span>
      </div>
      <span class="groupcheck" @click="groupchecked($event)" :data-id="fruits.groupcheck.id" :data-value="fruits.groupcheck.value" :name="fruits.groupcheck.checked" v-model="fruits.groupcheck.checked">
        <input type="checkbox" :checked="fruits.groupcheck.checked"  v-model="fruits.groupcheck.checked" :value="fruits.groupcheck.value"/>
      </span>{{fruits.groupcheck.value}}
    </div>

  </div>
</template>

数据结构参看以下图

在methods里添加方法
单选的方法

checked:function(){
      let classlist = event.currentTarget.parentNode.parentNode.getElementsByClassName("groupcheck")[0].getElementsByClassName("checkbox")[0].classList;
       event.currentTarget.getElementsByClassName("checkbox")[0].classList.toggle("checked");
       if(event.currentTarget.parentNode.getElementsByClassName("checked").length==this.fruits.groupitems.length){
          this.fruits.groupcheck.checked=1;
         classlist.add("checked")
       }else{
         this.fruits.groupcheck.checked=0;
         classlist.remove("checked")
       }
    }

组选的方法

groupchecked:function(){
      event.currentTarget.getElementsByClassName("checkbox")[0].classList.toggle("checked");
      let groupitems= event.currentTarget.parentNode.getElementsByClassName("groupitems")[0];
      if(groupitems.getElementsByClassName("checked").length!=this.fruits.groupitems.length){
        for(let i=0;i<=this.fruits.groupitems.length-1;i++){
          let ic = this.fruits.groupitems[i].checked;
          if(ic!=1){
            ic=1;
            groupitems.getElementsByClassName("checkbox")[i].classList.add("checked")
          }
        }
      }else{
        for(let i=0;i<=this.fruits.groupitems.length-1;i++){
          this.fruits.groupitems[i].checked=0;
          groupitems.getElementsByClassName("checkbox")[i].classList.remove("checked")
        }
      }
    }

实现结果

引用组件后

import vueCheckedbox from 'vue-checkedbox'

依然还是在mounted里调用

 mounted: function () {
    vueCheckedbox.vc();
  }