fxs 9 tháng trước cách đây
mục cha
commit
90c08ad543

+ 8 - 8
components.d.ts

@@ -7,8 +7,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
-    ElAffix: typeof import('element-plus/es')['ElAffix']
-    ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    Button: typeof import('./src/components/form/Button.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -22,7 +21,6 @@ declare module 'vue' {
     ElInput: typeof import('element-plus/es')['ElInput']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
-    ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
@@ -34,15 +32,17 @@ declare module 'vue' {
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     FilterPopover: typeof import('./src/components/toolsBtn/FilterPopover.vue')['default']
-    IconEpHistogram: typeof import('~icons/ep/histogram')['default']
-    IconEpMaterialSymbolsLightLogout: typeof import('~icons/ep/material-symbols-light-logout')['default']
-    IconEpPieChart: typeof import('~icons/ep/pie-chart')['default']
+    IconIcBaselineVisibility: typeof import('~icons/ic/baseline-visibility')['default']
+    IconIcBaselineVisibilityOff: typeof import('~icons/ic/baseline-visibility-off')['default']
     IconMaterialSymbolsLightLogout: typeof import('~icons/material-symbols-light/logout')['default']
-    IconMaterialSymbolsLightLogoutLogout: typeof import('~icons/material-symbols-light/logout-logout')['default']
+    IconMdiPassword: typeof import('~icons/mdi/password')['default']
+    IconMdiPasswordPassword: typeof import('~icons/mdi/password-password')['default']
+    MyButton: typeof import('./src/components/form/MyButton.vue')['default']
+    MyInput: typeof import('./src/components/form/MyInput.vue')['default']
     RegreshBtn: typeof import('./src/components/toolsBtn/RegreshBtn.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     Table: typeof import('./src/components/Table.vue')['default']
-    TextInput: typeof import('./src/components/input/TextInput.vue')['default']
+    TextInput: typeof import('./src/components/form/TextInput.vue')['default']
   }
 }

BIN
dist.rar


+ 22 - 0
package-lock.json

@@ -21,7 +21,9 @@
       },
       "devDependencies": {
         "@iconify-json/ep": "^1.1.16",
+        "@iconify-json/ic": "^1.1.18",
         "@iconify-json/material-symbols-light": "^1.1.28",
+        "@iconify-json/mdi": "^1.1.68",
         "@rushstack/eslint-patch": "^1.8.0",
         "@tsconfig/node20": "^20.1.4",
         "@types/node": "^20.14.5",
@@ -701,6 +703,16 @@
         "@iconify/types": "*"
       }
     },
+    "node_modules/@iconify-json/ic": {
+      "version": "1.1.18",
+      "resolved": "https://registry.npmmirror.com/@iconify-json/ic/-/ic-1.1.18.tgz",
+      "integrity": "sha512-RcfnSFVmQP6aEKQJM3Iczg9e5TXWlN0slIjiyH9l378YkhDPAhXdQ8xP8+tBwfz7B9jysqsV+vs9Iu8DMHvE6Q==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@iconify/types": "*"
+      }
+    },
     "node_modules/@iconify-json/material-symbols-light": {
       "version": "1.1.28",
       "resolved": "https://registry.npmmirror.com/@iconify-json/material-symbols-light/-/material-symbols-light-1.1.28.tgz",
@@ -711,6 +723,16 @@
         "@iconify/types": "*"
       }
     },
+    "node_modules/@iconify-json/mdi": {
+      "version": "1.1.68",
+      "resolved": "https://registry.npmmirror.com/@iconify-json/mdi/-/mdi-1.1.68.tgz",
+      "integrity": "sha512-7//TKCiXLU6kNWeOJRTBbisofVO7rF1sD1TZTL4/V9nqlmVNczQ5IOY0GgKOTsitkcTnX9GXgrgbjw3OI5B69w==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@iconify/types": "*"
+      }
+    },
     "node_modules/@iconify/types": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",

+ 2 - 0
package.json

@@ -26,7 +26,9 @@
   },
   "devDependencies": {
     "@iconify-json/ep": "^1.1.16",
+    "@iconify-json/ic": "^1.1.18",
     "@iconify-json/material-symbols-light": "^1.1.28",
+    "@iconify-json/mdi": "^1.1.68",
     "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node20": "^20.1.4",
     "@types/node": "^20.14.5",

+ 2 - 2
src/components/Table.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import type { PropsParams, TablePaginationSetting, QueryInfo } from '@/types/table'
-import { FilterType, FieldSpecialEffectType, ColorType } from '@/types/table'
+import type { PropsParams } from '@/types/table'
+import { FilterType, FieldSpecialEffectType } from '@/types/table'
 
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { useTable } from '@/hooks/useTable'

+ 48 - 0
src/components/form/MyButton.vue

@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+interface ButtonInfo {
+  btnInfo: {
+    text: string
+  }
+}
+const emits = defineEmits(['clickEvent'])
+
+defineProps<ButtonInfo>()
+</script>
+
+<template>
+  <button class="buttonBox" @click="emits('clickEvent')">{{ btnInfo.text }}</button>
+</template>
+
+<style scoped>
+.buttonBox {
+  width: 100%;
+  background-color: rgba(22, 93, 255, 1);
+  /* padding: 0 5px; */
+  border: 1px solid transparent;
+  min-height: 32px;
+  box-sizing: border-box;
+  cursor: pointer;
+  color: white;
+  font-size: 14px;
+  transition: background-color 0.2s;
+}
+
+.buttonBox:hover {
+  /* background-color: red; */
+  background-color: rgba(22, 93, 255, 0.8);
+}
+
+button {
+  border: none;
+  margin: 0;
+  padding: 0;
+  outline: none;
+  border-radius: 0;
+  background-color: transparent;
+  line-height: normal;
+}
+button::after {
+  border: none;
+}
+</style>

+ 190 - 0
src/components/form/MyInput.vue

@@ -0,0 +1,190 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import type { RuleInfo } from '@/types/input'
+
+interface InputInfo {
+  modelValue: string
+  pInputType: string
+  pinputRules: RuleInfo
+}
+
+const props = defineProps<InputInfo>()
+
+const emits = defineEmits(['validateInput', 'update:modelValue'])
+
+let inputType = ref(props.pInputType) // inpt类型
+
+const isPassword = ref(false) // 是否时password框
+
+const inputDivRef = ref() // input框外的整个div
+const inputRef = ref() // input框
+
+let passwordVisiable = ref(false) // 密码是否可见
+let activeClass = ref(false) // 是否被选中
+let showTip = ref(false)
+let errorMsg = ref('')
+
+onMounted(() => {
+  // 点击输入框则聚焦,并给边框
+  document.addEventListener('click', (e) => {
+    // 如果点击的地方不在 div 内部,则移除 active-class
+    if (inputDivRef.value && !inputDivRef.value.contains(e.target)) {
+      activeClass.value = false
+    } else {
+      activeClass.value = true
+      if (inputRef.value) {
+        inputRef.value.focus()
+      }
+    }
+  })
+  if (inputType.value === 'password') isPassword.value = true
+})
+
+const changeVisible = () => {
+  passwordVisiable.value = !passwordVisiable.value
+  if (passwordVisiable.value) {
+    inputType.value = 'text'
+  } else {
+    inputType.value = 'password'
+  }
+}
+
+const verifyIpt = () => {
+  let rulesInfo = props.pinputRules
+  if (rulesInfo) {
+    let vaild = rulesInfo.rules.every((item) => {
+      errorMsg.value = item.errMsg
+
+      return item.validator()
+    })
+
+    showTip.value = !vaild
+    if (vaild) errorMsg.value = ''
+  }
+}
+
+const updateValue = (val: any) => {
+  emits('update:modelValue', val.target.value)
+}
+</script>
+
+<template>
+  <div class="body">
+    <div class="inputBox" ref="inputDivRef" :class="{ activeInput: activeClass }">
+      <div class="inputIconBox">
+        <div class="icon">
+          <el-icon class="iconItem">
+            <slot name="icon"></slot>
+          </el-icon>
+        </div>
+        <div class="inputContent">
+          <input
+            ref="inputRef"
+            class="inputBody"
+            :type="inputType"
+            @input="updateValue"
+            @blur="verifyIpt"
+          />
+        </div>
+
+        <div class="inputVisble" v-if="isPassword" @click="changeVisible">
+          <el-icon v-if="passwordVisiable" class="iconItem">
+            <icon-ic-baseline-visibility></icon-ic-baseline-visibility>
+          </el-icon>
+          <el-icon v-else class="iconItem">
+            <icon-ic-baseline-visibility-off></icon-ic-baseline-visibility-off>
+          </el-icon>
+        </div>
+      </div>
+    </div>
+    <div v-if="showTip" class="tip">{{ errorMsg }}</div>
+  </div>
+</template>
+
+<style scoped>
+.body {
+  min-height: 52px;
+}
+
+.inputBox {
+  min-height: 32px;
+  width: 100%;
+  align-items: center;
+  background-color: #f2f3f5;
+  border-radius: 2px;
+  cursor: text;
+  border: 1px solid transparent;
+  display: flex;
+  align-items: center;
+  box-sizing: border-box;
+  transition: background-color 0.2s;
+}
+
+.inputBox:hover {
+  background-color: #dbdcdf;
+}
+
+.activeInput {
+  border: 1px solid #165dff;
+}
+
+.inputIconBox {
+  width: 100%;
+  height: 100%;
+  padding: 0 10px;
+  display: inline-flex;
+  align-items: center;
+  box-sizing: border-box;
+}
+
+.tip {
+  min-height: 20px;
+  color: red;
+  font-size: 12px;
+  line-height: 20px;
+}
+
+.icon {
+  padding-right: 10px;
+  user-select: none;
+}
+
+.inputContent {
+  width: 100%;
+  display: flex;
+  align-items: center;
+}
+
+.inputBody {
+  font-size: 14px;
+}
+
+.iconItem {
+  display: flex;
+  align-items: center;
+  height: 100%;
+}
+
+.inputVisble {
+  height: 100%;
+  padding-left: 12px;
+  cursor: pointer;
+}
+
+input {
+  width: 100%;
+  background: none;
+  outline: none;
+  border: none;
+}
+
+/* 如果input必须要有边框,但需要去掉选中时的蓝色框 ↓↓↓ */
+input {
+  background: none;
+  outline: none;
+  /* border: 1px solid #ccc; */
+}
+input:focus {
+  border: none;
+}
+</style>

+ 0 - 78
src/components/input/TextInput.vue

@@ -1,78 +0,0 @@
-<script setup lang="ts">
-import { onMounted, ref } from 'vue'
-
-interface InputInfo {
-  inputObj: {
-    inputValue: string
-    inputType: string
-  }
-}
-// 不要传一个基本类型进来双向绑定会报错
-// 可以用definemodel或者直接传一个对象
-defineProps<InputInfo>()
-
-const inputDivRef = ref()
-
-const activeClass = ref(false)
-
-onMounted(() => {
-  document.addEventListener('click', (e) => {
-    // 如果点击的地方不在 div 内部,则移除 active-class
-    if (inputDivRef.value && !inputDivRef.value.contains(e.target)) {
-      activeClass.value = false
-    } else {
-      activeClass.value = true
-    }
-  })
-})
-</script>
-
-<template>
-  <div class="inputBox" ref="inputDivRef" :class="{ activeInput: activeClass }">
-    <div class="icon">
-      <el-icon><UserFilled /></el-icon>
-    </div>
-    <input :type="inputObj.inputType" v-model="inputObj.inputValue" />
-  </div>
-</template>
-
-<style scoped>
-.inputBox {
-  min-height: 32px;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  background-color: #f2f3f5;
-  padding: 0 5px;
-  border-radius: 2px;
-}
-
-.inputBox:hover {
-  background-color: #dbdcdf;
-}
-
-.activeInput {
-  border: 1px solid #165dff;
-}
-
-.icon {
-  padding-right: 12px;
-}
-
-input {
-  width: 100%;
-  background: none;
-  outline: none;
-  border: none;
-}
-
-/* 如果input必须要有边框,但需要去掉选中时的蓝色框 ↓↓↓ */
-input {
-  background: none;
-  outline: none;
-  /* border: 1px solid #ccc; */
-}
-input:focus {
-  border: none;
-}
-</style>

+ 1 - 1
src/hooks/useDialog.ts

@@ -8,7 +8,7 @@ import 'element-plus/theme-chalk/el-message.css'
 import 'element-plus/theme-chalk/el-message-box.css'
 
 export function useDialog() {
-  const { AllApi, analysisResCode } = useRequest()
+  const { analysisResCode } = useRequest()
 
   // 对话框关闭
   const dialogClose = (formEl: FormInstance | undefined, dialogConfig: DialogSetting) => {

+ 1 - 1
src/stores/useTable.ts

@@ -1,4 +1,4 @@
-import { ref, computed, reactive } from 'vue'
+import { reactive } from 'vue'
 import { defineStore } from 'pinia'
 
 // 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。

+ 9 - 0
src/types/input.ts

@@ -0,0 +1,9 @@
+export interface RuleType {
+  validator: () => boolean
+  errMsg: string
+}
+
+export interface RuleInfo {
+  name: string
+  rules: Array<RuleType>
+}

+ 6 - 27
src/utils/echarts/CoreEcharts.ts

@@ -1,39 +1,19 @@
+// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
 import * as echarts from 'echarts/core'
-import { BarChart, LineChart } from 'echarts/charts'
+// 引入柱状图图表,图表后缀都为 Chart
+import { BarChart } from 'echarts/charts'
+// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
 import {
   TitleComponent,
   TooltipComponent,
   GridComponent,
-  // 数据集组件
   DatasetComponent,
-  // 内置数据转换器组件 (filter, sort)
   TransformComponent
 } from 'echarts/components'
+// 标签自动布局、全局过渡动画等特性
 import { LabelLayout, UniversalTransition } from 'echarts/features'
+// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
 import { CanvasRenderer } from 'echarts/renderers'
-import type {
-  // 系列类型的定义后缀都为 SeriesOption
-  BarSeriesOption,
-  LineSeriesOption
-} from 'echarts/charts'
-import type {
-  // 组件类型的定义后缀都为 ComponentOption
-  TitleComponentOption,
-  TooltipComponentOption,
-  GridComponentOption,
-  DatasetComponentOption
-} from 'echarts/components'
-import type { ComposeOption } from 'echarts/core'
-
-// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
-type ECOption = ComposeOption<
-  | BarSeriesOption
-  | LineSeriesOption
-  | TitleComponentOption
-  | TooltipComponentOption
-  | GridComponentOption
-  | DatasetComponentOption
->
 
 // 注册必须的组件
 echarts.use([
@@ -43,7 +23,6 @@ echarts.use([
   DatasetComponent,
   TransformComponent,
   BarChart,
-  LineChart,
   LabelLayout,
   UniversalTransition,
   CanvasRenderer

+ 2 - 2
src/views/Home/PlayerManageView.vue

@@ -13,7 +13,7 @@ import Table from '@/components/Table.vue'
 import CryptoJS from 'crypto-js'
 import axiosInstance from '@/utils/axios/axiosInstance'
 
-import { onMounted, reactive, ref, watch } from 'vue'
+import { onMounted, reactive, ref } from 'vue'
 import { ElMessageBox } from 'element-plus'
 import type { FormRules, FormInstance } from 'element-plus'
 
@@ -23,7 +23,7 @@ import { useDialog } from '@/hooks/useDialog'
 
 const { AllApi, analysisResCode } = useRequest()
 const tableStore = useTableStore()
-const { dialogClose, submitDialog, handleEdit, addNeweItem } = useDialog()
+const { dialogClose, submitDialog, handleEdit } = useDialog()
 
 // 对话框表单数据格式
 interface PlayerDialogFormData {

+ 97 - 35
src/views/Login/LoginView.vue

@@ -1,30 +1,26 @@
 <script setup lang="ts">
 import { onMounted, reactive, ref } from 'vue'
-import type { FormRules, FormInstance } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import type { RuleInfo } from '@/types/input'
 import { useRequest } from '@/hooks/useRequest'
 import router from '@/router'
 import axiosInstance from '@/utils/axios/axiosInstance'
-import TextInput from '@/components/input/TextInput.vue'
+import MyButton from '@/components/form/MyButton.vue'
+import MyInput from '@/components/form/MyInput.vue'
+import login from '@/router/login'
 
 const { AllApi, analysisResCode } = useRequest()
 
-interface loginRuleType {
+interface LoginInfoType {
   userName: string
   password: string
 }
 
-const rules = reactive<FormRules<loginRuleType>>({
-  userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
-})
-
-const loginInfo = reactive<loginRuleType>({
+const loginInfo = reactive<LoginInfoType>({
   userName: '',
   password: ''
 })
 
-const loginFormRef = ref<FormInstance>()
-
 const userLogin = () => {
   axiosInstance
     .post(AllApi.userLogin, loginInfo)
@@ -57,6 +53,46 @@ const onSubmit = (formEl: FormInstance | undefined) => {
   })
 }
 
+const formFieldsRules = reactive<{
+  [key: string]: RuleInfo
+}>({
+  userName: {
+    name: 'username',
+    rules: [
+      {
+        validator: (): boolean => {
+          console.log(loginInfo.userName.trim())
+          return loginInfo.userName.trim().length > 0
+        },
+        errMsg: '用户名不能为空'
+      },
+      {
+        validator: (): boolean => {
+          return loginInfo.userName.trim().length >= 1 && loginInfo.userName.trim().length <= 16
+        },
+        errMsg: '长度为1-16位'
+      }
+    ]
+  },
+  password: {
+    name: 'password',
+    rules: [
+      {
+        validator: (): boolean => {
+          return loginInfo.password.trim().length > 0
+        },
+        errMsg: '密码不能为空'
+      },
+      {
+        validator: (): boolean => {
+          return loginInfo.password.trim().length >= 1 && loginInfo.password.trim().length <= 16
+        },
+        errMsg: '长度为1-16位'
+      }
+    ]
+  }
+})
+
 // 加载数据
 onMounted(() => {})
 </script>
@@ -71,30 +107,34 @@ onMounted(() => {})
           <div class="loginFormTitle">登录管理系统</div>
           <div class="loginFormSubTitle">登录管理系统</div>
           <form class="loginForm">
-            <TextInput
-              :input-obj="{
-                inputType: 'text',
-                inputValue: loginInfo.userName
-              }"
-            ></TextInput>
+            <div class="loginFormItem">
+              <MyInput
+                p-input-type="'text'"
+                :pinput-rules="formFieldsRules.userName"
+                v-model="loginInfo.userName"
+                class="userName"
+              >
+                <template #icon>
+                  <UserFilled />
+                </template>
+              </MyInput>
+            </div>
+            <div class="loginFormItem">
+              <MyInput
+                p-input-type="'password'"
+                :pinput-rules="formFieldsRules.password"
+                v-model="loginInfo.password"
+                class="password"
+              >
+                <template #icon>
+                  <icon-mdi-password></icon-mdi-password>
+                </template>
+              </MyInput>
+            </div>
+            <div class="loginFormItem loginBtn">
+              <MyButton @click-event="userLogin" :btn-info="{ text: '登录' }"></MyButton>
+            </div>
           </form>
-          <!-- <el-form
-            ref="loginFormRef"
-            class="loginForm"
-            :rules="rules"
-            :model="loginInfo"
-            label-width="auto"
-          >
-            <el-form-item label="用户名" prop="userName">
-              <el-input class="formItem" v-model="loginInfo.userName" />
-            </el-form-item>
-            <el-form-item label="密码" prop="password">
-              <el-input class="formItem" v-model="loginInfo.password" />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" @click="onSubmit(loginFormRef)">登录</el-button>
-            </el-form-item>
-          </el-form> -->
         </div>
       </div>
     </div>
@@ -107,6 +147,18 @@ onMounted(() => {})
   height: 100vh;
   background-color: lightblue;
   position: relative;
+  font-family:
+    Inter,
+    -apple-system,
+    BlinkMacSystemFont,
+    PingFang SC,
+    Hiragino Sans GB,
+    noto sans,
+    Microsoft YaHei,
+    Helvetica Neue,
+    Helvetica,
+    Arial,
+    sans-serif;
 }
 
 .loginBox {
@@ -159,7 +211,17 @@ onMounted(() => {})
   align-items: center;
 }
 
-.formItem {
+.loginFormItem {
   width: 100%;
+  // min-height: 52px;
+}
+
+.loginBtn {
+  margin-bottom: 30px;
+  margin-top: 30px;
+}
+
+.userName {
+  margin: 1% 0;
 }
 </style>