guhongwei 2 vuotta sitten
commit
573336d113

+ 2 - 0
.env.development

@@ -0,0 +1,2 @@
+VITE_APP_HOST="http://basic.waityou24.cn"
+BASE_URL='web'

+ 2 - 0
.env.production

@@ -0,0 +1,2 @@
+VITE_APP_HOST="http://basic.waityou24.cn"
+BASE_URL='web'

+ 28 - 0
.eslintrc.cjs

@@ -0,0 +1,28 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  root: true,
+  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting'],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  },
+  rules: {
+    'vue/multi-word-component-names': 0,
+    'max-len': [
+      'warn',
+      {
+        code: 10000
+      }
+    ],
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160
+      }
+    ]
+  }
+}

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+*.history

+ 8 - 0
.prettierrc.json

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

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# jcyjdt_web

+ 1 - 0
env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>加载中</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 8813 - 0
package-lock.json


+ 44 - 0
package.json

@@ -0,0 +1,44 @@
+{
+  "name": "jcyjdt_web",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "dev": "vite --host",
+    "build": "run-p type-check build-only",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --noEmit",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+    "format": "prettier --write src/"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.1.0",
+    "animate.css": "^4.1.1",
+    "axios": "^1.3.4",
+    "element-plus": "^2.3.1",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "naf-core": "^0.1.2",
+    "pinia": "^2.0.32",
+    "vue": "^3.2.47",
+    "vue-router": "^4.1.6",
+    "vuex": "^4.1.0"
+  },
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.2.0",
+    "@types/node": "^18.14.2",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "@vue/eslint-config-prettier": "^7.1.0",
+    "@vue/eslint-config-typescript": "^11.0.2",
+    "@vue/tsconfig": "^0.1.3",
+    "eslint": "^8.34.0",
+    "eslint-plugin-vue": "^9.9.0",
+    "less": "^4.1.3",
+    "less-loader": "^11.1.0",
+    "npm-run-all": "^4.1.5",
+    "prettier": "^2.8.4",
+    "typescript": "~4.8.4",
+    "vite": "^4.1.4",
+    "vue-tsc": "^1.2.0"
+  }
+}

BIN
public/favicon.ico


+ 7 - 0
src/App.vue

@@ -0,0 +1,7 @@
+<template>
+  <RouterView />
+</template>
+<script setup lang="ts">
+import { RouterView } from 'vue-router'
+</script>
+<style scoped></style>

BIN
src/assets/bg.jpg


BIN
src/assets/logo.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 18 - 0
src/assets/main.css

@@ -0,0 +1,18 @@
+body{
+  margin: 0;
+}
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+
+p {
+  margin: 0;
+  padding: 0;
+}
+
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}

BIN
src/assets/user.png


+ 26 - 0
src/main.ts

@@ -0,0 +1,26 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import router from './router'
+// 样式
+import '@/assets/main.css'
+// 动画
+import 'animate.css'
+// element
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
+// moment
+import moment from 'moment'
+// lodash
+// import _ from 'lodash';
+const app = createApp(App)
+app.use(createPinia())
+app.use(router)
+app.use(ElementPlus, {})
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+app.config.globalProperties.$moment = moment
+app.mount('#app')

+ 35 - 0
src/router/index.ts

@@ -0,0 +1,35 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      meta: { title: '基础动态管理平台' },
+      // component: () => import('@/views/homeIndex.vue')
+      component: () => import('@/views/loginIndex.vue')
+    },
+    {
+      path: '/loginIndex',
+      meta: { title: '账号登录' },
+      component: () => import('@/views/loginIndex.vue')
+    },
+    {
+      path: '/loginAdmin',
+      meta: { title: '管理员登录' },
+      component: () => import('@/views/loginAdmin.vue')
+    },
+    {
+      path: '/register',
+      meta: { title: '账号注册' },
+      component: () => import('@/views/register.vue')
+    }
+  ]
+})
+router.beforeEach((to, from, next) => {
+  // 赋值标题
+  document.title = `${to.meta.title}`
+  // 向下进行
+  next()
+})
+export default router

+ 12 - 0
src/stores/counter copy.ts

@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { count, doubleCount, increment }
+})

+ 12 - 0
src/stores/counter.ts

@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { count, doubleCount, increment }
+})

+ 150 - 0
src/util/axios-wrapper.ts

@@ -0,0 +1,150 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash'
+import Axios from 'axios'
+import { Util, Error } from 'naf-core'
+// import { Indicator } from 'mint-ui';
+import type { IOptionsType, IQueryType, IRequestResult } from './types.util'
+
+const { trimData, isNullOrUndefined } = Util
+const { ErrorCode } = Error
+
+let currentRequests = 0
+
+// // 参数类型设置
+// type valueType = string | number | object | boolean | Array<any>;
+// type queryType = string | number | boolean;
+
+// export interface IQueryType {
+//   [props: string]: queryType;
+// }
+// export interface IOptionsType {
+//   [props: string]: valueType;
+// }
+
+// export interface IRequestResult {
+//   errcode: string | number;
+//   errmsg: string | number;
+//   details?: string;
+//   [props: string]: any;
+// }
+
+export class AxiosWrapper {
+  constructor({ baseUrl = import.meta.env.VITE_REQUEST_BASE, unwrap = true } = {}) {
+    this.baseUrl = baseUrl
+    this.unwrap = unwrap
+  }
+  baseUrl: string
+  unwrap: boolean
+
+  // 替换uri中的参数变量
+  static merge(uri: string, query: IQueryType) {
+    if (!uri.includes(':')) {
+      return uri
+    }
+    const keys = []
+    const regexp = /\/:([a-z0-9_]+)/gi
+    let res
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1])
+    }
+    keys.forEach((key) => {
+      const val = _.get(query, key)
+      if (!isNullOrUndefined(val)) {
+        uri = uri.replace(`:${key}`, `${val}`)
+      }
+    })
+    return uri
+  }
+
+  $get(uri: string, query?: IQueryType, options?: IOptionsType) {
+    return this.$request(uri, undefined, query, options)
+  }
+
+  $post(uri: string, data: object = {}, query?: IQueryType, options?: IOptionsType) {
+    return this.$request(uri, data, query, options)
+  }
+  $delete(uri: string, data: object = {}, query?: IQueryType, options: IOptionsType = {}) {
+    options = { ...options, method: 'delete' }
+    return this.$request(uri, data, query, options)
+  }
+  async $request(uri: string, data?: object, query?: IQueryType, options?: IOptionsType) {
+    if (query && _.isObject(query)) {
+      const keys = Object.keys(query)
+      for (const key of keys) {
+        const val = _.get(query, key)
+        if (val === '') {
+          delete query[key]
+        }
+      }
+    }
+    if (_.isObject(query) && _.isObject(options)) {
+      options = { ...options, params: query, method: 'get' }
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query }
+    } else if (_.isObject(query) && query.params) {
+      options = query
+    }
+    if (!options) options = {}
+    if (options.params) options.params = trimData(options.params, null, null)
+    const params = _.get(options, 'params')
+    const url = AxiosWrapper.merge(uri, params as IQueryType)
+    currentRequests += 1
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+    try {
+      let returnData: any
+      const axios = Axios.create({
+        baseURL: this.baseUrl
+      })
+      // if (util.token && util.token !== null) axios.defaults.headers.common.Authorization = util.token;
+      const token = sessionStorage.getItem('user')
+      const apiToken = sessionStorage.getItem('apiToken')
+      if (token) axios.defaults.headers.common['admin-token'] = token
+      if (apiToken) axios.defaults.headers.common['api-token'] = apiToken
+      const res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options
+      })
+      const returnRes: IRequestResult = res.data
+      const { errcode, errmsg, details } = returnRes
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`)
+        return returnRes
+      }
+      // unwrap data
+      if (this.unwrap) {
+        returnData = returnRes
+      }
+      // 处理apiToken
+      const { apiToken: at, ...others } = returnData
+      if (at) sessionStorage.setItem('apiToken', at)
+      return others
+    } catch (err: any) {
+      let errmsg = '接口请求失败,请稍后重试'
+      if (err.response) {
+        const { status } = err.response
+        if (status === 401) errmsg = '用户认证失败,请重新登录'
+        if (status === 403) errmsg = '当前用户不允许执行该操作'
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      )
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message }
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 29 - 0
src/util/types.util.ts

@@ -0,0 +1,29 @@
+// 参数类型设置
+type valueType = string | number | Object | boolean | Array<any>
+type queryType = string | number | boolean
+
+export interface IQueryType {
+  [props: string]: queryType
+}
+export interface IOptionsType {
+  [props: string]: valueType
+}
+
+export interface IRequestResult {
+  errcode: string | number
+  errmsg: string | number
+  details?: string
+  [props: string]: any
+}
+export interface IQueryResult {
+  errcode?: string | number
+  errmsg?: string | number
+  data: valueType
+  total: number
+}
+
+export interface IQueryParams {
+  skip?: number
+  limit?: number
+  [props: string]: any
+}

+ 30 - 0
src/views/homeIndex.vue

@@ -0,0 +1,30 @@
+<template>
+  <el-row>
+    <el-col :span="24" class="main animate__animated animate__backInRight">
+      <el-col :span="24" class="one"> 系统首页 </el-col>
+    </el-col>
+  </el-row>
+</template>
+<script setup lang="ts">
+// 基础
+// import type { Ref } from 'vue'
+// reactive, ref, onMounted
+import { onMounted } from 'vue'
+// 接口
+import { TestStore } from '@common/src/stores/test' //个人
+import type { IQueryResult } from '@/util/types.util'
+const testAxios = TestStore()
+// 分页数据
+const skip = 0
+const limit = 10
+// 请求
+onMounted(async () => {
+  await search({ skip, limit })
+})
+const search = async (e: { skip: number; limit: number }) => {
+  const info = { skip: e.skip, limit: e.limit }
+  const res: IQueryResult = await testAxios.query(info)
+  console.log(res)
+}
+</script>
+<style scoped lang="less"></style>

+ 302 - 0
src/views/loginAdmin.vue

@@ -0,0 +1,302 @@
+<template>
+  <el-row>
+    <el-col :span="24" class="main animate__animated animate__backInRight">
+      <el-col :span="24" class="one">
+        <el-col :span="24" class="w_1200">
+          <el-col :span="24" class="one_1">
+            <el-col :span="12" class="left">
+              <el-col :span="3" class="image">
+                <el-image :src="webInfos.logo_url"></el-image>
+              </el-col>
+              <el-col :span="21" class="other">
+                <el-col :span="24" class="zhTitle textOver">
+                  {{ webInfos.zhTitle }}
+                </el-col>
+                <el-col :span="24" class="enTitle textOver">
+                  {{ webInfos.enTitle }}
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="12" class="right"></el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+      <el-col :span="24" class="two">
+        <el-col :span="24" class="w_1200">
+          <el-col :span="14" class="left">
+            <el-col :span="24" class="txt">{{ webInfos.one_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.two_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.thr_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.four_title }}</el-col>
+          </el-col>
+          <el-col :span="10" class="right">
+            <el-col :span="24" class="login">
+              <el-col :span="24" class="login_1">
+                <el-image :src="webInfos.user_url"></el-image>
+                <span>欢迎登录</span>
+              </el-col>
+              <el-col :span="24" class="login_2">
+                <el-form ref="ruleFormRef" size="default" :model="form" :rules="rules">
+                  <el-form-item label="" prop="account">
+                    <el-input v-model="form.account" placeholder="请输入账号">
+                      <template #prefix>
+                        <el-icon><User /></el-icon>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="" prop="password">
+                    <el-input v-model="form.password" type="password" show-password placeholder="请输入密码">
+                      <template #prefix>
+                        <el-icon><Unlock /></el-icon>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="" prop="type">
+                    <el-select v-model="form.type" clearable placeholder="请选择用户类型" style="width: 100%" disabled>
+                      <el-option v-for="t in typeList" :key="t.value" :label="t.label" :value="t.value" />
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="" prop="code">
+                    <el-input v-model="form.code" placeholder="请输入验证码" disabled>
+                      <template #prefix>
+                        <el-icon><Key /></el-icon>
+                      </template>
+                      <template #append>
+                        <component :is="validcode" @toCode="toCode"></component>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-col :span="24" class="btn">
+                    <el-button type="warning">
+                      <el-icon><Burger /></el-icon>
+                      <span>依托单位账号密码找回</span>
+                    </el-button>
+                    <el-button type="primary">
+                      <el-icon><User /></el-icon>
+                      <span>个人账号密码找回</span>
+                    </el-button>
+                  </el-col>
+                  <el-col :span="24" class="save">
+                    <el-button type="primary" size="large" @click="toSave(ruleFormRef)">登录</el-button>
+                  </el-col>
+                </el-form>
+              </el-col>
+              <el-col :span="24" class="login_3">
+                <span>还没有账号?</span>
+                <el-button type="danger" size="small" plain @click="toRegister">去注册</el-button>
+              </el-col>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+      <el-col :span="24" class="thr">
+        <el-col :span="24" class="w_1200"><p v-html="webInfos.content"></p></el-col>
+      </el-col>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+// 组件
+import validcode from '@common/src/components/ValidCode.vue'
+// 基础
+import { webInfo } from '@common/src/layout/site'
+import type { Ref } from 'vue'
+import { reactive, ref, onMounted } from 'vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { ElMessage } from 'element-plus'
+// 接口
+import { AdminStore } from '@common/src/stores/users/admin' //管理员
+import { UnitStore } from '@common/src/stores/users/unit' //依托单位
+import { UsersStore } from '@common/src/stores/users/users' //个人
+import type { IQueryResult } from '@/util/types.util'
+const adminAxios = AdminStore()
+const unitAxios = UnitStore()
+const usersAxios = UsersStore()
+// 平台信息
+const webInfos: Ref<any> = ref(webInfo)
+// 登录
+const ruleFormRef = ref<FormInstance>()
+const form = reactive({ account: '', password: '', type: '2', code: '1234' })
+const rules = reactive<FormRules>({
+  account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+  type: [{ required: true, message: '请选择用户类型', trigger: 'change' }],
+  code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+})
+// 验证码
+const code: Ref<any> = ref('')
+// 字典
+const typeList: Ref<any> = ref([
+  { label: '依托单位账号', value: '0' },
+  { label: '个人账号', value: '1' },
+  { label: '管理员', value: '2' }
+])
+onMounted(() => {})
+// 验证码
+const toCode = (e) => {
+  code.value = e
+}
+// 提交登录
+const toSave = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      // if (form.code.toLowerCase() !== code.value.toLowerCase()) {
+      //   ElMessage({ message: '验证码错误', type: 'error' })
+      //   return
+      // }
+      delete form.code
+      let res: IQueryResult
+      if (form.type == '0') {
+        delete form.type
+        res = await unitAxios.login(form)
+      } else if (form.type == '1') {
+        delete form.type
+        res = await usersAxios.login(form)
+      } else if (form.type == '2') {
+        delete form.type
+        res = await adminAxios.login(form)
+      }
+      if (res.errcode == '0') {
+        ElMessage({ message: '登录成功', type: 'success' })
+        localStorage.setItem('token', `${res.data}`)
+        // window.location.href = `http://basic.waityou24.cn/admin`
+        window.location.href = `/admin`
+      } else {
+        ElMessage({ message: `${res.errmsg}`, type: 'error' })
+      }
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 去注册
+const toRegister = () => {
+  console.log('1')
+}
+</script>
+<style scoped lang="less">
+.main {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  .one {
+    .one_1 {
+      display: flex;
+      padding: 10px 0;
+      .left {
+        display: flex;
+        align-items: center;
+        .image {
+          text-align: center;
+          .el-image {
+            :deep(.el-image__inner) {
+              width: 50px;
+              height: 50px;
+            }
+          }
+        }
+        .other {
+          .zhTitle {
+            font-size: 30px;
+            font-family: cursive;
+            font-weight: 700;
+          }
+          .enTitle {
+            font-size: 12px;
+            text-transform: capitalize;
+          }
+        }
+      }
+      .right {
+        text-align: right;
+      }
+    }
+  }
+  .two {
+    background: url('@/assets/bg.jpg');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    .w_1200 {
+      height: 86vh;
+      padding: 10px 0;
+      display: flex;
+      .left {
+        padding: 3% 0 0 2%;
+        .txt {
+          font-size: 30px;
+          color: #ffffff;
+          font-family: cursive;
+          font-weight: bold;
+          margin: 0 0 10px 0;
+          font-style: italic;
+          padding: 0 0 0 0;
+        }
+        .txt:nth-child(2n) {
+          padding: 0 0 0 18%;
+        }
+      }
+      .right {
+        display: flex;
+        align-items: flex-start;
+        .login {
+          width: 80%;
+          height: 52vh;
+          overflow: hidden;
+          padding: 10px;
+          background-color: #ffffff;
+          border-radius: 5px;
+          box-shadow: 0px 0px 10px 5px #0505054d;
+          .login_1 {
+            margin: 0 0 10px 0;
+            span {
+              position: relative;
+              left: 5px;
+              top: -10px;
+              font-size: 24px;
+              font-weight: bold;
+              color: #666;
+            }
+          }
+          .login_2 {
+            .btn {
+              margin: 0 0 18px 0;
+              display: flex;
+              justify-content: space-around;
+              button {
+                width: 50%;
+                padding: 20px 0;
+                font-size: 15px;
+                span {
+                  padding: 0 0 0 5px;
+                }
+              }
+            }
+            .save {
+              margin: 0 0 18px 0;
+              button {
+                width: 100%;
+              }
+            }
+          }
+          .login_3 {
+            text-align: center;
+            span {
+              padding: 0 10px 0 0;
+            }
+          }
+        }
+      }
+    }
+  }
+  .thr {
+    padding: 10px 0;
+    text-align: center;
+    color: #999999;
+  }
+}
+:deep(.el-input-group__append, .el-input-group__prepend) {
+  padding: 0;
+}
+</style>

+ 303 - 0
src/views/loginIndex.vue

@@ -0,0 +1,303 @@
+<template>
+  <el-row>
+    <el-col :span="24" class="main animate__animated animate__backInRight">
+      <el-col :span="24" class="one">
+        <el-col :span="24" class="w_1200">
+          <el-col :span="24" class="one_1">
+            <el-col :span="12" class="left">
+              <el-col :span="3" class="image">
+                <el-image :src="webInfos.logo_url"></el-image>
+              </el-col>
+              <el-col :span="21" class="other">
+                <el-col :span="24" class="zhTitle textOver">
+                  {{ webInfos.zhTitle }}
+                </el-col>
+                <el-col :span="24" class="enTitle textOver">
+                  {{ webInfos.enTitle }}
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="12" class="right"></el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+      <el-col :span="24" class="two">
+        <el-col :span="24" class="w_1200">
+          <el-col :span="14" class="left">
+            <el-col :span="24" class="txt">{{ webInfos.one_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.two_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.thr_title }}</el-col>
+            <el-col :span="24" class="txt">{{ webInfos.four_title }}</el-col>
+          </el-col>
+          <el-col :span="10" class="right">
+            <el-col :span="24" class="login">
+              <el-col :span="24" class="login_1">
+                <el-image :src="webInfos.user_url"></el-image>
+                <span>欢迎登录</span>
+              </el-col>
+              <el-col :span="24" class="login_2">
+                <el-form ref="ruleFormRef" size="default" :model="form" :rules="rules">
+                  <el-form-item label="" prop="account">
+                    <el-input v-model="form.account" placeholder="请输入账号">
+                      <template #prefix>
+                        <el-icon><User /></el-icon>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="" prop="password">
+                    <el-input v-model="form.password" type="password" show-password placeholder="请输入密码">
+                      <template #prefix>
+                        <el-icon><Unlock /></el-icon>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="" prop="type">
+                    <el-select v-model="form.type" clearable placeholder="请选择用户类型" style="width: 100%">
+                      <el-option v-for="t in typeList" :key="t.value" :label="t.label" :value="t.value" />
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="" prop="code">
+                    <el-input v-model="form.code" placeholder="请输入验证码">
+                      <template #prefix>
+                        <el-icon><Key /></el-icon>
+                      </template>
+                      <template #append>
+                        <component :is="validcode" @toCode="toCode"></component>
+                      </template>
+                    </el-input>
+                  </el-form-item>
+                  <el-col :span="24" class="btn">
+                    <el-button type="warning">
+                      <el-icon><Burger /></el-icon>
+                      <span>依托单位账号密码找回</span>
+                    </el-button>
+                    <el-button type="primary">
+                      <el-icon><User /></el-icon>
+                      <span>个人账号密码找回</span>
+                    </el-button>
+                  </el-col>
+                  <el-col :span="24" class="save">
+                    <el-button type="primary" size="large" @click="toSave(ruleFormRef)">登录</el-button>
+                  </el-col>
+                </el-form>
+              </el-col>
+              <el-col :span="24" class="login_3">
+                <span>还没有账号?</span>
+                <el-button type="danger" size="small" plain @click="toRegister">去注册</el-button>
+              </el-col>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+      <el-col :span="24" class="thr">
+        <el-col :span="24" class="w_1200"><p v-html="webInfos.content"></p></el-col>
+      </el-col>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+// 组件
+import validcode from '@common/src/components/ValidCode.vue'
+// 基础
+import { webInfo } from '@common/src/layout/site'
+import type { Ref } from 'vue'
+import { reactive, ref, onMounted } from 'vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { ElMessage } from 'element-plus'
+// 接口
+import { AdminStore } from '@common/src/stores/users/admin' //管理员
+import { UnitStore } from '@common/src/stores/users/unit' //依托单位
+import { UsersStore } from '@common/src/stores/users/users' //个人
+import type { IQueryResult } from '@/util/types.util'
+import router from '@/router'
+const adminAxios = AdminStore()
+const unitAxios = UnitStore()
+const usersAxios = UsersStore()
+// 平台信息
+const webInfos: Ref<any> = ref(webInfo)
+// 登录
+const ruleFormRef = ref<FormInstance>()
+const form = reactive({ account: '', password: '', type: '', code: '' })
+const rules = reactive<FormRules>({
+  account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+  type: [{ required: true, message: '请选择用户类型', trigger: 'change' }],
+  code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+})
+// 验证码
+const code: Ref<any> = ref('')
+// 字典
+const typeList: Ref<any> = ref([
+  { label: '依托单位账号', value: '0' },
+  { label: '个人账号', value: '1' },
+  { label: '管理员', value: '2' }
+])
+onMounted(() => {})
+// 验证码
+const toCode = (e) => {
+  code.value = e
+}
+// 提交登录
+const toSave = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      if (form.code.toLowerCase() !== code.value.toLowerCase()) {
+        ElMessage({ message: '验证码错误', type: 'error' })
+        return
+      }
+      delete form.code
+      let res: IQueryResult
+      if (form.type == '0') {
+        delete form.type
+        res = await unitAxios.login(form)
+      } else if (form.type == '1') {
+        delete form.type
+        res = await usersAxios.login(form)
+      } else if (form.type == '2') {
+        delete form.type
+        res = await adminAxios.login(form)
+      }
+      if (res.errcode == '0') {
+        ElMessage({ message: '登录成功', type: 'success' })
+        localStorage.setItem('token', `${res.data}`)
+        // window.location.href = `http://basic.waityou24.cn/admin`
+        window.location.href = `/admin`
+      } else {
+        ElMessage({ message: `${res.errmsg}`, type: 'error' })
+      }
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 去注册
+const toRegister = () => {
+  router.push({ path: '/register' })
+}
+</script>
+<style scoped lang="less">
+.main {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  .one {
+    .one_1 {
+      display: flex;
+      padding: 10px 0;
+      .left {
+        display: flex;
+        align-items: center;
+        .image {
+          text-align: center;
+          .el-image {
+            :deep(.el-image__inner) {
+              width: 50px;
+              height: 50px;
+            }
+          }
+        }
+        .other {
+          .zhTitle {
+            font-size: 30px;
+            font-family: cursive;
+            font-weight: 700;
+          }
+          .enTitle {
+            font-size: 12px;
+            text-transform: capitalize;
+          }
+        }
+      }
+      .right {
+        text-align: right;
+      }
+    }
+  }
+  .two {
+    background: url('@/assets/bg.jpg');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    .w_1200 {
+      height: 86vh;
+      padding: 10px 0;
+      display: flex;
+      .left {
+        padding: 3% 0 0 2%;
+        .txt {
+          font-size: 30px;
+          color: #ffffff;
+          font-family: cursive;
+          font-weight: bold;
+          margin: 0 0 10px 0;
+          font-style: italic;
+          padding: 0 0 0 0;
+        }
+        .txt:nth-child(2n) {
+          padding: 0 0 0 18%;
+        }
+      }
+      .right {
+        display: flex;
+        align-items: flex-start;
+        .login {
+          width: 80%;
+          height: 52vh;
+          overflow: hidden;
+          padding: 10px;
+          background-color: #ffffff;
+          border-radius: 5px;
+          box-shadow: 0px 0px 10px 5px #0505054d;
+          .login_1 {
+            margin: 0 0 10px 0;
+            span {
+              position: relative;
+              left: 5px;
+              top: -10px;
+              font-size: 24px;
+              font-weight: bold;
+              color: #666;
+            }
+          }
+          .login_2 {
+            .btn {
+              margin: 0 0 18px 0;
+              display: flex;
+              justify-content: space-around;
+              button {
+                width: 50%;
+                padding: 20px 0;
+                font-size: 15px;
+                span {
+                  padding: 0 0 0 5px;
+                }
+              }
+            }
+            .save {
+              margin: 0 0 18px 0;
+              button {
+                width: 100%;
+              }
+            }
+          }
+          .login_3 {
+            text-align: center;
+            span {
+              padding: 0 10px 0 0;
+            }
+          }
+        }
+      }
+    }
+  }
+  .thr {
+    padding: 10px 0;
+    text-align: center;
+    color: #999999;
+  }
+}
+:deep(.el-input-group__append, .el-input-group__prepend) {
+  padding: 0;
+}
+</style>

+ 31 - 0
src/views/register.vue

@@ -0,0 +1,31 @@
+<template>
+  <el-row>
+    <el-col :span="24" class="main animate__animated animate__backInRight">
+      <el-col :span="24" class="one">注册页面</el-col>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+// 基础
+// import type { Ref } from 'vue'
+// reactive, ref, onMounted
+import { onMounted } from 'vue'
+// 接口
+import { TestStore } from '@common/src/stores/test'
+import type { IQueryResult } from '@/util/types.util'
+const testAxios = TestStore()
+// 分页数据
+const skip = 0
+const limit = 10
+// 请求
+onMounted(async () => {
+  await search({ skip, limit })
+})
+const search = async (e: { skip: number; limit: number }) => {
+  const info = { skip: e.skip, limit: e.limit }
+  const res: IQueryResult = await testAxios.query(info)
+  console.log(res)
+}
+</script>
+<style scoped lang="less"></style>

+ 47 - 0
tsconfig.json

@@ -0,0 +1,47 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.web.json",
+  // "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "../common/src/stores/user", "../common/src/stores/user"],
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.d.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "src/untils/baiduMap.js",
+    "src/untils/debuonce.js",
+    "**/*.ts",
+    "**/*.tsx"
+  ],
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": false,
+    "jsx": "preserve",
+    "moduleResolution": "node",
+    "baseUrl": ".",
+    "paths": {
+      "@/*": [
+        "./src/*"
+      ],
+      "@common/*": [
+        "../common/*"
+      ]
+    },
+    "isolatedModules": false,
+    "suppressImplicitAnyIndexErrors": true,
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "lib": [
+      "es5",
+      "es6",
+      "dom",
+      "dom.iterable"
+    ],
+    "allowSyntheticDefaultImports": true
+  },
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    }
+  ]
+}

+ 8 - 0
tsconfig.node.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.node.json",
+  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
+  "compilerOptions": {
+    "composite": true,
+    "types": ["node"]
+  }
+}

+ 33 - 0
vite.config.ts

@@ -0,0 +1,33 @@
+import { fileURLToPath, URL } from 'node:url'
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+const path = require('path')
+const common = path.resolve(__dirname, '../common')
+export default defineConfig({
+  plugins: [vue()],
+  server: {
+    port: 8001,
+    proxy: {
+      '/files': {
+        target: 'http://basic.waityou24.cn'
+      },
+      '/jcyjdtglpt/v1/api': {
+        target: 'http://192.168.230.1:13010',
+        changeOrigin: true,
+        ws: false
+      }
+    }
+  },
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
+      '@common': common
+    }
+  },
+  // 静态路径
+  base: '',
+  // 打包名称
+  build: {
+    outDir: ''
+  }
+})