zwq 6 éve
szülő
commit
8cf3bd413c

+ 10 - 0
babel.config.js

@@ -0,0 +1,10 @@
+module.exports = {
+  presets: ['@vue/cli-plugin-babel/preset'],
+  plugins: [
+    [
+      'import',
+      { libraryName: 'vant', libraryDirectory: 'es', style: true },
+      'vant'
+    ]
+  ]
+};

+ 86 - 0
package.json

@@ -0,0 +1,86 @@
+{
+  "name": "happyjob-dk",
+  "version": "1.0.0",
+  "description": "Collection of happyjob-dk.",
+  "author": "neverland <chenjiahan@neverl.com>",
+  "license": "MIT",
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "core-js": "^3.4.3",
+    "vant": "^2.2.0",
+    "vue": "^2.6.10",
+    "vue-router": "^3.0.5",
+    "axios": "^0.18.0",
+    "vuex": "^3.0.1",
+    "vuex-class": "^0.3.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^4.1.1",
+    "@vue/cli-plugin-eslint": "^3.3.0",
+    "@vue/cli-service": "^4.1.1",
+    "@vue/eslint-config-standard": "^4.0.0",
+    "babel-eslint": "^10.0.1",
+    "babel-plugin-import": "^1.12.0",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.1.0",
+    "less": "^3.8.1",
+    "less-loader": "^5.0.0",
+    "vue-template-compiler": "^2.6.10"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {
+      "generator-star-spacing": "off",
+      "no-mixed-operators": 0,
+      "linebreak-style": [
+        0,
+        "error",
+        "windows"
+      ],
+      "vue/max-attributes-per-line": [
+        2,
+        {
+          "singleline": 5,
+          "multiline": {
+            "max": 1,
+            "allowFirstLine": false
+          }
+        }
+      ],
+      "vue/attribute-hyphenation": 0,
+      "vue/html-self-closing": 0,
+      "vue/component-name-in-template-casing": 0,
+      "vue/html-closing-bracket-spacing": 0,
+      "vue/singleline-html-element-content-newline": 0,
+      "vue/no-unused-components": 0,
+      "vue/multiline-html-element-content-newline": 0,
+      "vue/no-use-v-if-with-v-for": 0,
+      "vue/html-closing-bracket-newline": 0,
+      "vue/no-parsing-error": 0,
+      "no-console": 0
+    }
+  },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {}
+    }
+  },
+  "browserslist": [
+    "Android >= 4.0",
+    "iOS >= 7"
+  ]
+}

+ 28 - 0
public/index.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
+    <link rel="icon" href="<%= BASE_URL %>logo.png">
+    <title>打卡</title>
+    <script>
+        window._CONFIG = {};
+        // 本地
+        window._CONFIG['domianURL'] = 'http://127.0.0.1:8088/happyjob';
+        //测试服
+        // window._CONFIG['domianURL'] = 'https://kf.hap-job.com/happyjob';
+        //正式服
+        // window._CONFIG['domianURL'] = 'https://web.hap-job.com/happyjob';
+        window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
+        window._CONFIG['imgDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/view';
+        <!-- oss路径 -->
+        window._CONFIG['imgDomainOssURL'] = 'https://happyworktest.oss-cn-shanghai.aliyuncs.com';
+        <!-- oss路径正式服 -->
+        // window._CONFIG['imgDomainOssURL'] = 'https://happywork.oss-cn-shanghai.aliyuncs.com';
+    </script>
+  </head>
+  <body>
+    <div id="app"></div>
+  </body>
+</html>

BIN
public/logo.png


+ 13 - 0
src/App.vue

@@ -0,0 +1,13 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<style>
+body {
+  font-size: 16px;
+  background-color: #f8f8f8;
+  -webkit-font-smoothing: antialiased;
+}
+</style>

+ 10 - 0
src/api/index.js

@@ -0,0 +1,10 @@
+const api = {
+    Login: '/sys/login',
+    Logout: '/sys/logout',
+    ForgePassword: '/auth/forge-password',
+    Register: '/auth/register',
+    SendSms: '/account/sms',
+    // get my info
+    UserInfo: '/user/info'
+}
+export default api

+ 51 - 0
src/api/login.js

@@ -0,0 +1,51 @@
+import api from './index'
+import { axios } from '@/utils/request'
+
+/**
+ * login func
+ * parameter: {
+ *     username: '',
+ *     password: '',
+ *     remember_me: true,
+ *     captcha: '12345'
+ * }
+ * @param parameter
+ * @returns {*}
+ */
+export function login(parameter) {
+  return axios({
+    url: api.Login,
+    method: 'post',
+    data: parameter
+  })
+}
+
+
+export function getSmsCaptcha(parameter) {
+  return axios({
+    url: api.SendSms,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getInfo() {
+  return axios({
+    url: '/api/user/info',
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+export function logout(logoutToken) {
+  return axios({
+    url: '/sys/logout',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+      'X-Access-Token':  logoutToken
+    }
+  })
+}

+ 73 - 0
src/api/manage.js

@@ -0,0 +1,73 @@
+import { axios } from '@/utils/request'
+
+
+//post
+export function postAction(url,parameter) {
+  return axios({
+    url: url,
+    method:'post' ,
+    data: parameter
+  })
+}
+
+//post method= {post | put}
+export function httpAction(url,parameter,method) {
+  return axios({
+    url: url,
+    method:method ,
+    data: parameter
+  })
+}
+
+//put
+export function putAction(url,parameter) {
+  return axios({
+    url: url,
+    method:'put',
+    data: parameter
+  })
+}
+
+//get
+export function getAction(url,parameter) {
+  return axios({
+    url: url,
+    method: 'get',
+    params: parameter
+  })
+}
+
+//deleteAction
+export function deleteAction(url,parameter) {
+  return axios({
+    url: url,
+    method: 'delete',
+    params: parameter
+  })
+}
+
+//batchAction
+export function batchAction(url,parameter) {
+  return axios({
+    url: url,
+    method: 'put',
+    params: parameter
+  })
+}
+
+
+/**
+ * 下载文件 用于excel导出
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function downFile(url,parameter){
+  return axios({
+    url: url,
+    params: parameter,
+    method:'get' ,
+    responseType: 'blob'
+  })
+}
+

BIN
src/assets/logo.png


+ 13 - 0
src/main.js

@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import App from './App';
+import { router } from './router';
+
+import { VueAxios } from "@/utils/request"
+
+Vue.use(VueAxios, router)
+
+new Vue({
+  router,
+  el: '#app',
+  render: h => h(App)
+});

+ 59 - 0
src/router.js

@@ -0,0 +1,59 @@
+import Vue from 'vue';
+import Router from 'vue-router';
+import {getStore} from "@/utils/storage";
+
+Vue.use(Router);
+
+const routes = [
+    {
+        path: '*',
+        redirect: getStore('login') ? '/user' : '/login'
+    },
+    {
+        name: 'user',
+        component: () => import('./view/user'),
+        meta: {
+            title: '会员中心'
+        }
+    },
+    {
+        name: 'cart',
+        component: () => import('./view/cart'),
+        meta: {
+            title: '购物车'
+        }
+    },
+    {
+        name: 'goods',
+        component: () => import('./view/goods'),
+        meta: {
+            title: '商品详情'
+        }
+    },
+    {
+        name: 'login',
+        component: () => import('./view/login'),
+        meta: {
+            title: '用户登录'
+        }
+    }
+];
+
+// add route path
+routes.forEach(route => {
+    route.path = route.path || '/' + (route.name || '');
+});
+
+const router = new Router({routes});
+
+router.beforeEach((to, from, next) => {
+    const title = to.meta && to.meta.title;
+    if (title) {
+        document.title = title;
+    }
+    next();
+});
+
+export {
+    router
+};

+ 37 - 0
src/utils/axios.js

@@ -0,0 +1,37 @@
+const VueAxios = {
+    vm: {},
+    // eslint-disable-next-line no-unused-vars
+    install(Vue, router = {}, instance) {
+        if (this.installed) {
+            return;
+        }
+        this.installed = true;
+
+        if (!instance) {
+            // eslint-disable-next-line no-console
+            console.error('You have to install axios');
+            return;
+        }
+
+        Vue.axios = instance;
+
+        Object.defineProperties(Vue.prototype, {
+            axios: {
+                get: function get() {
+                    return instance;
+                }
+            },
+            $http: {
+                get: function get() {
+                    return instance;
+                }
+            }
+        });
+    }
+};
+
+export {
+    VueAxios,
+    // eslint-disable-next-line no-undef
+    //instance as axios
+}

+ 72 - 0
src/utils/request.js

@@ -0,0 +1,72 @@
+import axios from 'axios'
+import { VueAxios } from './axios'
+import {Toast} from 'vant'
+
+// 创建 axios 实例
+const service = axios.create({
+  baseURL: '/happyjob', // api base_url
+  timeout: 15000 // 请求超时时间
+})
+
+const err = (error) => {
+  if (error.response) {
+    let data = error.response.data;
+    switch (error.response.status) {
+      case 403:
+        Toast('拒绝访问');
+        break
+      case 500:
+        Toast('很抱歉,登录已过期,请重新登录');
+        this.$router.go(0);
+        break
+      case 404:
+        Toast('很抱歉,资源未找到!');
+        break
+      case 504:
+        Toast('网络超时');
+        break
+      case 401:
+        Toast('未授权,请重新登录');
+        setTimeout(() => {
+            this.$router.go(0);
+        }, 1500)
+        break
+      default:
+        Toast(data.errmsg);
+        break
+    }
+  }
+  return Promise.reject(error)
+};
+
+// request interceptor
+service.interceptors.request.use(config => {
+  // const token = Vue.ls.get(ACCESS_TOKEN)
+  // if (token) {
+  //   config.headers[ 'X-Access-Token' ] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
+  // }
+  return config
+},(error) => {
+  return Promise.reject(error)
+})
+
+// response interceptor
+service.interceptors.response.use((response) => {
+    if(response.data.errcode === 0){
+        return response.data
+    }else{
+        return Promise.reject(response.data)
+    }
+  }, err)
+
+const installer = {
+  vm: {},
+  install (Vue, router = {}) {
+    Vue.use(VueAxios, router, service)
+  }
+}
+
+export {
+  installer as VueAxios,
+  service as axios
+}

+ 78 - 0
src/utils/storage.js

@@ -0,0 +1,78 @@
+/**
+ * Set storage
+ *
+ * @param name
+ * @param content
+ * @param maxAge
+ */
+export const setStore = (name, content, maxAge = null) => {
+  if (!global.window || !name) {
+    return;
+  }
+
+  if (typeof content !== 'string') {
+    content = JSON.stringify(content)
+  }
+
+  let storage = global.window.localStorage
+
+  storage.setItem(name, content)
+  if (maxAge && !isNaN(parseInt(maxAge))) {
+    let timeout = parseInt(new Date().getTime() / 1000)
+    storage.setItem(`${name}_expire`, timeout + maxAge)
+  }
+};
+
+/**
+ * Get storage
+ *
+ * @param name
+ * @returns {*}
+ */
+export const getStore = name => {
+  if (!global.window || !name) {
+    return;
+  }
+
+  let content = window.localStorage.getItem(name)
+  let _expire = window.localStorage.getItem(`${name}_expire`)
+
+  if (_expire) {
+    let now = parseInt(new Date().getTime() / 1000)
+    if (now > _expire) {
+      return;
+    }
+  }
+
+  try {
+    return JSON.parse(content)
+  } catch (e) {
+    return content
+  }
+};
+
+/**
+ * Clear storage
+ *
+ * @param name
+ */
+export const clearStore = name => {
+  if (!global.window || !name) {
+    return;
+  }
+
+  window.localStorage.removeItem(name)
+  window.localStorage.removeItem(`${name}_expire`)
+};
+
+/**
+ * Clear all storage
+ */
+export const clearAll = () => {
+  if (!global.window || !name) {
+    return;
+  }
+
+  window.localStorage.clear()
+}
+

+ 254 - 0
src/utils/util.js

@@ -0,0 +1,254 @@
+import { isURL } from '@/utils/validate'
+
+export function timeFix() {
+  const time = new Date()
+  const hour = time.getHours()
+  return hour < 9 ? '早上好' : (hour <= 11 ? '上午好' : (hour <= 13 ? '中午好' : (hour < 20 ? '下午好' : '晚上好')))
+}
+
+export function welcome() {
+  const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了']
+  let index = Math.floor((Math.random()*arr.length))
+  return arr[index]
+}
+
+/**
+ * 触发 window.resize
+ */
+export function triggerWindowResizeEvent() {
+  let event = document.createEvent('HTMLEvents')
+  event.initEvent('resize', true, true)
+  event.eventType = 'message'
+  window.dispatchEvent(event)
+}
+
+/**
+ * 过滤对象中为空的属性
+ * @param obj
+ * @returns {*}
+ */
+export function filterObj(obj) {
+  if (!(typeof obj == 'object')) {
+    return;
+  }
+
+  for ( var key in obj) {
+    if (obj.hasOwnProperty(key)
+      && (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
+      delete obj[key];
+    }
+  }
+  return obj;
+}
+
+/**
+ * 时间格式化
+ * @param value
+ * @param fmt
+ * @returns {*}
+ */
+export function formatDate(value, fmt) {
+  var regPos = /^\d+(\.\d+)?$/;
+  if(regPos.test(value)){
+    //如果是数字
+    let getDate = new Date(value);
+    let o = {
+      'M+': getDate.getMonth() + 1,
+      'd+': getDate.getDate(),
+      'h+': getDate.getHours(),
+      'm+': getDate.getMinutes(),
+      's+': getDate.getSeconds(),
+      'q+': Math.floor((getDate.getMonth() + 3) / 3),
+      'S': getDate.getMilliseconds()
+    };
+    if (/(y+)/.test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
+    }
+    for (let k in o) {
+      if (new RegExp('(' + k + ')').test(fmt)) {
+        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
+      }
+    }
+    return fmt;
+  }else{
+    //TODO
+    value = value.trim();
+    return value.substr(0,fmt.length);
+  }
+}
+
+// 生成首页路由
+export function generateIndexRouter(data) {
+let indexRouter = [{
+          path: '/',
+          name: 'dashboard',
+          //component: () => import('@/components/layouts/BasicLayout'),
+          component: resolve => require(['@/components/layouts/TabLayout'], resolve),
+          meta: { title: '首页' },
+          redirect: '/dashboard/analysis',
+          children: [
+            ...generateChildRouters(data)
+          ]
+        },{
+          "path": "*", "redirect": "/404", "hidden": true
+        }]
+  return indexRouter;
+}
+
+// 生成嵌套路由(子路由)
+
+function  generateChildRouters (data) {
+  const routers = [];
+  for (var item of data) {
+    let component = "";
+    if(item.component.indexOf("layouts")>=0){
+       component = "components/"+item.component;
+    }else{
+       component = "views/"+item.component;
+    }
+
+    // eslint-disable-next-line
+    let URL = (item.meta.url|| '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)) // URL支持{{ window.xxx }}占位符变量
+    if (isURL(URL)) {
+      item.meta.url = URL;
+    }
+
+    let menu =  {
+      path: item.path,
+      name: item.name,
+      redirect:item.redirect,
+      component: resolve => require(['@/' + component+'.vue'], resolve),
+      hidden:item.hidden,
+      //component:()=> import(`@/views/${item.component}.vue`),
+      meta: {
+        title:item.meta.title ,
+        icon: item.meta.icon,
+        url:item.meta.url ,
+        permissionList:item.meta.permissionList,
+        keepAlive:item.meta.keepAlive
+      }
+    }
+    if(item.alwaysShow){
+      menu.alwaysShow = true;
+      menu.redirect = menu.path;
+    }
+    if (item.children && item.children.length > 0) {
+      menu.children = [...generateChildRouters( item.children)];
+    }
+    //--update-begin----author:scott---date:20190320------for:根据后台菜单配置,判断是否路由菜单字段,动态选择是否生成路由(为了支持参数URL菜单)------
+    //判断是否生成路由
+    if(item.route && item.route === '0'){
+      //console.log(' 不生成路由 item.route:  '+item.route);
+      //console.log(' 不生成路由 item.path:  '+item.path);
+    }else{
+      routers.push(menu);
+    }
+    //--update-end----author:scott---date:20190320------for:根据后台菜单配置,判断是否路由菜单字段,动态选择是否生成路由(为了支持参数URL菜单)------
+  }
+  return routers
+}
+
+/**
+ * 深度克隆对象、数组
+ * @param obj 被克隆的对象
+ * @return 克隆后的对象
+ */
+export function cloneObject(obj) {
+  return JSON.parse(JSON.stringify(obj))
+}
+
+/**
+ * 随机生成数字
+ *
+ * 示例:生成长度为 12 的随机数:randomNumber(12)
+ * 示例:生成 3~23 之间的随机数:randomNumber(3, 23)
+ *
+ * @param1 最小值 | 长度
+ * @param2 最大值
+ * @return int 生成后的数字
+ */
+export function randomNumber() {
+  // 生成 最小值 到 最大值 区间的随机数
+  const random = (min, max) => {
+    return Math.floor(Math.random() * (max - min + 1) + min)
+  }
+  if (arguments.length === 1) {
+    let [length] = arguments
+  // 生成指定长度的随机数字,首位一定不是 0
+    let nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
+    return parseInt(nums.join(''))
+  } else if (arguments.length >= 2) {
+    let [min, max] = arguments
+    return random(min, max)
+  } else {
+    return Number.NaN
+  }
+}
+
+/**
+ * 随机生成字符串
+ * @param length 字符串的长度
+ * @param chats 可选字符串区间(只会生成传入的字符串中的字符)
+ * @return string 生成的字符串
+ */
+export function randomString(length, chats) {
+  if (!length) length = 1
+  if (!chats) chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'
+  let str = ''
+  for (let i = 0; i < length; i++) {
+    let num = randomNumber(0, chats.length - 1)
+    str += chats[num]
+  }
+  return str
+}
+
+/**
+ * 随机生成uuid
+ * @return string 生成的uuid
+ */
+export function randomUUID() {
+  let chats = '0123456789abcdef'
+  return randomString(32, chats)
+}
+
+/**
+ * 下划线转驼峰
+ * @param string
+ * @returns {*}
+ */
+export function underLine2CamelCase(string){
+  return string.replace( /_([a-z])/g, function( all, letter ) {
+    return letter.toUpperCase();
+  });
+}
+
+/**
+ * 判断是否显示办理按钮
+ * @param bpmStatus
+ * @returns {*}
+ */
+export function showDealBtn(bpmStatus){
+  if(bpmStatus!="1"&&bpmStatus!="3"&&bpmStatus!="4"){
+    return true;
+  }
+  return false;
+}
+
+/**
+ * 增强CSS,可以在页面上输出全局css
+ * @param css 要增强的css
+ * @param id style标签的id,可以用来清除旧样式
+ */
+export function cssExpand(css, id) {
+  let style = document.createElement('style')
+  style.type = "text/css"
+  style.innerHTML = `@charset "UTF-8"; ${css}`
+  // 清除旧样式
+  if (id) {
+    let $style = document.getElementById(id)
+    if ($style != null) $style.outerHTML = ''
+    style.id = id
+  }
+  // 应用新样式
+  document.head.appendChild(style)
+}

+ 31 - 0
src/utils/validate.js

@@ -0,0 +1,31 @@
+/**
+ * 邮箱
+ * @param {*} s
+ */
+export function isEmail (s) {
+  return /^([a-zA-Z0-9._-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
+}
+
+/**
+ * 手机号码
+ * @param {*} s
+ */
+export function isMobile (s) {
+  return /^1[0-9]{10}$/.test(s)
+}
+
+/**
+ * 电话号码
+ * @param {*} s
+ */
+export function isPhone (s) {
+  return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
+}
+
+/**
+ * URL地址
+ * @param {*} s
+ */
+export function isURL (s) {
+  return /^http[s]?:\/\/.*/.test(s)
+}

+ 119 - 0
src/view/cart/index.vue

@@ -0,0 +1,119 @@
+<template>
+  <div>
+    <van-checkbox-group class="card-goods" v-model="checkedGoods">
+      <van-checkbox
+        class="card-goods__item"
+        v-for="item in goods"
+        :key="item.id"
+        :name="item.id"
+      >
+        <van-card
+          :title="item.title"
+          :desc="item.desc"
+          :num="item.num"
+          :price="formatPrice(item.price)"
+          :thumb="item.thumb"
+        />
+      </van-checkbox>
+    </van-checkbox-group>
+    <van-submit-bar
+      :price="totalPrice"
+      :disabled="!checkedGoods.length"
+      :button-text="submitBarText"
+      @submit="onSubmit"
+    />
+  </div>
+</template>
+
+<script>
+import { Checkbox, CheckboxGroup, Card, SubmitBar, Toast } from 'vant';
+
+export default {
+  components: {
+    [Card.name]: Card,
+    [Checkbox.name]: Checkbox,
+    [SubmitBar.name]: SubmitBar,
+    [CheckboxGroup.name]: CheckboxGroup
+  },
+
+  data() {
+    return {
+      checkedGoods: ['1', '2', '3'],
+      goods: [{
+        id: '1',
+        title: '进口香蕉',
+        desc: '约250g,2根',
+        price: 200,
+        num: 1,
+        thumb: 'https://img.yzcdn.cn/public_files/2017/10/24/2f9a36046449dafb8608e99990b3c205.jpeg'
+      }, {
+        id: '2',
+        title: '陕西蜜梨',
+        desc: '约600g',
+        price: 690,
+        num: 1,
+        thumb: 'https://img.yzcdn.cn/public_files/2017/10/24/f6aabd6ac5521195e01e8e89ee9fc63f.jpeg'
+      }, {
+        id: '3',
+        title: '美国伽力果',
+        desc: '约680g/3个',
+        price: 2680,
+        num: 1,
+        thumb: 'https://img.yzcdn.cn/public_files/2017/10/24/320454216bbe9e25c7651e1fa51b31fd.jpeg'
+      }]
+    };
+  },
+
+  computed: {
+    submitBarText() {
+      const count = this.checkedGoods.length;
+      return '结算' + (count ? `(${count})` : '');
+    },
+
+    totalPrice() {
+      return this.goods.reduce((total, item) => total + (this.checkedGoods.indexOf(item.id) !== -1 ? item.price : 0), 0);
+    }
+  },
+
+  methods: {
+    formatPrice(price) {
+      return (price / 100).toFixed(2);
+    },
+
+    onSubmit() {
+      Toast('点击结算');
+    }
+  }
+};
+</script>
+
+<style lang="less">
+.card-goods {
+  padding: 10px 0;
+  background-color: #fff;
+
+  &__item {
+    position: relative;
+    background-color: #fafafa;
+
+    .van-checkbox__label {
+      width: 100%;
+      height: auto; // temp
+      padding: 0 10px 0 15px;
+      box-sizing: border-box;
+    }
+
+    .van-checkbox__icon {
+      top: 50%;
+      left: 10px;
+      z-index: 1;
+      position: absolute;
+      margin-top: -10px;
+    }
+
+    .van-card__price {
+      color: #f44;
+    }
+  }
+}
+</style>

+ 156 - 0
src/view/goods/index.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="goods">
+    <!-- NavBar导航栏 -->
+    <van-nav-bar :title="title" left-text="返回" left-arrow @click-left="onClickLeft"></van-nav-bar>
+    <van-swipe class="goods-swipe" :autoplay="3000">
+      <van-swipe-item v-for="thumb in goods.thumb" :key="thumb">
+        <img :src="thumb" >
+      </van-swipe-item>
+    </van-swipe>
+
+    <van-cell-group>
+      <van-cell>
+        <div class="goods-title">{{ goods.title }}</div>
+        <div class="goods-price">{{ formatPrice(goods.price) }}</div>
+      </van-cell>
+      <van-cell class="goods-express">
+        <van-col span="10">运费:{{ goods.express }}</van-col>
+        <van-col span="14">剩余:{{ goods.remain }}</van-col>
+      </van-cell>
+    </van-cell-group>
+
+    <van-cell-group class="goods-cell-group">
+      <van-cell value="进入店铺" icon="shop-o" is-link @click="sorry">
+        <template slot="title">
+          <span class="van-cell-text">有赞的店</span>
+          <van-tag class="goods-tag" type="danger">官方</van-tag>
+        </template>
+      </van-cell>
+      <van-cell title="线下门店" icon="location-o" is-link @click="sorry" />
+    </van-cell-group>
+
+    <van-cell-group class="goods-cell-group">
+      <van-cell title="查看商品详情" is-link @click="sorry" />
+    </van-cell-group>
+
+    <van-goods-action>
+      <van-goods-action-icon icon="chat-o" @click="sorry">
+        客服
+      </van-goods-action-icon>
+      <van-goods-action-icon icon="cart-o" @click="onClickCart">
+        购物车
+      </van-goods-action-icon>
+      <van-goods-action-button type="warning" @click="sorry">
+        加入购物车
+      </van-goods-action-button>
+      <van-goods-action-button type="danger" @click="sorry">
+        立即购买
+      </van-goods-action-button>
+    </van-goods-action>
+  </div>
+</template>
+
+<script>
+import {
+  Tag,
+  Col,
+  Icon,
+  Cell,
+  CellGroup,
+  Swipe,
+  Toast,
+  SwipeItem,
+  GoodsAction,
+  GoodsActionIcon,
+  GoodsActionButton,
+  NavBar
+} from 'vant';
+
+export default {
+  components: {
+    [Tag.name]: Tag,
+    [Col.name]: Col,
+    [Icon.name]: Icon,
+    [Cell.name]: Cell,
+    [CellGroup.name]: CellGroup,
+    [Swipe.name]: Swipe,
+    [SwipeItem.name]: SwipeItem,
+    [GoodsAction.name]: GoodsAction,
+    [GoodsActionIcon.name]: GoodsActionIcon,
+    [GoodsActionButton.name]: GoodsActionButton,
+    [NavBar.name]: NavBar
+  },
+
+  data() {
+    return {
+      title:"商品详情",
+      goods: {
+        title: '美国伽力果(约680g/3个)',
+        price: 2680,
+        express: '免运费',
+        remain: 19,
+        thumb: [
+          'https://img.yzcdn.cn/public_files/2017/10/24/e5a5a02309a41f9f5def56684808d9ae.jpeg',
+          'https://img.yzcdn.cn/public_files/2017/10/24/1791ba14088f9c2be8c610d0a6cc0f93.jpeg'
+        ]
+      }
+    };
+  },
+
+  methods: {
+    formatPrice() {
+      return '¥' + (this.goods.price / 100).toFixed(2);
+    },
+
+    onClickCart() {
+      this.$router.push('cart');
+    },
+
+    sorry() {
+      Toast('暂无后续逻辑~');
+    },
+    onClickLeft(){
+        this.$router.go(-1);
+    }
+  }
+};
+</script>
+
+<style lang="less">
+.goods {
+  padding-bottom: 50px;
+
+  &-swipe {
+    img {
+      width: 100%;
+      display: block;
+    }
+  }
+
+  &-title {
+    font-size: 16px;
+  }
+
+  &-price {
+    color: #f44;
+  }
+
+  &-express {
+    color: #999;
+    font-size: 12px;
+    padding: 5px 15px;
+  }
+
+  &-cell-group {
+    margin: 15px 0;
+
+    .van-cell__value {
+      color: #999;
+    }
+  }
+
+  &-tag {
+    margin-left: 5px;
+  }
+}
+</style>

+ 96 - 0
src/view/login/index.vue

@@ -0,0 +1,96 @@
+<template>
+    <div class="login">
+        <van-cell-group style="margin-top: 150px;">
+            <!-- 输入手机号,调起手机号键盘 -->
+            <van-field
+                    v-model="tel"
+                    center
+                    clearable
+                    type="tel"
+                    label="手机号"
+                    placeholder="请输入手机号"
+            >
+                <van-button :disabled="state.smsSendBtn" slot="button" size="small" type="primary" @click="sendCode">
+                    {{!state.smsSendBtn && '获取验证码' || (state.time+' s')}}
+                </van-button>
+            </van-field>
+            <!-- 允许输入整数,调起数字键盘 -->
+            <van-field v-model="code" maxlength="6" type="digit" label="验证码" placeholder="请输入验证码"/>
+        </van-cell-group>
+        <van-button :loading="loading" type="primary" size="large" @click="login" style="margin: 250px 0 0 0;">
+            {{text}}
+        </van-button>
+    </div>
+</template>
+
+<script>
+    import {postAction} from '@/api/manage'
+    import {Button, Cell, CellGroup, Field, Toast} from 'vant';
+    import {setStore} from "@/utils/storage";
+
+    export default {
+        name: "index",
+        components: {
+            [Button.name]: Button,
+            [Cell.name]: Cell,
+            [CellGroup.name]: CellGroup,
+            [Field.name]: Field,
+        },
+        data() {
+            return {
+                loading: false,
+                text: "登录",
+                tel: '',
+                code: '',
+                state: {
+                    time: 60,
+                    smsSendBtn: false,
+                },
+                url: {
+                    getSmsCaptcha: '/f/api/login/getAuthCode',
+                }
+            };
+        },
+        methods: {
+            login() {
+                this.loading = true;
+                //登录成功,缓存用户信息,跳转相应页面
+                setStore('login', "123");
+                this.$router.replace('goods');
+            },
+            //获取验证码
+            sendCode() {
+                if (!this.tel) {
+                    Toast('请输入手机号~');
+                    return;
+                }
+                this.state.smsSendBtn = true;
+                let interval = window.setInterval(() => {
+                    if (this.state.time-- <= 0) {
+                        this.state.time = 60;
+                        this.state.smsSendBtn = false;
+                        window.clearInterval(interval);
+                    }
+                }, 1000);
+                let smsParams = {};
+                smsParams.phone = this.tel;
+                postAction(this.url.getSmsCaptcha, smsParams)
+                    .then(() => {
+                        setTimeout(Toast('验证码发送中~'), 500);
+                    })
+                    .catch(() => {
+                        setTimeout(Toast('验证码发送失败~'), 500);
+                        clearInterval(interval);
+                        this.state.time = 60;
+                        this.state.smsSendBtn = false;
+                    });
+            }
+        }
+    }
+</script>
+
+<style scoped>
+    .login {
+        padding: 0 10px;
+    }
+</style>

+ 73 - 0
src/view/user/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div>
+    <img class="user-poster" src="https://img.yzcdn.cn/public_files/2017/10/23/8690bb321356070e0b8c4404d087f8fd.png">
+    <van-row class="user-links">
+      <van-col span="6">
+        <van-icon name="pending-payment" />
+        待付款
+      </van-col>
+      <van-col span="6">
+        <van-icon name="records" />
+        待接单
+      </van-col>
+      <van-col span="6">
+        <van-icon name="tosend" />
+        待发货
+      </van-col>
+      <van-col span="6">
+        <van-icon name="logistics" />
+        已发货
+      </van-col>
+    </van-row>
+
+    <van-cell-group class="user-group">
+      <van-cell icon="records" title="全部订单" is-link />
+    </van-cell-group>
+
+    <van-cell-group>
+      <van-cell icon="points" title="我的积分" is-link />
+      <van-cell icon="gold-coin-o" title="我的优惠券" is-link />
+      <van-cell icon="gift-o" title="我收到的礼物" is-link />
+    </van-cell-group>
+  </div>
+</template>
+
+<script>
+import { Row, Col, Icon, Cell, CellGroup } from 'vant';
+
+export default {
+  components: {
+    [Row.name]: Row,
+    [Col.name]: Col,
+    [Icon.name]: Icon,
+    [Cell.name]: Cell,
+    [CellGroup.name]: CellGroup
+  }
+};
+</script>
+
+<style lang="less">
+.user {
+  &-poster {
+    width: 100%;
+    height: 53vw;
+    display: block;
+  }
+
+  &-group {
+    margin-bottom: 15px;
+  }
+
+  &-links {
+    padding: 15px 0;
+    font-size: 12px;
+    text-align: center;
+    background-color: #fff;
+
+    .van-icon {
+      display: block;
+      font-size: 24px;
+    }
+  }
+}
+</style>

+ 30 - 0
vue.config.js

@@ -0,0 +1,30 @@
+const path = require('path')
+
+function resolve(dir) {
+    return path.join(__dirname, dir)
+}
+
+module.exports = {
+    outputDir: 'dist',
+    publicPath: process.env.NODE_ENV === 'production' ? '/dk' : '/',
+
+    chainWebpack: (config) => {
+        config.resolve.alias
+            .set('@$', resolve('src'))
+            .set('@api', resolve('src/api'))
+            .set('@assets', resolve('src/assets'))
+            .set('@views', resolve('src/views'))
+    },
+
+    devServer: {
+        port: 8082,
+        proxy: {
+            '/happyjob': {
+                target: 'http://localhost:8088', //请求本地 需要happyjob后台项目
+                ws: false,
+                changeOrigin: true
+            },
+        }
+    },
+    lintOnSave: undefined
+};