晓晓晓晓丶vv 4 years ago
parent
commit
75faef9c82

+ 3 - 0
.browserslistrc

@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead

+ 22 - 5
.gitignore

@@ -1,6 +1,23 @@
-# ---> Laravel
-/bootstrap/compiled.php
-.env.*.php
-.env.php
-.env
+.DS_Store
+node_modules
+/dist
 
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 20 - 1
README.md

@@ -1,3 +1,22 @@
 # precise_delivery_distribution_front
 
-精准投放后台前端部分
+精准投放后台前端部分
+# zhuishuyun_put_admin
+
+## Project setup
+```
+yarn install
+```
+
+### Compiles and hot-reloads for development
+```
+yarn serve
+```
+
+### Compiles and minifies for production
+```
+yarn build
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 9 - 0
babel.config.js

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

+ 33 - 0
package.json

@@ -0,0 +1,33 @@
+{
+  "name": "zhuishuyun_put_admin",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build"
+  },
+  "dependencies": {
+    "ant-design-vue": "^2.0.0-beta.15",
+    "axios": "^0.21.0",
+    "clipboard": "^2.0.6",
+    "core-js": "^3.6.5",
+    "vue": "^3.0.0",
+    "vue-router": "^4.0.0-0",
+    "vuex": "^4.0.0-0"
+  },
+  "devDependencies": {
+    "@types/clipboard": "^2.0.1",
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-typescript": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/compiler-sfc": "^3.0.0",
+    "babel-plugin-import": "^1.13.1",
+    "less": "^3.12.2",
+    "less-loader": "^7.0.2",
+    "sass": "^1.26.5",
+    "sass-loader": "^8.0.2",
+    "typescript": "~3.9.3"
+  }
+}

+ 13 - 0
public/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <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" />
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 18 - 0
src/App.vue

@@ -0,0 +1,18 @@
+<template>
+  <div id="nav">
+    <router-link to="/">Home</router-link> |
+    <router-link to="/about">About</router-link>
+  </div>
+  <a-config-provider :locale="locale">
+    <router-view />
+  </a-config-provider>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import zhCN from "ant-design-vue/lib/locale-provider/zh_CN";
+
+const locale = ref(zhCN);
+
+export { locale };
+</script>

+ 0 - 0
src/api/config.ts


+ 38 - 0
src/api/index.ts

@@ -0,0 +1,38 @@
+import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
+import { message } from "ant-design-vue";
+
+import { HTTP } from "@/helper/enum";
+import { IResponse } from "@/types/api";
+
+const instance: AxiosInstance = axios.create({
+  baseURL: "/api",
+});
+
+instance.interceptors.request.use(
+  (config: AxiosRequestConfig): AxiosRequestConfig => {
+    return config;
+  }
+);
+
+instance.interceptors.response.use(
+  (
+    res: AxiosResponse<IResponse<any>>
+  ): Promise<AxiosResponse<IResponse<any>>> => {
+    let result = res.data;
+    let success = false;
+    switch (result.code) {
+      case HTTP.success:
+        success = true;
+        break;
+    }
+    return success ? Promise.resolve(result) : Promise.reject(result);
+  },
+  (error) => {
+    if (error?.response?.status === HTTP.serverError)
+      message.error("服务器开小差了,请稍后再试!");
+    if (axios.isCancel(error)) return Promise.resolve(error.message);
+    return Promise.reject(error);
+  }
+);
+
+export default instance;

BIN
src/assets/logo.png


+ 49 - 0
src/components/confirm-button/index.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="confirm-button">
+    <a-button :type="type"
+              @click.stop="onConfirm">{{label}}</a-button>
+  </div>
+</template>
+
+<script lang="ts">
+import { createVNode, defineComponent, inject } from "vue";
+import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
+
+import { ModalConfirmKey } from "@/plugins/injectionKey";
+
+const ConfirmButton = defineComponent({
+  props: {
+    label: String,
+    type: {
+      type: String,
+      default: "primary",
+    },
+    confirmTitle: {
+      type: String,
+      default: "请确认您的操作",
+    },
+    confirmContent: {
+      type: String,
+      default: "确定删除该内容吗?",
+    },
+  },
+  setup(props, { emit }) {
+    const confirm = inject(ModalConfirmKey)!;
+
+    const onConfirm = () => {
+      confirm({
+        icon: createVNode(ExclamationCircleOutlined),
+        title: props.confirmTitle,
+        content: props.confirmContent,
+        onOk: () => {
+          emit("click");
+        },
+      });
+    };
+
+    return { onConfirm };
+  },
+});
+
+export default ConfirmButton;
+</script>

+ 9 - 0
src/global.d.ts

@@ -0,0 +1,9 @@
+import { ModalFunc, ModalFuncProps } from "ant-design-vue/lib/modal/Modal";
+import { ComponentCustomProperties } from "vue";
+
+declare module "@vue/runtime-core" {
+  interface ComponentCustomProperties {
+    $copyText(text: string, container?: HTMLElement): Promise<Clipboard.Event>;
+    $confirm(options: ModalFuncProps): ModalFunc;
+  }
+}

+ 30 - 0
src/helper/cache.ts

@@ -0,0 +1,30 @@
+export default class CacheMap {
+  private maxLength: number;
+  private map: Map<string, any>;
+
+  constructor(max: number) {
+    this.maxLength = max || 50;
+    this.map = new Map();
+  }
+
+  get<T>(key: string): T | null {
+    if (this.map.has(key)) {
+      let mapVal = this.map.get(key);
+      this.map.delete(key);
+      this.map.set(key, mapVal);
+      return mapVal;
+    } else return null;
+  }
+
+  put<T>(key: string, val: T) {
+    if (this.map.has(key)) this.map.delete(key);
+    else {
+      // 达到最大长度时删除不常用的key
+      if (this.map.size === this.maxLength) {
+        let notCommandMap = this.map.keys().next().value;
+        this.map.delete(notCommandMap);
+      }
+    }
+    this.map.set(key, val);
+  }
+}

+ 4 - 0
src/helper/enum.ts

@@ -0,0 +1,4 @@
+export enum HTTP {
+  success = 0,
+  serverError = 500,
+}

+ 7 - 0
src/helper/index.ts

@@ -0,0 +1,7 @@
+export const listener = (
+  el: HTMLElement | Element,
+  cb: Function,
+  type: string = "click"
+) => {
+  el.addEventListener(type, (e) => cb(e), false);
+};

+ 9 - 0
src/hooks/useClipboard.ts

@@ -0,0 +1,9 @@
+import { clipboardKey } from "@/plugins/injectionKey";
+import { inject } from "vue";
+
+const useClipboard = () => {
+  const { $copyText } = inject(clipboardKey)!;
+  return $copyText;
+};
+
+export default useClipboard;

+ 7 - 0
src/hooks/useStore.ts

@@ -0,0 +1,7 @@
+import store, { Store } from "@/store";
+
+const useStore = (): Store => {
+  return store as Store;
+};
+
+export default useStore;

+ 12 - 0
src/main.ts

@@ -0,0 +1,12 @@
+import { createApp } from "vue";
+import App from "./App.vue";
+import router from "./router";
+import store from "./store";
+
+import install from "@/plugins/install";
+
+const app = install(createApp(App));
+
+app.use(store).use(router);
+
+router.isReady().then((_) => app.mount("#app"));

+ 7 - 0
src/plugins/injectionKey.ts

@@ -0,0 +1,7 @@
+import { ModalFunc } from "ant-design-vue/lib/modal/Modal";
+import { InjectionKey } from "vue";
+
+const clipboardKey: InjectionKey<ClipboardMethod> = Symbol();
+const confirmKey: InjectionKey<ModalFunc> = Symbol();
+
+export { clipboardKey, confirmKey as ModalConfirmKey };

+ 13 - 0
src/plugins/install.ts

@@ -0,0 +1,13 @@
+import { App } from "vue";
+import { Button, ConfigProvider, Modal } from "ant-design-vue";
+
+import { ModalConfirmKey } from "./injectionKey";
+
+const install = (app: App<Element>) => {
+  app.provide(ModalConfirmKey, Modal.confirm);
+  app.config.globalProperties.$confirm = Modal.confirm;
+
+  return app.use(ConfigProvider).use(Button);
+};
+
+export default install;

+ 113 - 0
src/plugins/vue-clipboard.ts

@@ -0,0 +1,113 @@
+import Clipboard from "clipboard";
+import { App, DirectiveBinding, VNode } from "vue";
+import { clipboardKey } from "./injectionKey";
+
+// clipboard config
+const VueClipboardConfig = {
+  autoSetContainer: false,
+  appendToBody: true,
+};
+
+// 枚举复制的状态
+enum status {
+  success = "success",
+  error = "error",
+}
+
+// 方法调用 返回promise
+const $copyText = (
+  text: string,
+  container?: HTMLElement
+): Promise<ClipboardJS.Event> => {
+  return new Promise((resolve, reject) => {
+    const fakeElement = document.createElement("button");
+    const clipboard = new Clipboard(fakeElement, {
+      text: () => text,
+      action: () => "copy",
+      container: typeof container === "object" ? container : document.body,
+    });
+
+    clipboard.on(status.success, (e: ClipboardJS.Event) => {
+      clipboard.destroy();
+      resolve(e);
+    });
+
+    clipboard.on(status.error, (e: ClipboardJS.Event) => {
+      clipboard.destroy();
+      reject(e);
+    });
+
+    if (VueClipboardConfig.appendToBody) document.body.appendChild(fakeElement);
+    fakeElement.click();
+    if (VueClipboardConfig.appendToBody) document.body.removeChild(fakeElement);
+  });
+};
+
+// v-direction bind
+const bind = (
+  el: ClipboardElement,
+  binding: DirectiveBinding,
+  vnode: VNode
+) => {
+  if (binding.arg === status.success) el._vClipboard_success = binding.value;
+  else if (binding.arg === status.error) el._vClipboard_error = binding.value;
+  else {
+    const clipboard = new Clipboard(el, {
+      text: () => binding.value,
+      action: () => (binding.arg === "cut" ? "cut" : "copy"),
+      container: VueClipboardConfig.autoSetContainer ? el : undefined,
+    });
+
+    clipboard.on(status.success, (e) => {
+      const cb = el._vClipboard_success;
+      cb && cb(e);
+    });
+
+    clipboard.on(status.error, (e) => {
+      const cb = el._vClipboard_error;
+      cb && cb(e);
+    });
+
+    el._vClipboard = clipboard;
+  }
+};
+
+// v-direction update
+const update = (el: ClipboardElement, binding: DirectiveBinding) => {
+  if (binding.arg === status.success) el._vClipboard_success = binding.value;
+  else if (binding.arg === status.error) el._vClipboard_error = binding.value;
+  else {
+    (<ClipboardJS.Options>el._vClipboard).text = () => binding.value;
+    (<ClipboardJS.Options>el._vClipboard).action = () =>
+      binding.arg === "cut" ? "cut" : "copy";
+  }
+};
+
+// v-direction unbind
+const unbind = (el: ClipboardElement, binding: DirectiveBinding) => {
+  if (binding.arg === status.success) delete el._vClipboard_success;
+  else if (binding.arg === status.error) delete el._vClipboard_error;
+  else {
+    (<ClipboardJS>el._vClipboard).destroy();
+    delete el._vClipboard;
+  }
+};
+
+const VueClipboard3 = {
+  install: (app: App<Element>) => {
+    // const copySymbol = Symbol();
+    // provide注入 方便组合api调用
+    app.provide(clipboardKey, { $copyText });
+    app.config.globalProperties.$clipboardConfig = VueClipboardConfig;
+    app.config.globalProperties.$copyText = $copyText;
+
+    app.directive("clipboard", {
+      beforeMount: bind,
+      updated: update,
+      unmounted: unbind,
+    });
+  },
+  config: VueClipboardConfig,
+};
+
+export default VueClipboard3;

+ 25 - 0
src/router/index.ts

@@ -0,0 +1,25 @@
+import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
+import Home from '../views/Home.vue'
+
+const routes: Array<RouteRecordRaw> = [
+  {
+    path: '/',
+    name: 'Home',
+    component: Home
+  },
+  {
+    path: '/about',
+    name: 'About',
+    // route level code-splitting
+    // this generates a separate chunk (about.[hash].js) for this route
+    // which is lazy-loaded when the route is visited.
+    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
+  }
+]
+
+const router = createRouter({
+  history: createWebHistory(process.env.BASE_URL),
+  routes
+})
+
+export default router

+ 10 - 0
src/scss/antd.scss

@@ -0,0 +1,10 @@
+.ant-table-tbody > tr > td,
+.ant-table-thead > tr > th {
+  border-bottom: none !important;
+}
+
+.ant-form-item {
+  &:last-child {
+    margin-bottom: 0;
+  }
+}

+ 4 - 0
src/scss/config.scss

@@ -0,0 +1,4 @@
+$namespace: "x";
+$element-separator: "__";
+$modifier-separator: "--";
+$state-prefix: "is-";

+ 44 - 0
src/scss/function.scss

@@ -0,0 +1,44 @@
+@import "./config";
+
+/* BEM support Func
+ -------------------------- */
+@function selectorToString($selector) {
+  $selector: inspect($selector);
+  $selector: str-slice($selector, 2, -2);
+  @return $selector;
+}
+
+@function containsModifier($selector) {
+  $selector: selectorToString($selector);
+
+  @if str-index($selector, $modifier-separator) {
+    @return true;
+  } @else {
+    @return false;
+  }
+}
+
+@function containWhenFlag($selector) {
+  $selector: selectorToString($selector);
+
+  @if str-index($selector, "." + $state-prefix) {
+    @return true;
+  } @else {
+    @return false;
+  }
+}
+
+@function containPseudoClass($selector) {
+  $selector: selectorToString($selector);
+
+  @if str-index($selector, ":") {
+    @return true;
+  } @else {
+    @return false;
+  }
+}
+
+@function hitAllSpecialNestRule($selector) {
+  @return containsModifier($selector) or containWhenFlag($selector) or
+    containPseudoClass($selector);
+}

+ 16 - 0
src/scss/index.scss

@@ -0,0 +1,16 @@
+// @import "./common.scss";
+@import "./transition.scss";
+@import "./antd.scss";
+// @import "./components/index.scss";
+// @import "./views/index.scss";
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}

+ 117 - 0
src/scss/mixins.scss

@@ -0,0 +1,117 @@
+@import "./function.scss";
+
+@mixin flex($space: space-between, $direction: row, $align: center) {
+  display: flex;
+  align-items: $align;
+  flex-direction: $direction;
+  justify-content: $space;
+}
+
+@mixin multi-line($line) {
+  -webkit-line-clamp: $line;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+@mixin single-line($line-height: 1.5) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  line-height: $line-height;
+}
+
+@mixin extend-click() {
+  position: relative;
+
+  &::before {
+    content: "";
+    position: absolute;
+    top: -10px;
+    left: -10px;
+    right: -10px;
+    bottom: -10px;
+  }
+}
+
+/*-------   bem    ---------*/
+@mixin b($block) {
+  $B: $namespace + "-" + $block !global;
+
+  .#{$B} {
+    @content;
+  }
+}
+
+@mixin e($element) {
+  $E: $element !global;
+  $selector: &;
+  $currentSelector: "";
+  @each $unit in $element {
+    $currentSelector: #{$currentSelector +
+      "." +
+      $B +
+      $element-separator +
+      $unit +
+      ","};
+  }
+
+  @if hitAllSpecialNestRule($selector) {
+    @at-root {
+      #{$selector} {
+        #{$currentSelector} {
+          @content;
+        }
+      }
+    }
+  } @else {
+    @at-root {
+      #{$currentSelector} {
+        @content;
+      }
+    }
+  }
+}
+
+@mixin m($modifier) {
+  $selector: &;
+  $currentSelector: "";
+  @each $unit in $modifier {
+    $currentSelector: #{$currentSelector +
+      & +
+      $modifier-separator +
+      $unit +
+      ","};
+  }
+
+  @at-root {
+    #{$currentSelector} {
+      @content;
+    }
+  }
+}
+
+@mixin meb($modifier: false, $element: $E, $block: $B) {
+  $selector: &;
+  $modifierCombo: "";
+
+  @if $modifier {
+    $modifierCombo: $modifier-separator + $modifier;
+  }
+
+  @at-root {
+    #{$selector} {
+      .#{$block + $element-separator + $element + $modifierCombo} {
+        @content;
+      }
+    }
+  }
+}
+
+@mixin when($state) {
+  @at-root {
+    &.#{$state-prefix + $state} {
+      @content;
+    }
+  }
+}

+ 48 - 0
src/scss/transition.scss

@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all 0.5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all 0.5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all 0.5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}

+ 17 - 0
src/scss/variables.scss

@@ -0,0 +1,17 @@
+// 导入一些常用mixin
+@import "./mixins.scss";
+
+// 变量
+$font-size-small: 12px;
+$font-size-base: 13px;
+$font-size-medium: 14px;
+$font-size-big: 15px;
+$font-size-bigger: 16px;
+$font-size-large: 18px;
+
+$theme-color: #39a4ff;
+$theme-color_2: #38b2ff;
+
+$red: #ff6d6d;
+
+$navBarWidth: 240px;

+ 22 - 0
src/shims-vue.d.ts

@@ -0,0 +1,22 @@
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}
+
+declare module "ant-design-vue/lib/locale-provider/zh_CN";
+
+// 定义clipboard注入的元素
+interface ClipboardElement extends HTMLElement {
+  _vClipboard_success: any;
+  _vClipboard_error: any;
+  _vClipboard: ClipboardJS | ClipboardJS.Options | undefined;
+}
+
+// 定义provide提供的方法
+interface ClipboardMethod {
+  $copyText(
+    string: string,
+    container?: HTMLElement
+  ): Promise<ClipboardJS.Event>;
+}

+ 21 - 0
src/store/index.ts

@@ -0,0 +1,21 @@
+import { createStore, createLogger } from "vuex";
+
+import app, { State as appState, Store as appStore } from "./modules/app";
+
+const debug = process.env.NODE_ENV !== "production";
+
+export type RootState = {
+  app: appState;
+};
+
+export type Store = appStore<Pick<RootState, "app">>;
+
+const store = createStore({
+  modules: {
+    app,
+  },
+  strict: debug,
+  plugins: debug ? [createLogger()] : [],
+});
+
+export default store;

+ 36 - 0
src/store/modules/app/_module.ts

@@ -0,0 +1,36 @@
+import {
+  ActionContext,
+  Store as VuexStore,
+  CommitOptions,
+  DispatchOptions,
+} from "vuex";
+import { State, Actions, Getters, Mutations } from "./_type";
+import { RootState } from "@/store";
+
+export type AugmentedActionContext = {
+  commit<M extends keyof Mutations>(
+    key: M,
+    payload: Parameters<Mutations[M]>[1]
+  ): ReturnType<Mutations[M]>;
+} & Omit<ActionContext<State, RootState>, "commit">;
+
+export type Store<S = State> = Omit<
+  VuexStore<S>,
+  "commit" | "dispatch" | "getters"
+> & {
+  commit<M extends keyof Mutations, P extends Parameters<Mutations[M]>[1]>(
+    key: M,
+    payload: P,
+    options?: CommitOptions
+  ): ReturnType<Mutations[M]>;
+} & {
+  dispatch<A extends keyof Actions>(
+    key: A,
+    payload: Parameters<Actions[A]>[1],
+    options?: DispatchOptions
+  ): ReturnType<Actions[A]>;
+} & {
+  getters: {
+    [G in keyof Getters]: ReturnType<Getters[G]>;
+  };
+};

+ 21 - 0
src/store/modules/app/_type.ts

@@ -0,0 +1,21 @@
+import CacheMap from "@/helper/cache";
+import { ActionType } from "./actions";
+import { MutationType } from "./mutations";
+import { AugmentedActionContext } from "./_module";
+
+type State = {};
+
+type Mutations<S = State> = {
+  // [MutationType.SET_AGE_VALUE](state: S, age: number): void;
+};
+
+type Actions = {
+  // [ActionType.SET_AGE_VALUE](
+  //   { commit }: AugmentedActionContext,
+  //   age: number
+  // ): void;
+};
+
+type Getters = {};
+
+export { State, Mutations, Actions, Getters };

+ 23 - 0
src/store/modules/app/actions.ts

@@ -0,0 +1,23 @@
+import { ActionTree } from "vuex";
+import { RootState } from "@/store";
+import { AugmentedActionContext } from "./_module";
+import { State } from "./_type";
+import { MutationType } from "./mutations";
+
+export enum ActionType {}
+// SET_AGE_VALUE = "SET_AGE_VALUE",
+
+export type Actions = {
+  // [ActionType.SET_AGE_VALUE](
+  //   { commit }: AugmentedActionContext,
+  //   age: number
+  // ): void;
+};
+
+const actions: ActionTree<State, RootState> & Actions = {
+  // [ActionType.SET_AGE_VALUE]({ commit }, age: number) {
+  //   commit(MutationType.SET_AGE_VALUE, age);
+  // },
+};
+
+export default actions;

+ 13 - 0
src/store/modules/app/getters.ts

@@ -0,0 +1,13 @@
+import { GetterTree } from "vuex";
+import { RootState } from "@/store";
+import { State } from "./_type";
+
+export type Getters = {
+  // age(state: State): number;
+};
+
+const getters: GetterTree<State, RootState> & Getters = {
+  // age: (state) => state.age
+};
+
+export default getters;

+ 23 - 0
src/store/modules/app/index.ts

@@ -0,0 +1,23 @@
+import { Module } from "vuex";
+import state from "./state";
+import mutations from "./mutations";
+import actions from "./actions";
+import getters from "./getters";
+
+import { State as _State } from "./_type";
+import { Store } from "./_module";
+
+import { RootState } from "@/store";
+
+const appModule: Module<State, RootState> = {
+  // namespaced: true,
+  state,
+  getters,
+  mutations,
+  actions,
+};
+
+export type State = _State;
+export { Store };
+
+export default appModule;

+ 17 - 0
src/store/modules/app/mutations.ts

@@ -0,0 +1,17 @@
+import { MutationTree } from "vuex";
+import { State } from "./_type";
+
+export enum MutationType {}
+// SET_AGE_VALUE = "SET_AGE_VALUE",
+
+export type Mutations<S = State> = {
+  // [MutationType.SET_AGE_VALUE](state: S, age: number): void;
+};
+
+const mutations: MutationTree<State> & Mutations = {
+  // [MutationType.SET_AGE_VALUE](state, age: number) {
+  //   state.age = age;
+  // },
+};
+
+export default mutations;

+ 8 - 0
src/store/modules/app/state.ts

@@ -0,0 +1,8 @@
+import { State } from "./_type";
+
+const state: State = {
+  age: 12,
+  name: "张三",
+};
+
+export default state;

+ 5 - 0
src/types/api.d.ts

@@ -0,0 +1,5 @@
+export interface IResponse<T> extends Promise<T> {
+  code: number;
+  data?: T;
+  msg: string;
+}

+ 5 - 0
src/views/About.vue

@@ -0,0 +1,5 @@
+<template>
+  <div class="about">
+    <h1>This is an about page</h1>
+  </div>
+</template>

+ 27 - 0
src/views/Home.vue

@@ -0,0 +1,27 @@
+<template>
+  <div class="home">
+    <a-button type="primary">按钮</a-button>
+    <confirm-button label="点我"
+                    @click="add" />
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from "vue";
+import ConfirmButton from "@/components/confirm-button/index.vue";
+
+export default defineComponent({
+  name: "Home",
+  components: {
+    ConfirmButton,
+  },
+  setup() {
+    const title = ref("test");
+    const add = () => {
+      console.log("on confirm");
+    };
+
+    return { title, add };
+  },
+});
+</script>

+ 39 - 0
tsconfig.json

@@ -0,0 +1,39 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "importHelpers": true,
+    "moduleResolution": "node",
+    "skipLibCheck": 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"
+  ]
+}

+ 53 - 0
vue.config.js

@@ -0,0 +1,53 @@
+module.exports = {
+  publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
+  devServer: {
+    // * 接口跨域处理
+    proxy: {
+      "/api": {
+        target: "https://channelpre2.aizhuishu.com",
+        changeOrigin: true,
+      },
+      "/channel/img": {
+        target: "https://channelpre2.aizhuishu.com",
+        changeOrigin: true,
+      },
+    },
+    disableHostCheck: true,
+  },
+  css: {
+    sourceMap: false,
+    loaderOptions: {
+      scss: {
+        prependData: `@import "~@/scss/variables.scss";`,
+      },
+      less: {
+        lessOptions: {
+          modifyVars: {
+            "primary-color": "#39a4ff",
+            "link-color": "#39a4ff",
+          },
+          javascriptEnabled: true,
+        },
+      },
+    },
+  },
+  configureWebpack: {
+    resolve: {
+      extensions: [".js", ".vue", ".json", ".ts"],
+    },
+  },
+  chainWebpack: (config) => {
+    // * 移除prefetch和preload
+    config.plugins.delete("prefetch");
+    config.plugins.delete("preload");
+    if (process.env.NODE_ENV === "production") {
+      // config.entry("index").add("babel-polyfill");
+      config.plugin("html").tap((args) => {
+        // 加上属性引号
+        args[0].minify.removeAttributeQuotes = false;
+        // args[0].cdn = cdns.build;
+        return args;
+      });
+    }
+  },
+};

File diff suppressed because it is too large
+ 8798 - 0
yarn.lock