YY 2 years ago
parent
commit
d5e5dc3acd

+ 19 - 2
.eslintrc.cjs

@@ -3,13 +3,30 @@ require('@rushstack/eslint-patch/modern-module-resolution')
 
 module.exports = {
   root: true,
-  'extends': [
+  extends: [
     'plugin:vue/vue3-essential',
     'eslint:recommended',
     '@vue/eslint-config-typescript',
-    '@vue/eslint-config-prettier/skip-formatting'
+    '@vue/eslint-config-prettier'
   ],
   parserOptions: {
     ecmaVersion: 'latest'
+  },
+  rules: {
+    'max-len': [
+      'warn',
+      {
+        code: 10000
+      }
+    ],
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160
+      }
+    ]
   }
 }

+ 4 - 6
.prettierrc.json

@@ -1,8 +1,6 @@
 {
-  "$schema": "https://json.schemastore.org/prettierrc",
-  "semi": false,
-  "tabWidth": 2,
   "singleQuote": true,
-  "printWidth": 100,
-  "trailingComma": "none"
-}
+  "printWidth": 160,
+  "bracketSpacing": true,
+  "endOfLine": "lf"
+}

File diff suppressed because it is too large
+ 2494 - 91
package-lock.json


+ 12 - 0
package.json

@@ -12,7 +12,17 @@
     "format": "prettier --write src/"
   },
   "dependencies": {
+    "@element-plus/icons-vue": "^2.1.0",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^5.1.12",
+    "animate.css": "^4.1.1",
+    "axios": "^1.3.4",
+    "element-plus": "^2.3.0",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "naf-core": "^0.1.2",
     "pinia": "^2.0.32",
+    "sortablejs": "^1.15.0",
     "vue": "^3.2.47",
     "vue-router": "^4.1.6"
   },
@@ -27,6 +37,8 @@
     "eslint-plugin-vue": "^9.9.0",
     "npm-run-all": "^4.1.5",
     "prettier": "^2.8.4",
+    "sass": "^1.59.3",
+    "sass-loader": "^13.2.0",
     "typescript": "~4.8.4",
     "vite": "^4.1.4",
     "vue-tsc": "^1.2.0"

+ 19 - 76
src/App.vue

@@ -1,85 +1,28 @@
-<script setup lang="ts">
-import { RouterLink, RouterView } from 'vue-router'
-import HelloWorld from './components/HelloWorld.vue'
-</script>
-
 <template>
-  <header>
-    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
-
-    <div class="wrapper">
-      <HelloWorld msg="You did it!" />
-
-      <nav>
-        <RouterLink to="/">Home</RouterLink>
-        <RouterLink to="/about">About</RouterLink>
-      </nav>
-    </div>
-  </header>
-
-  <RouterView />
+  <div>
+    <Suspense>
+      <router-view />
+    </Suspense>
+  </div>
 </template>
 
-<style scoped>
-header {
-  line-height: 1.5;
-  max-height: 100vh;
-}
-
-.logo {
-  display: block;
-  margin: 0 auto 2rem;
-}
-
-nav {
-  width: 100%;
-  font-size: 12px;
-  text-align: center;
-  margin-top: 2rem;
-}
-
-nav a.router-link-exact-active {
-  color: var(--color-text);
-}
+<script lang="ts" setup></script>
 
-nav a.router-link-exact-active:hover {
-  background-color: transparent;
+<style>
+body {
+  margin: 0;
 }
-
-nav a {
-  display: inline-block;
-  padding: 0 1rem;
-  border-left: 1px solid var(--color-border);
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
 }
-
-nav a:first-of-type {
-  border: 0;
+p {
+  margin: 0;
+  padding: 0;
 }
-
-@media (min-width: 1024px) {
-  header {
-    display: flex;
-    place-items: center;
-    padding-right: calc(var(--section-gap) / 2);
-  }
-
-  .logo {
-    margin: 0 2rem 0 0;
-  }
-
-  header .wrapper {
-    display: flex;
-    place-items: flex-start;
-    flex-wrap: wrap;
-  }
-
-  nav {
-    text-align: left;
-    margin-left: -1rem;
-    font-size: 1rem;
-
-    padding: 1rem 0;
-    margin-top: 1rem;
-  }
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 </style>

+ 5 - 63
src/assets/base.css

@@ -1,74 +1,16 @@
 /* color palette from <https://github.com/vuejs/theme> */
 :root {
-  --vt-c-white: #ffffff;
-  --vt-c-white-soft: #f8f8f8;
-  --vt-c-white-mute: #f2f2f2;
-
-  --vt-c-black: #181818;
-  --vt-c-black-soft: #222222;
-  --vt-c-black-mute: #282828;
-
-  --vt-c-indigo: #2c3e50;
-
-  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
-  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
-  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
-  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
-
-  --vt-c-text-light-1: var(--vt-c-indigo);
-  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
-  --vt-c-text-dark-1: var(--vt-c-white);
-  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+  --vt-c-white: #f2ebd9;
+  --vt-c-black: #66363c;
 }
 
-/* semantic color variables for this project */
 :root {
   --color-background: var(--vt-c-white);
-  --color-background-soft: var(--vt-c-white-soft);
-  --color-background-mute: var(--vt-c-white-mute);
-
-  --color-border: var(--vt-c-divider-light-2);
-  --color-border-hover: var(--vt-c-divider-light-1);
-
-  --color-heading: var(--vt-c-text-light-1);
-  --color-text: var(--vt-c-text-light-1);
-
-  --section-gap: 160px;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --color-background: var(--vt-c-black);
-    --color-background-soft: var(--vt-c-black-soft);
-    --color-background-mute: var(--vt-c-black-mute);
-
-    --color-border: var(--vt-c-divider-dark-2);
-    --color-border-hover: var(--vt-c-divider-dark-1);
-
-    --color-heading: var(--vt-c-text-dark-1);
-    --color-text: var(--vt-c-text-dark-2);
-  }
-}
-
-*,
-*::before,
-*::after {
-  box-sizing: border-box;
-  margin: 0;
-  position: relative;
-  font-weight: normal;
 }
 
 body {
   min-height: 100vh;
-  color: var(--color-text);
-  background: var(--color-background);
-  transition: color 0.5s, background-color 0.5s;
-  line-height: 1.6;
-  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
-    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-  font-size: 15px;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+  color: var(--vt-c-black);
+  background: var(--vt-c-white);
+
 }

BIN
src/assets/image/login.jpg


BIN
src/assets/image/logo.png


+ 1 - 31
src/assets/main.css

@@ -1,35 +1,5 @@
 @import './base.css';
 
 #app {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 2rem;
-
-  font-weight: normal;
-}
-
-a,
-.green {
-  text-decoration: none;
-  color: hsla(160, 100%, 37%, 1);
-  transition: 0.4s;
-}
-
-@media (hover: hover) {
-  a:hover {
-    background-color: hsla(160, 100%, 37%, 0.2);
-  }
-}
-
-@media (min-width: 1024px) {
-  body {
-    display: flex;
-    place-items: center;
-  }
-
-  #app {
-    display: grid;
-    grid-template-columns: 1fr 1fr;
-    padding: 0 2rem;
-  }
+  width: 100%;
 }

+ 2 - 0
src/assets/style/mixin.scss

@@ -0,0 +1,2 @@
+$red:#66363c;
+$white:#f2ebd9;

+ 0 - 40
src/components/HelloWorld.vue

@@ -1,40 +0,0 @@
-<script setup lang="ts">
-defineProps<{
-  msg: string
-}>()
-</script>
-
-<template>
-  <div class="greetings">
-    <h1 class="green">{{ msg }}</h1>
-    <h3>
-      You’ve successfully created a project with
-      <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
-      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
-    </h3>
-  </div>
-</template>
-
-<style scoped>
-h1 {
-  font-weight: 500;
-  font-size: 2.6rem;
-  top: -10px;
-}
-
-h3 {
-  font-size: 1.2rem;
-}
-
-.greetings h1,
-.greetings h3 {
-  text-align: center;
-}
-
-@media (min-width: 1024px) {
-  .greetings h1,
-  .greetings h3 {
-    text-align: left;
-  }
-}
-</style>

+ 0 - 86
src/components/TheWelcome.vue

@@ -1,86 +0,0 @@
-<script setup lang="ts">
-import WelcomeItem from './WelcomeItem.vue'
-import DocumentationIcon from './icons/IconDocumentation.vue'
-import ToolingIcon from './icons/IconTooling.vue'
-import EcosystemIcon from './icons/IconEcosystem.vue'
-import CommunityIcon from './icons/IconCommunity.vue'
-import SupportIcon from './icons/IconSupport.vue'
-</script>
-
-<template>
-  <WelcomeItem>
-    <template #icon>
-      <DocumentationIcon />
-    </template>
-    <template #heading>Documentation</template>
-
-    Vue’s
-    <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
-    provides you with all information you need to get started.
-  </WelcomeItem>
-
-  <WelcomeItem>
-    <template #icon>
-      <ToolingIcon />
-    </template>
-    <template #heading>Tooling</template>
-
-    This project is served and bundled with
-    <a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
-    recommended IDE setup is
-    <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
-    <a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
-    you need to test your components and web pages, check out
-    <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
-    <a href="https://on.cypress.io/component" target="_blank">Cypress Component Testing</a>.
-
-    <br />
-
-    More instructions are available in <code>README.md</code>.
-  </WelcomeItem>
-
-  <WelcomeItem>
-    <template #icon>
-      <EcosystemIcon />
-    </template>
-    <template #heading>Ecosystem</template>
-
-    Get official tools and libraries for your project:
-    <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
-    <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
-    <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
-    <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
-    you need more resources, we suggest paying
-    <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
-    a visit.
-  </WelcomeItem>
-
-  <WelcomeItem>
-    <template #icon>
-      <CommunityIcon />
-    </template>
-    <template #heading>Community</template>
-
-    Got stuck? Ask your question on
-    <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
-    Discord server, or
-    <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
-      >StackOverflow</a
-    >. You should also subscribe to
-    <a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
-    the official
-    <a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
-    twitter account for latest news in the Vue world.
-  </WelcomeItem>
-
-  <WelcomeItem>
-    <template #icon>
-      <SupportIcon />
-    </template>
-    <template #heading>Support Vue</template>
-
-    As an independent project, Vue relies on community backing for its sustainability. You can help
-    us by
-    <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
-  </WelcomeItem>
-</template>

+ 0 - 86
src/components/WelcomeItem.vue

@@ -1,86 +0,0 @@
-<template>
-  <div class="item">
-    <i>
-      <slot name="icon"></slot>
-    </i>
-    <div class="details">
-      <h3>
-        <slot name="heading"></slot>
-      </h3>
-      <slot></slot>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-.item {
-  margin-top: 2rem;
-  display: flex;
-}
-
-.details {
-  flex: 1;
-  margin-left: 1rem;
-}
-
-i {
-  display: flex;
-  place-items: center;
-  place-content: center;
-  width: 32px;
-  height: 32px;
-
-  color: var(--color-text);
-}
-
-h3 {
-  font-size: 1.2rem;
-  font-weight: 500;
-  margin-bottom: 0.4rem;
-  color: var(--color-heading);
-}
-
-@media (min-width: 1024px) {
-  .item {
-    margin-top: 0;
-    padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
-  }
-
-  i {
-    top: calc(50% - 25px);
-    left: -26px;
-    position: absolute;
-    border: 1px solid var(--color-border);
-    background: var(--color-background);
-    border-radius: 8px;
-    width: 50px;
-    height: 50px;
-  }
-
-  .item:before {
-    content: ' ';
-    border-left: 1px solid var(--color-border);
-    position: absolute;
-    left: 0;
-    bottom: calc(50% + 25px);
-    height: calc(50% - 25px);
-  }
-
-  .item:after {
-    content: ' ';
-    border-left: 1px solid var(--color-border);
-    position: absolute;
-    left: 0;
-    top: calc(50% + 25px);
-    height: calc(50% - 25px);
-  }
-
-  .item:first-of-type:before {
-    display: none;
-  }
-
-  .item:last-of-type:after {
-    display: none;
-  }
-}
-</style>

+ 14 - 0
src/components/btn-1.vue

@@ -0,0 +1,14 @@
+<template>
+  <div id="btn">
+    <el-button type="success" @click="toAdd()">新增</el-button>
+    <el-button type="primary" disabled>修改</el-button>
+    <el-button type="danger" disabled>删除</el-button>
+    <el-button type="warning" disabled>导出</el-button>
+  </div>
+</template>
+<script setup lang="ts">
+const emit = defineEmits(['toAdd']);
+const toAdd = () => {
+  emit('toAdd');
+};
+</script>

+ 220 - 0
src/components/c-form.vue

@@ -0,0 +1,220 @@
+<template>
+  <div id="c-form">
+    <el-row type="flex" justify="space-around">
+      <el-col :span="span">
+        <el-form ref="formRef" :model="form" :rules="rules" :label-width="labelWidth" class="form" @submit.prevent>
+          <template v-for="(item, index) in fields">
+            <el-form-item v-if="display(item)" :key="'form-field-' + index" :label="getField('label', item)" :prop="item.model" :required="item.required">
+              <template v-if="!item.custom">
+                <template v-if="item.type === 'textarea'">
+                  <el-input
+                    clearable
+                    v-model="form[item.model]"
+                    :type="item.type"
+                    :placeholder="getField('placeholder', item)"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    show-word-limit
+                    :disabled="item.readonly"
+                  ></el-input>
+                </template>
+                <template v-else-if="item.type === 'numbers'">
+                  <el-input-number
+                    v-model="form[item.model]"
+                    :placeholder="getField('placeholder', item)"
+                    @change="dataChange(item.model)"
+                    style="width: 100%"
+                    :disabled="item.readonly"
+                  />
+                </template>
+                <template v-else-if="item.type === 'radio'">
+                  <el-radio-group v-model="form[item.model]" :type="item.type" v-bind="item.options" @change="dataChange(item.model)" :disabled="item.readonly">
+                    <slot :name="item.model" v-bind="{ item }"></slot>
+                  </el-radio-group>
+                </template>
+                <template v-else-if="item.type === 'checkbox'">
+                  <el-checkbox-group v-model="form[item.model]" :type="item.type" v-bind="item.options" :disabled="item.readonly">
+                    <slot :name="item.model" v-bind="{ item }"></slot>
+                  </el-checkbox-group>
+                </template>
+                <template v-else-if="item.type === 'select'">
+                  <el-select
+                    clearable
+                    filterable
+                    v-model="form[item.model]"
+                    :type="item.type"
+                    :placeholder="getField('selectplaceholder', item)"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    style="width: 100%"
+                    :disabled="item.readonly"
+                  >
+                    <slot :name="item.model" v-bind="{ item }"></slot>
+                  </el-select>
+                </template>
+                <template v-else-if="item.type === 'selectMany'">
+                  <el-select
+                    filterable
+                    clearable
+                    multiple
+                    collapse-tags
+                    v-model="form[item.model]"
+                    :type="item.type"
+                    :placeholder="getField('selectplaceholder', item)"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    style="width: 100%"
+                    :disabled="item.readonly"
+                  >
+                    <slot :name="item.model" v-bind="{ item }"></slot>
+                  </el-select>
+                </template>
+                <template
+                  v-else-if="
+                    item.type === `year` ||
+                    item.type == 'month' ||
+                    item.type == 'date' ||
+                    item.type == 'daterange' ||
+                    item.type == 'datetime' ||
+                    item.type == 'datetimerange'
+                  "
+                >
+                  <el-date-picker
+                    v-model="form[item.model]"
+                    :type="item.type"
+                    :placeholder="getField('selectplaceholder', item)"
+                    :format="getDateFormat(item.type)"
+                    :value-format="getDateFormat(item.type)"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    range-separator="至"
+                    style="width: 100%"
+                    :disabled="item.readonly"
+                  >
+                  </el-date-picker>
+                </template>
+                <template v-else-if="item.type === `time`">
+                  <el-time-picker
+                    v-model="form[item.model]"
+                    :placeholder="getField('selectplaceholder', item)"
+                    :format="getDateFormat(item.type)"
+                    :value-format="getDateFormat(item.type)"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    style="width: 100%"
+                    :disabled="item.readonly"
+                  >
+                  </el-time-picker>
+                </template>
+                <template v-else>
+                  <el-input
+                    clearable
+                    v-model="form[item.model]"
+                    :type="getField('type', item)"
+                    :placeholder="getField('placeholder', item)"
+                    :show-password="getField('type', item) === 'password'"
+                    v-bind="item.options"
+                    @change="dataChange(item.model)"
+                    :disabled="item.readonly"
+                  ></el-input>
+                </template>
+              </template>
+              <template v-else>
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </template>
+            </el-form-item>
+          </template>
+          <el-col :span="24" label="" class="btn" v-if="isSave">
+            <slot name="submit">
+              <el-button type="primary" @click="save(formRef)">{{ submitText }}</el-button>
+            </slot>
+          </el-col>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, toRefs } from 'vue';
+import type { FormInstance } from 'element-plus';
+import _ from 'lodash';
+// #region 传递
+interface fieldsItem {
+  model: string;
+  type: string;
+  readonly: string;
+  options: object;
+  custom: string;
+  required: string;
+  limit: number | undefined;
+  url: string;
+}
+const props = defineProps({
+  fields: { type: Array<fieldsItem>, default: () => [{ readonly: false }] },
+  rules: { type: Object, default: () => {} },
+  labelWidth: { type: String, default: '120px' },
+  submitText: { type: String, default: '保存' },
+  form: { type: Object, default: () => {} },
+  reset: { type: Boolean, default: false },
+  isSave: { type: Boolean, default: true },
+  span: { type: Number, default: 24 }, // 限制两侧的距离,24就是整行全用
+});
+const { fields } = toRefs(props);
+const { rules } = toRefs(props);
+const { labelWidth } = toRefs(props);
+const { submitText } = toRefs(props);
+const { form } = toRefs(props);
+const { reset } = toRefs(props);
+const { isSave } = toRefs(props);
+const { span } = toRefs(props);
+const formRef = ref<FormInstance>();
+const getField = (item: string, data: any) => {
+  let res: string | null | boolean = _.get(data, item, null);
+  if (item === 'type') res = res === null ? `text` : res;
+  if (item === 'placeholder') res = res === null ? `请输入${data.label}` : res;
+  if (item === `selectplaceholder`) res = res === null ? `请选择${data.label}` : res;
+  if (item === 'required') res = res === null ? false : res;
+  if (item === `error`) res = res === null ? `${data.label}错误` : res;
+  return res;
+};
+
+const getDateFormat = (e: string) => {
+  if (e === 'year') return 'YYYY';
+  if (e === 'month') return 'MM';
+  if (e === 'date') return 'YYYY-MM-DD';
+  if (e === 'daterange') return 'YYYY-MM-DD';
+  if (e === 'datetime') return 'YYYY-MM-DD HH:mm:ss';
+  if (e === 'datetimerange') return 'YYYY-MM-DD HH:mm:ss';
+  if (e === 'time') return 'HH:mm:ss';
+};
+const emit = defineEmits(['save', 'dataChange']);
+const clear = ref<any>();
+// 提交
+const save = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      emit('save', form.value);
+      if (reset.value) clear.value.resetFields();
+    } else {
+      console.log('error submit!', fields);
+    }
+  });
+};
+const display = (field: any) => {
+  let dis = _.get(field, `display`);
+  if (!_.isFunction(dis)) return true;
+  else return dis(field, form);
+};
+const dataChange = (model: string) => {
+  const value = form.value[model];
+  emit('dataChange', { model, value });
+};
+</script>
+
+<style scoped>
+.btn {
+  text-align: center;
+}
+</style>

+ 332 - 0
src/components/c-table.vue

@@ -0,0 +1,332 @@
+<template>
+  <div id="c-table">
+    <el-table
+      ref="table"
+      :row-key="rowKey"
+      :data="data"
+      border
+      stripe
+      :max-height="height !== null ? height : ''"
+      @select="handleSelectionChange"
+      @select-all="handleSelectAll"
+      :show-summary="useSum"
+      @row-click="rowClick"
+      :summary-method="computedSum"
+    >
+      <el-table-column type="selection" width="55" v-if="select" :prop="rowKey" :reserve-selection="true"> </el-table-column>
+      <template v-for="(item, index) in fields">
+        <template v-if="item.custom">
+          <el-table-column :key="index" align="center" :label="item.label" v-bind="item.options" :show-overflow-tooltip="item.showTip || true">
+            <template v-slot="{ row }">
+              <slot :name="item.model" v-bind="{ item, row }"></slot>
+            </template>
+          </el-table-column>
+        </template>
+        <template v-else>
+          <el-table-column
+            :key="index"
+            align="center"
+            :label="item.label"
+            :prop="item.model"
+            :formatter="toFormatter"
+            :sortable="true"
+            v-bind="item.options"
+            :show-overflow-tooltip="item.showTip === false ? item.showTip : true"
+          >
+          </el-table-column>
+        </template>
+      </template>
+      <template v-if="opera.length > 0">
+        <el-table-column label="操作" align="center" :width="operaWidth">
+          <template v-slot="{ row, $index }">
+            <template v-for="(item, index) in opera">
+              <template v-if="display(item, row)">
+                <template v-if="vOpera">
+                  <el-link
+                    v-opera="item.method"
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="small"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+                <template v-else>
+                  <el-link
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="small"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+              </template>
+            </template>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+    <el-row type="flex" align="middle" justify="end" v-if="usePage">
+      <el-col :span="24" class="page">
+        <el-pagination
+          background
+          layout="sizes,total, prev, pager, next"
+          :page-sizes="[10, 20, 50, 100, 200]"
+          :total="total"
+          :page-size="limit"
+          v-model:current-page="currentPage"
+          @current-change="changePage"
+          @size-change="sizeChange"
+        >
+        </el-pagination>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script setup lang="ts">
+import type { Ref } from 'vue';
+import { ref, toRefs, nextTick, getCurrentInstance } from 'vue';
+import { ElMessageBox } from 'element-plus';
+
+import _ from 'lodash';
+const { proxy } = getCurrentInstance() as any;
+
+// #region 传递
+
+interface fieldsItem {
+  custom: string;
+  label: string;
+  options: string;
+  showTip: boolean;
+  model: string;
+}
+interface operaItem {
+  method: string;
+  model: string;
+  type: string;
+  icon: string;
+  confirmWord: string;
+  label: string;
+  confirm: boolean;
+  methodZh: string;
+}
+interface dataItem {
+  _id?: string;
+}
+
+const props = defineProps({
+  fields: { type: Array<fieldsItem>, required: true },
+  data: { type: Array<dataItem>, required: true },
+  opera: { type: Array<operaItem>, default: () => [] },
+  rowKey: { type: String, default: '_id' },
+  select: { type: Boolean, default: false },
+  selected: { type: Array, default: () => [] },
+  usePage: { type: Boolean, default: true },
+  total: { type: Number, default: 0 },
+  useSum: { type: Boolean, default: false },
+  sumcol: { type: Array, default: () => [] },
+  sumres: { type: String, default: 'total' },
+  // limit: { type: Number, default: 10 },
+  height: null,
+  operaWidth: { type: Number, default: 200 },
+  vOpera: { type: Boolean, default: false },
+});
+const { fields } = toRefs(props);
+const { data } = toRefs(props);
+const { opera } = toRefs(props);
+const { rowKey } = toRefs(props);
+const { select } = toRefs(props);
+const { selected } = toRefs(props);
+const { usePage } = toRefs(props);
+const { total } = toRefs(props);
+const { useSum } = toRefs(props);
+const { sumcol } = toRefs(props);
+const { sumres } = toRefs(props);
+// const { limit } = toRefs(props);
+const { height } = toRefs(props);
+const { operaWidth } = toRefs(props);
+const { vOpera } = toRefs(props);
+// #endregion
+const emit = defineEmits(['method', 'handleSelect', 'query', 'rowClick']);
+
+let pageSelected: Ref<any[]> = ref([]);
+let currentPage: Ref<number> = ref(1);
+
+let limit: number = proxy.$limit;
+const toFormatter = (row: any, column: { property: string }, cellValue: string, index: string) => {
+  let this_fields = fields.value.filter((fil) => fil.model === column.property);
+  if (this_fields.length > 0) {
+    let format: any = _.get(this_fields[0], `format`, false);
+    if (format) {
+      let res;
+      if (_.isFunction(format)) {
+        res = format(cellValue);
+      } else {
+        res = toFormat({
+          model: this_fields[0].model,
+          value: cellValue,
+        });
+      }
+      return res;
+    } else {
+      return cellValue;
+    }
+  }
+};
+const toFormat = (e: { model: string; value: string }) => {};
+const handleOpera = (data: string, method: any, confirm = false, methodZh: string, label: string, index: string, confirmWord: string) => {
+  let self = true;
+  if (_.isFunction(methodZh)) methodZh = methodZh(data);
+  else if (!_.isString(methodZh)) {
+    methodZh = label;
+    self = false;
+  }
+  if (confirm) {
+    let word = self ? methodZh : `您确认${methodZh}该数据?`;
+    if (confirmWord) word = confirmWord;
+    ElMessageBox.confirm(word, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+      .then(() => {
+        emit(method, data, index);
+      })
+      .catch(() => {});
+  } else emit(method, data, index);
+};
+const handleSelectionChange = (selection: string, row: never) => {
+  //根据row是否再pageSelected中,判断是添加还是删除
+  let isSelecteds = _.cloneDeep(pageSelected);
+  const is_has = isSelecteds.value.findIndex((f) => f[rowKey.value] === row[rowKey.value]);
+  if (is_has <= -1) {
+    // 没有找到,属于添加
+    isSelecteds.value.push(row);
+  } else {
+    // 找到了,删除
+    isSelecteds.value.splice(is_has, 1);
+  }
+  pageSelected.value = isSelecteds.value;
+  emit(`handleSelect`, isSelecteds);
+};
+const handleSelectAll = (selection: any) => {
+  //处于没全选状态,选择之后一定是全选,只有处于全选状态时,才会反选(全取消)
+  let res: any = [];
+  if (selection.length > 0) {
+    //全选
+    res = _.uniqBy(pageSelected.value.concat(selection), rowKey.value);
+  } else {
+    //全取消
+    res = _.differenceBy(pageSelected.value, data.value, rowKey.value);
+  }
+  pageSelected.value = res;
+  emit(`handleSelect`, res);
+};
+const table = ref<any>();
+const initSelection = () => {
+  nextTick(() => {
+    table.value.clearSelection();
+    selected.value.forEach((info: any) => {
+      let d = data.value.filter((p) => p._id === info._id);
+      if (d.length > 0) table.value.toggleRowSelection(d[0]);
+    });
+  });
+};
+const selectReset = () => {
+  table.value.clearSelection();
+};
+const changePage = (page: number = currentPage.value) => {
+  emit('query', { skip: (page - 1) * limit, limit: limit });
+};
+const sizeChange = (limits: number) => {
+  limit = limits;
+  currentPage.value = 1;
+  emit('query', { skip: 0, limit: limit });
+};
+const rowClick = (row: any, column: string, event: string) => {
+  emit(`rowClick`, row);
+};
+const display = (item: operaItem, row: any) => {
+  let display: any = _.get(item, `display`, true);
+  if (display === true) return true;
+  else {
+    let res = display(row);
+    return res;
+  }
+};
+// 计算合计
+const computedSum = (columns: any, data: any) => {
+  if (columns.length <= 0 || data.length <= 0) return '';
+  const result = [];
+  const reg = new RegExp(/^\d+$/);
+  for (const column of columns) {
+    // 判断有没有prop属性
+    const prop = _.get(column, 'property');
+    if (!prop) {
+      result.push('');
+      continue;
+    }
+    // 判断是否需要计算
+    const inlist = sumcol.value.find((f) => f == prop);
+    if (!inlist) {
+      result.push('');
+      continue;
+    }
+    let res: number | unknown = 0;
+    // 整理出要计算的属性(只取出数字或者可以为数字的值)
+    const resetList = data.map((i: any) => {
+      const d = _.get(i, prop);
+      return d * 1;
+    });
+    let p1: any;
+    if (sumres.value === 'total') {
+      res = totalComputed(p1, resetList);
+    } else if (sumres.value === 'avg') {
+      res = avgComputed(resetList);
+    } else if (sumres.value === 'max') {
+      res = maxComputed(resetList);
+    } else if (sumres.value === 'min') {
+      res = minComputed(resetList);
+    }
+    result.push(res);
+  }
+  result[0] = '合计';
+  return result;
+};
+// 合计计算
+const totalComputed = (columns: any, data: any) => {
+  const total = data.reduce((p: number, n: string) => p + parseFloat(n), 0);
+  return total;
+};
+// 平均值计算
+const avgComputed = (data: any) => {
+  let p1: any;
+  const total = totalComputed(p1, data);
+  return _.round(_.divide(total, data.length), 2);
+};
+// 最大值计算
+const maxComputed = (data: any) => {
+  return _.max(data);
+};
+// 最小值计算
+const minComputed = (data: any) => {
+  return _.min(data);
+};
+</script>
+
+<style scoped>
+.page {
+  background-color: #fff;
+  padding: 8px;
+  height: 50px;
+}
+.el-pagination {
+  position: absolute;
+  right: 10px;
+  background-color: #fff;
+}
+</style>

+ 103 - 0
src/components/c-upload.vue

@@ -0,0 +1,103 @@
+<template>
+  <div id="c-upload">
+    <el-upload
+      v-if="url"
+      ref="upload"
+      :action="url"
+      :limit="limit"
+      :accept="accept"
+      :file-list="list"
+      :list-type="listType"
+      :on-exceed="outLimit"
+      :on-preview="filePreview"
+      :on-success="onSuccess"
+      :before-remove="onRemove"
+    >
+      <el-button type="primary">选择文件</el-button>
+      <template #tip v-if="tip">
+        <p style="color: #ff0000">{{ tip }}</p>
+      </template>
+    </el-upload>
+    <el-dialog v-model="dialog.show" append-to-body>
+      <img width="100%" :src="dialog.url" alt="" />
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { Ref } from 'vue';
+import { ref, toRefs } from 'vue';
+import { ElMessage } from 'element-plus';
+import _ from 'lodash';
+
+// #region
+interface ListItem {
+  errcode?: string | number;
+  errmsg?: string;
+  uri?: string;
+  name?: string;
+  url?: string;
+  id?: any;
+}
+let dialog: Ref<{ show: boolean; url: string }> = ref({ show: false, url: '' });
+const props = defineProps({
+  url: { type: String, default: () => '' },
+  limit: { type: Number, default: () => 6 },
+  accept: { type: String, default: () => 'image/png, image/jpeg' },
+  listType: { type: String, default: () => 'text' },
+  tip: { type: String, default: () => undefined },
+  list: { type: Array<ListItem>, default: () => [] },
+  model: { type: String, default: () => '' },
+});
+// 图片上传地址
+const { url } = toRefs(props);
+// 可上传文件数目
+const { limit } = toRefs(props);
+// 接收上传的文件类型
+const { accept } = toRefs(props);
+// 文件列表的类型--picture-card---picture
+const { listType } = toRefs(props);
+// 文件提醒
+const { tip } = toRefs(props);
+// 已有数据,赋值,预览
+const { list } = toRefs(props);
+const { model } = toRefs(props);
+// const list = ref<UploadUserFile[]>([]);
+
+const emit = defineEmits(['change']);
+// 图片预览
+const filePreview = (file: { url: string }) => {
+  // this.dialog = { show: true, url: file.url };
+  window.open(file.url);
+};
+// 只允许上传多少个文件
+const outLimit = () => {
+  ElMessage.error(`只允许上传${limit.value}个文件`);
+};
+// 上传成功,response:成功信息,file:图片信息,fileList:图片列表
+const onSuccess = (response: { errcode: string | number; errmsg: string; uri: string }, file: { name: string }, fileList: any) => {
+  if (response.errcode !== 0) {
+    ElMessage({ type: 'error', message: '删除成功' });
+    return;
+  }
+  let ponse = _.omit(response, ['errcode', 'errmsg']);
+  let arr: Ref<ListItem[]> = _.cloneDeep(list);
+  if (_.isArray(list.value)) {
+    arr.value.push({ ...ponse, name: file.name, url: `${import.meta.env.VITE_APP_HOST}${response.uri}` });
+  } else {
+    arr.value = [{ ...ponse, name: file.name, url: `${import.meta.env.VITE_APP_HOST}${response.uri}` }];
+  }
+  emit('change', { model: model.value, value: arr.value });
+};
+// 删除图片
+const onRemove = (file: { id: any; uri: string }, fileList: any) => {
+  // let arr: Ref<ListItem[]> = _.cloneDeep(list);
+  // let info = arr.value.filter((f) => f.id != file.id);
+  // emit('change', info);
+  return true;
+};
+
+// #endregion
+</script>
+
+<style lang="less" scoped></style>

+ 57 - 0
src/components/file-1.vue

@@ -0,0 +1,57 @@
+<template>
+  <div id="file-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-form :model="form" ref="formRef" label-width="auto">
+          <el-form-item label="佐证资料" prop="file">
+            <component :is="CUpload" :limit="limit" :url="url" :list="form.file" @change="onChange" style="width: 100%"></component>
+          </el-form-item>
+          <el-col :span="24" class="btn" v-if="!noEdit">
+            <el-button type="primary" @click="onSubmit()">确定</el-button>
+            <el-button type="danger" @click="toReset()">取消</el-button>
+          </el-col>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import CUpload from '@/components/c-upload.vue';
+import type { Ref } from 'vue';
+import { ref, toRefs } from 'vue';
+import type { FormInstance } from 'element-plus';
+interface dataItem {}
+let limit: Ref<number> = ref(6);
+// let url: Ref<string> = ref('/files/freeLabel/outcome/upload');
+const formRef = ref<FormInstance>();
+// #region 参数传递
+const props = defineProps({
+  form: { type: Object, default: () => {} },
+  noEdit: { type: Boolean, default: () => false },
+  url: { type: String, default: () => '/files/freeLabel/upload' },
+});
+const { form } = toRefs(props);
+const { noEdit } = toRefs(props);
+const { url } = toRefs(props);
+// #endregion
+
+const emit = defineEmits(['onSubmit', 'resetForm']);
+const onChange = (e: { model: string; value: Array<dataItem> }) => {
+  const { model, value } = e;
+  form.value[model] = value;
+};
+const onSubmit = () => {
+  emit('onSubmit', form.value);
+};
+// 重置
+const toReset = () => {
+  form.value = { file: [] };
+  emit('resetForm');
+};
+</script>
+<style scoped>
+.btn {
+  text-align: center;
+}
+</style>

File diff suppressed because it is too large
+ 0 - 7
src/components/icons/IconCommunity.vue


File diff suppressed because it is too large
+ 0 - 7
src/components/icons/IconDocumentation.vue


File diff suppressed because it is too large
+ 0 - 7
src/components/icons/IconEcosystem.vue


+ 0 - 7
src/components/icons/IconSupport.vue

@@ -1,7 +0,0 @@
-<template>
-  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
-    <path
-      d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
-    />
-  </svg>
-</template>

+ 0 - 19
src/components/icons/IconTooling.vue

@@ -1,19 +0,0 @@
-<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
-<template>
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    xmlns:xlink="http://www.w3.org/1999/xlink"
-    aria-hidden="true"
-    role="img"
-    class="iconify iconify--mdi"
-    width="24"
-    height="24"
-    preserveAspectRatio="xMidYMid meet"
-    viewBox="0 0 24 24"
-  >
-    <path
-      d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
-      fill="currentColor"
-    ></path>
-  </svg>
-</template>

+ 69 - 0
src/components/wang-editor.vue

@@ -0,0 +1,69 @@
+<template>
+  <div id="editor">
+    <div style="border: 1px solid #ccc">
+      <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
+      <Editor style="height: 500px; overflow-y: hidden" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode" @onCreated="onCreated" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import '@wangeditor/editor/dist/css/style.css'; // 引入 css
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+import type { Ref } from 'vue';
+import { ref, toRefs, onBeforeUnmount, shallowRef, computed } from 'vue';
+interface EmitEvent {
+  (e: 'update:modelValue', params: string): void;
+}
+// #region 参数传递
+const props = defineProps({
+  modelValue: { type: String, default: () => '' },
+  mode: { type: String, default: () => 'default' },
+  url: { type: String, default: () => '' },
+});
+const { modelValue } = toRefs(props);
+const { mode } = toRefs(props);
+const { url } = toRefs(props);
+// #endregion
+
+const editorRef = shallowRef();
+const onCreated = (editor: string) => {
+  editorRef.value = Object.seal(editor); // 一定要用 Object.seal() ,否则会报错
+};
+
+const emit = defineEmits<EmitEvent>();
+const valueHtml = computed({
+  get() {
+    return modelValue.value;
+  },
+  set(value: string) {
+    emit('update:modelValue', value);
+  },
+});
+const customPicInsert = (result: { errcode: number; uri: string; name: string }, insertFn: any) => {
+  const { errcode, uri, name } = result;
+  const url = `${import.meta.env.VITE_APP_HOST}${uri}`;
+  if (errcode === 0) {
+    insertFn(url, name);
+  }
+};
+let editorConfig: Ref<object> = ref({
+  placeholder: '请输入内容...',
+  MENU_CONF: { uploadImage: { server: url, customInsert: customPicInsert } },
+});
+let toolbarConfig: Ref<object> = ref({
+  // excludeKeys: ['insertImage', 'insertVideo', 'uploadVideo', 'video'],
+});
+onBeforeUnmount(() => {
+  const editor = editorRef.value;
+  if (editor == null) return;
+  editor.destroy();
+});
+</script>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>
+<style scoped>
+.editor {
+  overflow-y: hidden;
+}
+</style>

+ 41 - 0
src/layout/Home.vue

@@ -0,0 +1,41 @@
+<template>
+  <div id="home">
+    <el-container>
+      <el-header>
+        <HomeHead></HomeHead>
+      </el-header>
+      <el-container>
+        <el-aside class="one">
+          <HomeLeft></HomeLeft>
+        </el-aside>
+        <el-main class="two">
+          <el-scrollbar>
+            <router-view></router-view>
+          </el-scrollbar>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+<script setup lang="ts">
+import HomeLeft from '@/layout/homeParts/left-1.vue'
+import HomeHead from '@/layout/homeParts/header-1.vue'
+</script>
+
+<style scoped lang="scss">
+.el-header {
+  padding: 0;
+  background-color: $red;
+  border-bottom: 2px solid $white;
+}
+.one {
+  width: 200px;
+  border-right: 2px solid $white;
+}
+.two {
+  height: 92vh;
+  .router-view {
+    height: 92vh;
+  }
+}
+</style>

+ 70 - 0
src/layout/homeParts/header-1.vue

@@ -0,0 +1,70 @@
+<template>
+  <el-col :span="24" class="main">
+    <el-row class="head">
+      <el-col :span="8" class="left">
+        <el-image :src="data.imgUrl" class="image"></el-image>
+        <span>{{ data.name }}-管理中心</span>
+      </el-col>
+      <el-col :span="16" class="right">
+        <span>{{ data.user.name || '游客' }}</span>
+        <el-button type="danger" @click="logout()">退出登录</el-button>
+      </el-col>
+    </el-row>
+  </el-col>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref } from 'vue';
+import type { Ref } from 'vue';
+const data = reactive({
+  name: '基础动态研究管理平台',
+  user: { name: '王泓璎' },
+  imgUrl: new URL('@/assets/image/logo.png', import.meta.url).href,
+});
+const logout = () => {};
+</script>
+
+<style scoped>
+.head {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 0 10px 0 5px;
+}
+.left {
+  line-height: 60px;
+}
+.left span {
+  display: inline-block;
+  margin: 0 10px;
+  font-size: 24px;
+  color: var(--vt-c-white);
+  font-weight: bold;
+  font-family: cursive;
+}
+.left .image {
+  background: var(--vt-c-white);
+  height: 32px;
+  width: 32px;
+  border-radius: 90px;
+  top: 5px;
+}
+.right {
+  text-align: right;
+  /* line-height: 60px; */
+  word-break: keep-all;
+  white-space: nowrap;
+}
+.right i {
+  position: relative;
+  top: 5px;
+  margin: 0px 15px;
+  font-size: 30px;
+  color: var(--vt-c-white);
+}
+.right span {
+  color: var(--vt-c-white);
+  font-size: 16px;
+  padding: 0 15px 0 0px;
+}
+</style>

+ 98 - 0
src/layout/homeParts/left-1.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="main">
+    <el-menu :collapse="false" unique-opened router background-color="#66363c">
+      <template v-for="item in items">
+        <!-- 二级菜单 -->
+        <template v-if="item.type === '0'">
+          <el-sub-menu :index="item._id || item.index" :key="item._id">
+            <template v-slot:title>
+              <span>{{ item.name }}</span>
+            </template>
+            <template v-for="subItem in item.children">
+              <!-- 三级菜单 -->
+              <el-sub-menu
+                v-if="subItem.children && subItem.children.length > 0 && subItem.children.every((f:any) => f.type === '0' || f.type === '1')"
+                :index="subItem._id"
+                :key="subItem._id"
+              >
+                <template v-slot:title>
+                  <span>{{ subItem.name }}</span>
+                </template>
+                <el-menu-item v-for="(threeItem, i) in subItem.children" :key="i" :index="threeItem.path">
+                  <template v-slot:title>
+                    <span>{{ threeItem.name }}</span>
+                  </template>
+                </el-menu-item>
+              </el-sub-menu>
+              <el-menu-item v-else :index="subItem.path" :key="subItem.path">
+                <template v-slot:title>
+                  <span>{{ subItem.name }}</span>
+                </template>
+              </el-menu-item>
+            </template>
+          </el-sub-menu>
+        </template>
+        <!-- 一级菜单 -->
+        <template v-else>
+          <el-menu-item :index="item.path" :key="item.path">
+            <span>{{ item.name }}</span>
+          </el-menu-item>
+        </template>
+      </template>
+    </el-menu>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { Ref } from 'vue';
+import { system } from '@/layout/menu';
+
+// 请求列表
+let items: Ref<any[]> = ref([]);
+items.value = system;
+</script>
+
+<style scoped>
+.el-menu {
+  height: 92.5vh;
+  overflow-y: auto;
+}
+.el-menu-item {
+  color: var(--vt-c-white);
+  font-size: 16px;
+  font-weight: 500;
+}
+.el-menu-item span {
+  overflow-y: auto;
+  overflow-x: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+:deep().el-sub-menu__title {
+  color: var(--vt-c-white);
+  font-size: 16px;
+  font-weight: 500;
+}
+:deep().el-sub-menu__title span {
+  overflow-y: auto;
+  overflow-x: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.el-menu-item.is-active {
+  color: var(--vt-c-black) !important;
+  background-color: var(--vt-c-white) !important;
+}
+.el-menu-item:hover {
+  color: var(--vt-c-black) !important;
+  background-color: var(--vt-c-white) !important;
+}
+.el-menu::-webkit-scrollbar {
+  width: 2px;
+}
+.el-menu::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  background: rgb(255, 255, 255);
+}
+</style>

+ 8 - 0
src/layout/menu.ts

@@ -0,0 +1,8 @@
+export const system = [
+  {
+    icon: 'el-icon-s-home',
+    path: '/',
+    name: '系统首页',
+    index: '1'
+  }
+]

+ 22 - 10
src/main.ts

@@ -1,14 +1,26 @@
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
 
-import App from './App.vue'
-import router from './router'
+import App from './App.vue';
+import router from './router';
 
-import './assets/main.css'
+import './assets/main.css';
+import 'animate.css';
 
-const app = createApp(App)
+import ElementPlus from 'element-plus';
+import 'element-plus/theme-chalk/index.css';
+import locale from 'element-plus/lib/locale/lang/zh-cn';
+import moment from 'moment';
+import _ from 'lodash';
+import * as ElementPlusIconsVue from '@element-plus/icons-vue';
+const app = createApp(App);
+app.config.globalProperties.$limit = parseInt(import.meta.env.VITE_APP_PAGE_SIZE) || 10;
+app.use(createPinia());
+app.use(router);
+app.use(ElementPlus, { locale });
 
-app.use(createPinia())
-app.use(router)
-
-app.mount('#app')
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+app.config.globalProperties.$moment = moment;
+app.mount('#app');

+ 9 - 13
src/router/index.ts

@@ -1,23 +1,19 @@
 import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
-
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
     {
       path: '/',
-      name: 'home',
-      component: HomeView
-    },
-    {
-      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('../views/AboutView.vue')
+      component: () => import('@/layout/Home.vue'),
+      children: [
+        {
+          path: '/',
+          name: 'index',
+          meta: { title: '系统首页' },
+          component: () => import('@/views/index.vue')
+        },
+      ]
     }
   ]
 })
-
 export default router

+ 0 - 15
src/views/AboutView.vue

@@ -1,15 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>
-
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
-}
-</style>

+ 0 - 9
src/views/HomeView.vue

@@ -1,9 +0,0 @@
-<script setup lang="ts">
-import TheWelcome from '../components/TheWelcome.vue'
-</script>
-
-<template>
-  <main>
-    <TheWelcome />
-  </main>
-</template>

+ 13 - 0
src/views/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main"> test </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { Ref } from 'vue';
+import { ref, toRefs } from 'vue';
+</script>
+<style scoped></style>

+ 10 - 0
vite.config.ts

@@ -6,9 +6,19 @@ import vue from '@vitejs/plugin-vue'
 // https://vitejs.dev/config/
 export default defineConfig({
   plugins: [vue()],
+  server: {
+    port: 20001
+  },
   resolve: {
     alias: {
       '@': fileURLToPath(new URL('./src', import.meta.url))
     }
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: `@import "@/assets/style/mixin.scss";` // 此处全局的scss文件
+      }
+    }
   }
 })