youyawu 5 years ago
commit
f318aa5935

+ 2 - 0
.browserslistrc

@@ -0,0 +1,2 @@
+> 1%
+last 2 versions

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VUE_APP_BaseUrl=/oneportal
+VUE_APP_publicPath=/website
+VUE_APP_publicName=中国钢制家具产业聚集网

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VUE_APP_Target=http://192.168.100.254:9300/

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VUE_APP_Target=http://192.168.100.254:9300/

+ 15 - 0
.eslintrc.js

@@ -0,0 +1,15 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  extends: ["plugin:vue/essential", "@vue/prettier", "@vue/typescript"],
+  rules: {
+    //  "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
+    "no-console": 0,
+    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
+  },
+  parserOptions: {
+    parser: "@typescript-eslint/parser"
+  }
+};

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 1 - 0
README.md

@@ -0,0 +1 @@
+## README

+ 3 - 0
babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"]
+};

File diff suppressed because it is too large
+ 11913 - 0
package-lock.json


+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "name": "furniture-vue",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.19.0",
+    "core-js": "^3.4.3",
+    "element-ui": "^2.13.0",
+    "js-cookie": "^2.2.1",
+    "js-md5": "^0.7.3",
+    "vue": "^2.6.10",
+    "vue-class-component": "^7.0.2",
+    "vue-property-decorator": "^8.3.0",
+    "vue-router": "^3.1.3",
+    "vuex": "^3.1.2",
+    "vuex-module-decorators": "^0.11.0"
+  },
+  "devDependencies": {
+    "@types/js-cookie": "^2.2.4",
+    "@types/qs": "^6.9.0",
+    "@vue/cli-plugin-babel": "^4.1.0",
+    "@vue/cli-plugin-eslint": "^4.1.0",
+    "@vue/cli-plugin-router": "^4.1.0",
+    "@vue/cli-plugin-typescript": "^4.1.0",
+    "@vue/cli-plugin-vuex": "^4.1.0",
+    "@vue/cli-service": "^4.1.0",
+    "@vue/eslint-config-prettier": "^5.0.0",
+    "@vue/eslint-config-typescript": "^4.0.0",
+    "eslint": "^5.16.0",
+    "eslint-plugin-prettier": "^3.1.1",
+    "eslint-plugin-vue": "^5.0.0",
+    "prettier": "^1.19.1",
+    "sass": "^1.23.7",
+    "sass-loader": "^8.0.0",
+    "typescript": "~3.5.3",
+    "vue-template-compiler": "^2.6.10"
+  }
+}

BIN
public/favicon.ico


+ 21 - 0
public/index.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<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, user-scalable=0;" />
+  <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
+  <title><%= VUE_APP_publicName %></title>
+</head>
+
+<body>
+  <noscript>
+    <strong>We're sorry but web-vue doesn't work properly without JavaScript
+      enabled. Please enable it to continue.</strong>
+  </noscript>
+  <div id="app"></div>
+  <!-- built files will be auto injected -->
+</body>
+
+</html>

+ 10 - 0
src/App.vue

@@ -0,0 +1,10 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+<style scoped>
+#app {
+  height: 100%;
+}
+</style>

+ 9 - 0
src/api/user.ts

@@ -0,0 +1,9 @@
+import { post } from "@/utils/request";
+
+export const getUserInfo = async (token: string) => {
+  const [err, data] = await post<IUser>("", {
+    token
+  });
+  if (err) return null;
+  return data;
+};

BIN
src/assets/noneData.png


+ 105 - 0
src/components/loadMore.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="loadMore">
+    <template v-if="count">
+      <slot v-for="(item, index) in items" :index="index" :item="item" />
+      <el-pagination
+        class="pagination"
+        v-if="count > rows"
+        background
+        layout="prev, pager, next,total, jumper"
+        :total="count"
+        :page-size="rows"
+        @current-change="currentChange"
+      />
+    </template>
+    <template v-else-if="count === 0 && !hideTip">
+      <div class="noneData">
+        <img src="@assets/noneData.png" alt="" />
+        <br />
+        <div>暂无数据</div>
+      </div>
+    </template>
+  </div>
+</template>
+<script lang="ts">
+import {
+  Component,
+  Vue,
+  Prop,
+  PropSync,
+  Watch,
+  Emit
+} from "vue-property-decorator";
+interface IPageResult {
+  items: any[];
+  total: number;
+}
+@Component
+export default class extends Vue {
+  @Prop({ required: true })
+  private readonly url!: string;
+  @Prop(Object)
+  params: object | undefined;
+  @Prop({ default: 10 })
+  private readonly rows!: number;
+  @Prop(Boolean) private auth!: boolean;
+  @Prop(Boolean) private hideTip!: boolean;
+  @PropSync("total") totalSync!: number;
+
+  private count: number | null = null;
+  private page = 0;
+  private items: any[] = [];
+  @Watch("params", { immediate: true, deep: true })
+  reload() {
+    if (this.page === 1) return this.load();
+    this.page = 1;
+  }
+  @Watch("count", { immediate: true })
+  totalChange() {
+    this.totalSync = this.count || 0;
+  }
+  @Watch("page", { immediate: true })
+  async load() {
+    const { page, rows } = this;
+    const [err, { items = [], total = 0 }] = await this.post<IPageResult>(
+      this.url,
+      Object.assign({}, this.params, { page, rows })
+    );
+    if (err) console.error("列表加载失败");
+    // if (err) this.$message.error("列表加载失败"); //throw Error("loadMore fail");
+    this.count = total;
+    this.setItems(items);
+  }
+  currentChange(page: number) {
+    this.page = page;
+  }
+  @Emit("getItems")
+  private setItems(items: any[]) {
+    return (this.items = items);
+  }
+  get post() {
+    return this.auth ? this.$post_auth : this.$post;
+  }
+}
+</script>
+<style lang="scss" scoped>
+.loadMore {
+  width: 100%;
+  overflow: hidden;
+  .pagination {
+    text-align: right;
+  }
+  .noneData {
+    padding-top: 5rem;
+    text-align: center;
+    img {
+      width: 15rem;
+    }
+    div {
+      font-size: 1.5rem;
+      color: #999;
+      margin-top: 1rem;
+    }
+  }
+}
+</style>

+ 22 - 0
src/components/routerLink.vue

@@ -0,0 +1,22 @@
+<template>
+  <a v-if="!to.startsWith('/')" :href="to" :target="target">
+    <slot></slot>
+  </a>
+  <router-link v-else v-bind="vProps">
+    <slot></slot>
+  </router-link>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from "vue-property-decorator";
+@Component
+export default class extends Vue {
+  @Prop({
+    required: true
+  })
+  private to!: string;
+  @Prop({ default: "_self" }) target!: string;
+  get vProps() {
+    return { ...this.$props, ...this.$attrs };
+  }
+}
+</script>

+ 77 - 0
src/components/tab.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="tab">
+    <span
+      v-for="({ title }, index) in Items"
+      :key="`tab-${index}`"
+      @click="e => click(e, index)"
+      :class="{ curr: aIndex === index }"
+      ref="tab"
+    >
+      {{ title }}
+    </span>
+    <div
+      class="bar"
+      :style="{ transform: `translateX(${barLeft}px)`, width: `${barWidth}px` }"
+    ></div>
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop, Emit } from "vue-property-decorator";
+@Component
+export default class extends Vue {
+  @Prop({
+    required: true
+  })
+  private arr!: any[];
+  private aIndex = 0;
+  private barLeft = 0;
+  private barWidth = 0;
+  mounted() {
+    (this.$refs.tab as HTMLElement[])[0].click();
+  }
+  get Items() {
+    if (this.arr instanceof Array) return this.arr;
+    if (typeof this.arr === "string")
+      return (this.arr as string).split(",").map(title => ({ title }));
+    return [];
+  }
+  @Emit("click")
+  click({ target }: MouseEvent, index: number) {
+    this.aIndex = index;
+    const { offsetWidth, offsetLeft } = target as HTMLElement;
+    this.barLeft = offsetLeft;
+    this.barWidth = offsetWidth;
+    return {
+      index,
+      item: this.Items[index]
+    };
+  }
+}
+</script>
+<style lang="scss" scoped>
+.tab {
+  border-bottom: 1px solid #e4e7ed;
+  margin-bottom: 2rem;
+  padding: 0.5rem 0;
+  position: relative;
+  span {
+    font-size: 2rem;
+    font-weight: bolder;
+    color: rgb(51, 51, 51);
+    margin-right: 40px;
+    cursor: pointer;
+    &.hover,
+    &.curr {
+      color: #c52829;
+    }
+  }
+  .bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 0.2rem;
+    background: #c52829;
+    transition: all 0.5s;
+  }
+}
+</style>

+ 20 - 0
src/main.ts

@@ -0,0 +1,20 @@
+import Vue from "vue";
+import elementUI, { Form } from "element-ui";
+import App from "./App.vue";
+import router from "./router";
+import store from "./store";
+import "./styles/index.scss";
+import request from "./utils/request";
+import { setTitles } from "./utils/index";
+import message from "./utils/message";
+Vue.use(elementUI);
+Vue.use(request);
+Vue.use(message);
+setTitles(router);
+Vue.config.productionTip = false;
+
+new Vue({
+  router,
+  store,
+  render: h => h(App)
+}).$mount("#app");

+ 75 - 0
src/router/index.ts

@@ -0,0 +1,75 @@
+import Vue from "vue";
+import VueRouter, { Route, RouteConfig } from "vue-router";
+import layout from "../views/layout/index.vue";
+import { getTitle } from "@/utils";
+import user from "@/store/modules/user";
+import { Loading } from "element-ui";
+import { Message } from "@/utils/message";
+import { login } from "@/utils/cookies";
+Vue.use(VueRouter);
+const router = new VueRouter({
+  mode: "history",
+  base: process.env.BASE_URL,
+  scrollBehavior(to, from, savedPosition) {
+    if (savedPosition) {
+      return savedPosition;
+    } else {
+      return { x: 0, y: 0 };
+    }
+  },
+  routes: [
+    {
+      path: "/",
+      component: layout,
+      redirect: "/home",
+      children: [
+        {
+          path: "/home",
+          component: () => import("../views/home/index.vue"),
+          meta: { title: "首页", nav: true }
+        }
+      ]
+    },
+    {
+      path: "/error/:code",
+      component: () => import("../views/error.vue")
+    }
+  ]
+});
+
+let loading: any;
+router.beforeEach(async (to, from, next) => {
+  const { token } = to.query;
+  if (typeof token === "string") {
+    await user.setToken(token);
+    return next(login.get());
+  }
+  if (user.Token && !user.UserInfo) {
+    await user.getUserInfo();
+  }
+  let errCode = 0;
+  if (!to.matched.length) errCode = 404;
+  if (to.path.startsWith("/user") && !user.UserInfo) errCode = 401;
+  if (user.UserInfo === null && to.matched.some(({ meta: { auth } }) => auth)) {
+    Message.warning("请先登录!");
+    return next(false);
+  }
+  loading = Loading.service({
+    fullscreen: true,
+    text: "跳转中..."
+  });
+  if (errCode)
+    return next({
+      path: `/error/${errCode}`,
+      replace: true
+    });
+  const title = getTitle(to);
+  if (title) document.title = `${title} - ${process.env.VUE_APP_publicName}`;
+  next();
+});
+router.afterEach((to, from) =>
+  setTimeout(() => {
+    loading.close();
+  })
+);
+export default router;

+ 4 - 0
src/store/index.ts

@@ -0,0 +1,4 @@
+import Vue from "vue";
+import Vuex from "vuex";
+Vue.use(Vuex);
+export default new Vuex.Store({});

+ 45 - 0
src/store/modules/user.ts

@@ -0,0 +1,45 @@
+import {
+  VuexModule,
+  Module,
+  getModule,
+  Mutation,
+  Action,
+  MutationAction
+} from "vuex-module-decorators";
+import { auth } from "@/utils/cookies";
+import { getUserInfo } from "@/api/user";
+import store from "../index";
+
+@Module({ dynamic: true, store, name: "user" })
+class User extends VuexModule {
+  public Token = auth.getToken() || "";
+  public UserInfo: IUser | null = null;
+  @Action({ commit: "setUserInfo" })
+  public async getUserInfo() {
+    if (!this.Token) return null;
+    const userInfo = await getUserInfo(this.Token);
+    if (userInfo === null) this.clear();
+    return userInfo;
+  }
+  @Mutation
+  private setUserInfo(userInfo: IUser | null) {
+    this.UserInfo = userInfo;
+  }
+  @Mutation
+  private set_token(token: string) {
+    this.Token = token;
+  }
+  @Action({ commit: "set_token" })
+  public setToken(token: string) {
+    if (this.UserInfo) return this.Token;
+    auth.setToken(token);
+    return token;
+  }
+  @Mutation
+  public clear() {
+    this.Token = "";
+    this.UserInfo = null;
+    auth.removeToken();
+  }
+}
+export default getModule(User);

+ 65 - 0
src/styles/index.scss

@@ -0,0 +1,65 @@
+$--font-path: "~element-ui/lib/theme-chalk/fonts";
+@import "~element-ui/packages/theme-chalk/src/index";
+
+html {
+  height: 100%;
+
+  @media screen and (max-width: 1400px) {
+    font-size: 62.5%;
+  }
+
+  @media screen and (min-width: 1400px) {
+    font-size: 75%;
+  }
+
+  body {
+    padding: 0;
+    margin: 0;
+    height: 100%;
+    font-family: "Microsoft YaHei";
+
+    .container {
+      width: 1150px;
+      margin: auto;
+    }
+
+    .noneData {
+      padding-top: 4rem;
+      text-align: center;
+
+      img {
+        width: 8rem;
+      }
+
+      div {
+        font-size: 1.4rem;
+        color: #666;
+        margin-top: 1rem;
+      }
+    }
+
+    .fl {
+      float: left;
+    }
+
+    .fr {
+      float: right;
+    }
+
+    a {
+      text-decoration: none;
+      color: #333;
+    }
+
+    table {
+      border-collapse: collapse;
+      border-spacing: 0;
+    }
+
+    * {
+      padding: 0;
+      margin: 0;
+      outline: none;
+    }
+  }
+}

+ 45 - 0
src/types/index.d.ts

@@ -0,0 +1,45 @@
+import Vue from "vue";
+
+declare module "vue/types/vue" {
+  interface Vue {
+    $post: IRequest;
+    $get: IRequest;
+    $post_auth: IRequest;
+    $get_auth: IRequest;
+  }
+  interface VueConstructor {
+    $post: IRequest;
+    $get: IRequest;
+    $post_auth: IRequest;
+    $get_auth: IRequest;
+  }
+}
+
+declare global {
+  interface IAny {
+    [index: string]: any;
+  }
+  interface IError {
+    errmsg: string;
+    errno: number;
+  }
+  interface IBaseResult<T = any> extends IError {
+    data: T;
+  }
+  interface IRequest {
+    <T = any>(url: string, data?: any): Promise<IResult<T>>;
+  }
+
+  interface IResult<T> extends Array<IError | null | T> {
+    0: IError | null;
+    1: T;
+    length: 2;
+  }
+
+  interface IUser {}
+
+  interface IBreadCrumbs {
+    path: string;
+    title: string;
+  }
+}

+ 13 - 0
src/types/shims-tsx.d.ts

@@ -0,0 +1,13 @@
+import Vue, { VNode } from "vue";
+
+declare global {
+  namespace JSX {
+    // tslint:disable no-empty-interface
+    interface Element extends VNode {}
+    // tslint:disable no-empty-interface
+    interface ElementClass extends Vue {}
+    interface IntrinsicElements {
+      [elem: string]: any;
+    }
+  }
+}

+ 4 - 0
src/types/shims-vue.d.ts

@@ -0,0 +1,4 @@
+declare module "*.vue" {
+  import Vue from "vue";
+  export default Vue;
+}

+ 18 - 0
src/utils/cookies.ts

@@ -0,0 +1,18 @@
+import Cookies from "js-cookie";
+
+const TokenKey = "Token";
+export const auth = {
+  getToken: () => Cookies.get(TokenKey),
+  setToken: (token: string) => Cookies.set(TokenKey, token),
+  removeToken: () => Cookies.remove(TokenKey)
+};
+
+export const login = {
+  get: () => {
+    const x = Cookies.get("Login") || "/user";
+    login.remove();
+    return x;
+  },
+  set: (value: string) => Cookies.set("Login", value),
+  remove: () => Cookies.remove("Login")
+};

+ 75 - 0
src/utils/index.ts

@@ -0,0 +1,75 @@
+import { Route, RouteConfig } from "vue-router";
+
+export const getLevel = (level: string) => {
+  switch (level) {
+    case "1":
+      return 3;
+    case "2":
+      return 1;
+
+    case "3":
+      return 2;
+  }
+  return 0;
+};
+
+const titles: IBreadCrumbs[] = [];
+export const setTitles = (router: any) =>
+  titles.push(
+    ...getChildren(router.options.routes[0].children as RouteConfig[])
+  );
+const getChildren = (route: RouteConfig[], parent = "") => {
+  const t: IBreadCrumbs[] = [];
+
+  route.forEach(({ path, children, meta: { title } = { title: "" } }) => {
+    const tit: IBreadCrumbs = {
+      path: path.startsWith("/")
+        ? path
+        : `${parent ? `${parent}/` : parent}${path}`,
+      title
+    };
+    t.push(tit);
+    if (children) t.push(...getChildren(children, tit.path));
+  });
+  return t;
+};
+
+export const getTitle: (route: Route) => string = route => {
+  let title = getTitleByPath(route.path);
+  if (title) return title;
+  let length = route.matched.length;
+  while (!title && length--) {
+    title = getTitleByPath(route.matched[length].path);
+  }
+  return title;
+};
+export const getTitleByPath = (path: string) => {
+  let title = "";
+  titles.forEach(t => {
+    if (t.path === path) title = t.title;
+  });
+  return title;
+};
+export const dateFormat = (fmt: string, x: Date | string) => {
+  const date = typeof x === "string" ? new Date(x) : x;
+  let ret;
+  let opt: IAny = {
+    "Y+": date.getFullYear().toString(), // 年
+    "M+": (date.getMonth() + 1).toString(), // 月
+    "d+": date.getDate().toString(), // 日
+    "h+": date.getHours().toString(), // 时
+    "m+": date.getMinutes().toString(), // 分
+    "s+": date.getSeconds().toString() // 秒
+    // 有其他格式化字符需求可以继续添加,必须转化成字符串
+  };
+  for (let k in opt) {
+    ret = new RegExp("(" + k + ")").exec(fmt);
+    if (ret) {
+      fmt = fmt.replace(
+        ret[1],
+        ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
+      );
+    }
+  }
+  return fmt;
+};

+ 39 - 0
src/utils/message.ts

@@ -0,0 +1,39 @@
+import Vue from "vue";
+import { ElMessageOptions, ElMessageComponent } from "element-ui/types/message";
+import { Message as elMage } from "element-ui";
+
+let instance: ElMessageComponent | null = null;
+const showMessage = (options: ElMessageOptions) => {
+  if (instance) instance.close();
+  instance = elMage(options);
+};
+
+const $message = Object.assign(
+  (message: string | ElMessageOptions, options?: ElMessageOptions) => {
+    if (typeof message === "string")
+      return showMessage({ type: "info", ...options, message });
+    showMessage(message);
+  },
+  {
+    success(message: string, options?: ElMessageOptions) {
+      showMessage({ ...options, type: "success", message });
+    },
+    warning(message: string, options?: ElMessageOptions) {
+      showMessage({ ...options, type: "warning", message });
+    },
+    info(message: string, options?: ElMessageOptions) {
+      showMessage({ ...options, type: "info", message });
+    },
+    error(message: string, options?: ElMessageOptions) {
+      showMessage({ ...options, type: "error", message });
+    }
+  }
+);
+export const Message = $message;
+export default {
+  install(vue: typeof Vue) {
+    Object.assign(vue.prototype, {
+      $message
+    });
+  }
+};

+ 65 - 0
src/utils/request.ts

@@ -0,0 +1,65 @@
+import Vue from "vue";
+import axios from "axios";
+import qs from "qs";
+import user from "@/store/modules/user";
+const instance = axios.create({
+  baseURL: process.env.VUE_APP_BaseUrl,
+  timeout: 5000,
+  headers: {
+    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
+  },
+  transformRequest: [
+    data => {
+      if (typeof data !== "object") return data;
+      let newData: IAny = {};
+      Object.keys(data).forEach(key => {
+        if ([null, undefined, ""].includes(data[key])) return;
+        if (typeof data[key] === "object") {
+          data[key] = JSON.stringify(data[key]);
+        }
+        newData[key] = data[key];
+      });
+      return qs.stringify(newData);
+    }
+  ]
+});
+instance.interceptors.response.use(
+  x => x,
+  err => ({
+    data: {
+      errno: -1,
+      errmsg: "网络请求失败",
+      data: err
+    }
+  })
+);
+const getResult: <T>(x: IBaseResult) => IResult<T> = <T>({
+  data,
+  errmsg,
+  errno
+}: IBaseResult<T>) => {
+  if (errno === 0) return [null, data];
+  return [{ errno, errmsg }, (data || {}) as T];
+};
+export const post: IRequest = async <T>(url: string, params: any) => {
+  const { data } = await instance.post<IBaseResult<T>>(url, params);
+  return getResult<T>(data);
+};
+export const get: IRequest = async <T>(url: string, params: any) => {
+  const { data } = await instance.get<IBaseResult<T>>(url, { params });
+  return getResult<T>(data);
+};
+export const post_auth: IRequest = async <T>(url: string, params: any) =>
+  await post<T>(url, Object.assign({}, params, { token: user.Token }));
+export const get_auth: IRequest = async <T>(url: string, params: any) =>
+  await get<T>(url, Object.assign({}, params, { token: user.Token }));
+export default {
+  install(vue: typeof Vue) {
+    Object.assign(vue.prototype, {
+      $get: get,
+      $post: post,
+      $get_auth: get_auth,
+      $post_auth: post_auth
+    });
+  }
+};

+ 117 - 0
src/views/error.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="error">
+    <div class="main-logo">
+      {{ title }}
+    </div>
+    <div class="error-main">
+      <div class="err-reason">
+        {{ message }}
+      </div>
+      <div class="tips">
+        不要着急,你可以返回
+        <a class="click-page" @click="back">上一页</a>或
+        <a class="click-page" @click="toHome">首页</a>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Watch } from "vue-property-decorator";
+@Component
+export default class extends Vue {
+  private title = process.env.VUE_APP_publicName;
+  get message() {
+    switch (this.$route.params.code) {
+      case "401":
+        return "登陆失效,请重新登陆";
+      case "404":
+        return "抱歉,你访问的页面不存在";
+      case "500":
+        return "抱歉,服务器出错了";
+    }
+    return "请求失败";
+  }
+  toHome() {
+    this.$router.push({ path: "/home" });
+  }
+  back() {
+    this.$router.go(-1);
+  }
+}
+</script>
+<style lang="scss" scoped>
+.error {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background-color: rgb(230, 230, 230);
+
+  .main-logo {
+    margin-top: 50px;
+    height: 90px;
+    line-height: 90px;
+    background-color: rgb(2, 131, 141);
+    text-align: center;
+    color: #ffffff;
+    font-size: 24px;
+    font-weight: 900;
+  }
+
+  .error-main {
+    margin-top: 50px;
+    text-align: center;
+
+    .err-message {
+      display: inline-block;
+      width: 300px;
+      height: 180px;
+      color: #ffffff;
+      font-size: 20px;
+    }
+    .err-reason {
+      margin: 30px 0 20px 0;
+      position: relative;
+      left: 50%;
+      transform: translateX(-50%);
+      height: 60px;
+      width: 360px;
+      line-height: 60px;
+      font-size: 26px;
+      font-weight: 600;
+    }
+    .err-reason::before {
+      content: "";
+      width: 360px;
+      height: 4px;
+      position: absolute;
+      top: 0px;
+      left: 0px;
+      background: linear-gradient(to bottom, #999999, #eeeeee);
+      border-radius: 50% / 100% 100% 0 0;
+      transform: rotateX(180deg);
+    }
+    .err-reason::after {
+      content: "";
+      width: 360px;
+      height: 4px;
+      position: absolute;
+      bottom: 0px;
+      left: 0px;
+      background: linear-gradient(to bottom, #999999, #eeeeee);
+      border-radius: 50% / 100% 100% 0 0;
+    }
+    .tips {
+      font-size: 18px;
+      .click-page {
+        color: rgb(2, 131, 141);
+        font-weight: 600;
+      }
+      .click-page:hover {
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
src/views/home/index.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>index</div>
+</template>

+ 50 - 0
src/views/layout/components/breadcrumb.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="bgF5">
+    <el-breadcrumb
+      v-if="arr.length > 1"
+      class="container  breadcrumb"
+      separator-class="el-icon-arrow-right"
+    >
+      <el-breadcrumb-item
+        v-for="({ path, title }, index) in arr"
+        :key="path"
+        :to="index === arr.length - 1 ? '' : path"
+        >{{ title }}</el-breadcrumb-item
+      >
+    </el-breadcrumb>
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop, Watch, Emit } from "vue-property-decorator";
+import { getTitle, getTitleByPath } from "@/utils/index";
+@Component
+export default class extends Vue {
+  get arr() {
+    const arr: IBreadCrumbs[] = [];
+    const paths = this.$route.path.split("/");
+    paths.reduce((total = "", curr, index) => {
+      const path = `${total}/${curr}`;
+      let title = getTitleByPath(path);
+      if (index === paths.length - 1 && !title) title = getTitle(this.$route);
+      if (title) arr.push({ title, path });
+      return path;
+    });
+    return arr;
+  }
+}
+</script>
+<style lang="scss" scoped>
+.breadcrumb {
+  height: 4rem;
+  line-height: 4rem;
+  padding: 0 20px;
+  box-sizing: border-box;
+  span {
+    cursor: pointer;
+  }
+}
+.pd20 {
+  padding: 20px;
+  box-sizing: border-box;
+}
+</style>

+ 18 - 0
src/views/layout/components/footer.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="footer">
+    footer
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue } from "vue-property-decorator";
+@Component
+export default class yfooter extends Vue {}
+</script>
+<style scoped lang="scss">
+.footer {
+  width: 100%;
+  height: 11rem;
+  background: #444;
+  color: #fff;
+}
+</style>

+ 11 - 0
src/views/layout/components/header.vue

@@ -0,0 +1,11 @@
+<template>
+  <div class="header">
+    header
+  </div>
+</template>
+
+<style scoped lang="scss">
+.header {
+  height: 14rem;
+}
+</style>

+ 33 - 0
src/views/layout/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="layout">
+    <y-header />
+    <!-- <breadcrumb /> -->
+    <keep-alive>
+      <router-view class="main" />
+    </keep-alive>
+    <y-footer />
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Watch } from "vue-property-decorator";
+import yHeader from "./components/header.vue";
+import yFooter from "./components/footer.vue";
+import breadcrumb from "./components/breadcrumb.vue";
+@Component({
+  components: {
+    yHeader,
+    yFooter,
+    breadcrumb
+  }
+})
+export default class layout extends Vue {}
+</script>
+<style lang="scss" scoped>
+.layout {
+  height: 100%;
+  .main {
+    min-height: calc(100% - 250px);
+    overflow: hidden;
+  }
+}
+</style>

+ 39 - 0
tsconfig.json

@@ -0,0 +1,39 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "importHelpers": true,
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "sourceMap": true,
+    "baseUrl": ".",
+    "types": [
+      "webpack-env"
+    ],
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "lib": [
+      "esnext",
+      "dom",
+      "dom.iterable",
+      "scripthost"
+    ]
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "tests/**/*.ts",
+    "tests/**/*.tsx"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}

+ 18 - 0
vue.config.js

@@ -0,0 +1,18 @@
+module.exports = {
+  publicPath: "/",
+  configureWebpack: {
+    devtool: "source-map"
+  },
+  chainWebpack: config => {
+    config.resolve.alias.set("@assets", "@/assets");
+  },
+  devServer: {
+    open: true,
+    proxy: {
+      "/oneportal": {
+        target: process.env.VUE_APP_Target
+        // changeOrigin: true,
+      }
+    }
+  }
+};