bzkf3 2 роки тому
батько
коміт
98f4d89dcb

+ 7 - 1
src/App.vue

@@ -4,7 +4,13 @@
 
 <template>
   <el-config-provider :message="{ max: 3 }">
-    <router-view />
+    <router-view class="w-screen h-screen" />
   </el-config-provider>
 </template>
 
+<style lang="scss">
+body{
+  margin: 0;
+  padding: 0;
+}
+</style>

BIN
src/assets/img/login/bar.webp


+ 17 - 0
src/components/AppLink/index.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { isExternal } from '~/utils/validate'
+const props = defineProps<{ to: string }>()
+const router = useRouter()
+const linkTo = () => {
+  router.push({ name: props.to })
+}
+</script>
+
+<template>
+  <a v-if="isExternal(props.to)" :href="props.to" target="_blank">
+    <slot />
+  </a>
+  <div v-else @click="linkTo">
+    <slot />
+  </div>
+</template>

+ 116 - 0
src/components/Breadcrumb/index.vue

@@ -0,0 +1,116 @@
+<script setup lang="ts">
+import type { RouteRecordName } from 'vue-router'
+const router = useRouter()
+const route = useRoute()
+const matchedList = computed(() => {
+  const res: { title: unknown; name?: RouteRecordName }[] = []
+  route.matched.forEach((item) => {
+    if (item.meta.breadcrumb) {
+      res.push({
+        name: item.name,
+        title: item.meta.title,
+      })
+    }
+  })
+  return res
+})
+
+function routerReplace(name?: RouteRecordName) {
+  if (name === route.name)
+    return
+  router.replace({ name: name ?? '/' })
+}
+</script>
+
+<template>
+  <el-breadcrumb class="text-hex-909399 tracking-wide">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="({ name, title }) in matchedList" :key="name" @click="routerReplace(name)">
+        <span class="cursor-pointer" :class="name === route.name ? 'text-hex-303133' : 'text-hex-909399'">{{ title
+        }}</span>
+        <!-- <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> -->
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<!-- <script>
+
+export default {
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route() {
+      this.getBreadcrumb()
+    }
+  },
+  created() {
+    this.getBreadcrumb()
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+      const first = matched[0]
+
+      if (!this.isDashboard(first)) {
+        matched = [{ path: '/', meta: { title: '首页' }}].concat(matched)
+      }
+
+      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+    },
+    isDashboard(route) {
+      const name = route && route.name
+      if (!name) {
+        return false
+      }
+      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    },
+    pathCompile(path) {
+      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
+      const { params } = this.$route
+      const toPath = pathToRegexp.compile(path)
+      return toPath(params)
+    },
+    handleLink(item) {
+      const { redirect, path } = item
+      if (redirect) {
+        this.$router.push(redirect)
+        return
+      }
+      this.$router.push(this.pathCompile(path))
+    }
+  }
+}
+</script> -->
+
+<style lang="scss" scoped>
+.text-hex-909399 :deep(.el-breadcrumb__separator){
+  color: #909399;
+  font-weight: 300;
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}
+</style>

+ 44 - 0
src/components/InputSearch/index.vue

@@ -0,0 +1,44 @@
+<script lang="ts" setup>
+const props = defineProps<{
+  modelValue?: string
+}>()
+
+const emits = defineEmits(['update:model-value', 'search'])
+
+const text = ref(props.modelValue ?? '')
+</script>
+
+<template>
+  <el-input v-model="text" placeholder="搜索" @change="emits('update:model-value', $event)">
+    <template #prepend>
+      <div class="w-full h-full flex_center" @click="emits('search', text)">
+        <i:ic:baseline-search class="w-5 h-5 text-hex-3F8CFF" />
+      </div>
+    </template>
+  </el-input>
+</template>
+
+<!--
+<style lang="scss" scoped>
+:deep(.el-input) {
+  height: 42px;
+  background: #EBF3FF;
+  border-radius: 13px;
+
+  .el-input-group__prepend {
+    box-shadow: none;
+    background: #D0E2FF;
+    border-radius: 13px;
+    width: 42px;
+    padding: 0;
+    cursor: pointer;
+  }
+
+  .el-input__wrapper {
+    box-shadow: none;
+    background: #EBF3FF;
+    border-radius: 0 8px 8px 0;
+  }
+}
+</style>
+-->

+ 75 - 67
src/layout/app/components/AppHeader/index.vue

@@ -1,95 +1,103 @@
 <script setup>
-import {ref} from 'vue';
-import { user} from '~/store/user';
-const APP_TITLE = ref(document.title)
+import Breadcrumb from '~/components/Breadcrumb/index.vue'
+// import { user } from '~/store/user'
+
+const user = undefined
 function handleCommand(command) {
   switch (command) {
     case 'loginOut':
       if (sessionStorage.getItem('userInfo')) {
-        sessionStorage.removeItem('userInfo');
-        window.open(window.GLOBAL_CONFIG.origin, '_self');
+        sessionStorage.removeItem('userInfo')
+        window.open(window.GLOBAL_CONFIG.origin, '_self')
         // loginShow = true;
       }
-      break;
+      break
     case 'home':
-      window.open(window.GLOBAL_CONFIG.origin, '_self');
+      window.open(window.GLOBAL_CONFIG.origin, '_self')
   }
 }
 </script>
 
 <template>
-  <div class="header w-full h-50px leading-50px line px-8 text-xl text-white tracking-wider">
-    <div>{{ APP_TITLE }}</div>
-    <div>
-      <span class="icon-message"></span>
-      <el-dropdown @command="handleCommand">
-        <div>
-          <div class="shot pr hand">
-            <img :src="user.user_avatar" alt="" :key="user?.user_avatar">
-            <span class="dot"></span>
-          </div>
-          <h3 class="shot-name">{{user.user_realname}}</h3>
-        </div>
+  <div class="w-full leading-50px py-2 px-8 text-xl text-white  flex justify-between items-center">
+    <Breadcrumb />
 
+    <div class="flex items-center justify-evenly">
+      <i:clarity:bell-line class="text-hex-606266 mr-6" />
+      <i:clarity:bell-outline-badged v-show="false" class="text-hex-606266 mr-6" />
+      <el-avatar class="mr-1" />
+      <el-dropdown @command="handleCommand">
+        <span class="flex items-center cursor-pointer text-center">
+          <div class="min-w-16">{{ user?.user_realname ?? '测试用户' }}</div>
+          <i:ep:arrow-down class="ml-1" />
+        </span>
         <template #dropdown>
           <el-dropdown-menu>
-            <el-dropdown-item command="home">首页</el-dropdown-item>
-            <el-dropdown-item command="loginOut">退出登录</el-dropdown-item>
+            <el-dropdown-item command="home">
+              首页
+            </el-dropdown-item>
+            <el-dropdown-item command="loginOut">
+              退出登录
+            </el-dropdown-item>
           </el-dropdown-menu>
         </template>
       </el-dropdown>
-
     </div>
   </div>
 </template>
-<style lang="scss">
-  .header{
-    display: flex;
-    justify-content: space-between;
-    .icon-message {
-      margin-right: 20px;
-      display: inline-block;
-      vertical-align: middle;
-      width: 16px;
-      height: 20px;
-      background: url("/images/header/icon-message.png") center no-repeat;
-    }
-  }
-  .shot {
-    margin-top: 1px;
-    position: relative;
-    cursor: pointer;
+
+<!-- <style lang="scss">
+.header {
+  display: flex;
+  justify-content: space-between;
+
+  .icon-message {
+    margin-right: 20px;
     display: inline-block;
     vertical-align: middle;
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
-    background: #fff;
-    overflow: hidden;
-    img {
-      width: 100%;
-      height: 100%;
-      border-radius: 50%;
-    }
+    width: 16px;
+    height: 20px;
+    background: url("/images/header/icon-message.png") center no-repeat;
+  }
+}
 
-    .dot {
-      position: absolute;
-      right: 1px;
-      bottom: 1px;
-      width: 14px;
-      height: 14px;
-      border-radius: 50%;
-      background: #3EBB92;
-      border: 2px solid #F1FEF6;
-    }
+.shot {
+  margin-top: 1px;
+  position: relative;
+  cursor: pointer;
+  display: inline-block;
+  vertical-align: middle;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background: #fff;
+  overflow: hidden;
+
+  img {
+    width: 100%;
+    height: 100%;
+    border-radius: 50%;
   }
-  .shot-name{
-    margin-left: 10px;
-    display: inline-block;
-    vertical-align: middle;
-    font-size: 14px;
-    color: #fff;
-    line-height: 50px;
+
+  .dot {
+    position: absolute;
+    right: 1px;
+    bottom: 1px;
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    background: #3EBB92;
+    border: 2px solid #F1FEF6;
   }
-</style>
+}
+
+.shot-name {
+  margin-left: 10px;
+  display: inline-block;
+  vertical-align: middle;
+  font-size: 14px;
+  color: #fff;
+  line-height: 50px;
+}
+</style> -->
 

+ 21 - 49
src/layout/app/components/AppSider/MenuItem.vue

@@ -1,66 +1,38 @@
 <script setup lang="ts">
+import { ref } from 'vue'
 import Item from './Item.vue'
-import MenuItem from './MenuItem.vue';
-import type { RouteRecordDetailRaw } from '~/router/routes.d'
-import { ref } from 'vue';
-
+import MenuItem from './MenuItem.vue'
+import type { RouteRecordRawWithMeta } from '~/router/routes.d'
 
 const props = defineProps<{
-  item: RouteRecordDetailRaw
+  item: RouteRecordRawWithMeta
 }>()
-
-const currentRoute: RouteRecordDetailRaw = props.item
-const showRoute = ref<RouteRecordDetailRaw>(currentRoute)
-
-const showMode = ref<number>(0)
-
-function filterShowChild(children?: RouteRecordDetailRaw[]): RouteRecordDetailRaw[] {
-  if (Array.isArray(children)) {
-    const temp = children.filter(child => !child.meta.hidden).sort((a, b) => a.meta.sort - b.meta.sort)
-    if (temp.length === 1 && Array.isArray(temp[0].children)) {
-      return filterShowChild(temp[0].children)
-    }
-    return temp
-  }
-  else {
-    return []
-  }
-}
-const currentRouteShowChildren = filterShowChild(currentRoute.children)
-
-if (currentRouteShowChildren.length === 0) {
-  showMode.value = 1
-}
-else if (currentRouteShowChildren.length === 1) {
-  showRoute.value = currentRouteShowChildren[0]
-  showMode.value = 1
-}
-else {
-  showMode.value = 2
-}
-
 </script>
 
 <template>
   <template v-if="!item?.meta?.hidden">
-    <template v-if="showMode === 1">
-      <AppLink :to="showRoute.name">
-        <el-menu-item :index="showRoute.name">
-          <Item :icon="showRoute?.meta?.icon" :title="showRoute?.meta?.title"></Item>
-        </el-menu-item>
-      </AppLink>
-    </template>
-
-    <template v-if="showMode === 2">
-      <el-sub-menu :index="showRoute.name">
+    <template v-if="item?.children?.length">
+      <el-sub-menu :index="item.name">
         <template #title>
-          <Item :icon="showRoute?.meta?.icon" :title="showRoute.meta.title"></Item>
+          <Item :icon="item?.meta?.icon" :title="item.meta.title" />
         </template>
 
-        <MenuItem v-for="route in showRoute.children" :item="route" />
-
+        <MenuItem v-for="route in item.children" :key="route.name" :item="route" />
       </el-sub-menu>
     </template>
+
+    <template v-else>
+      <AppLink :to="item.name">
+        <el-menu-item :index="item.name">
+          <Item :icon="item?.meta?.icon" :title="item?.meta?.title" />
+        </el-menu-item>
+      </AppLink>
+    </template>
   </template>
 
+  <template v-else>
+    <template v-if="item?.children?.length">
+      <MenuItem v-for="route in item.children" :key="route.name" :item="route" />
+    </template>
+  </template>
 </template>

+ 15 - 11
src/layout/app/components/AppSider/index.vue

@@ -1,32 +1,36 @@
 <script setup lang="ts">
-import { ref, computed, watch } from 'vue';
-import { useRouter, useRoute } from 'vue-router';
+import { computed, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import MenuItem from './MenuItem.vue'
 import type { RouteRecordRawWithMeta } from '~/router/routes.d'
-import MenuItem from './MenuItem.vue';
+import { asyncFilteredRoutes } from '~/router/index'
 
 const router = useRouter()
 const route = useRoute()
 
 const allRoutes = computed(() => {
-  const routes: RouteRecordRawWithMeta[] = JSON.parse(sessionStorage.getItem('routes') as string)
+  const routes = asyncFilteredRoutes as unknown as RouteRecordRawWithMeta[]
   return routes.sort((a, b) => a.meta.sort - b.meta.sort)
 })
+console.log('allRoutes :>> ', allRoutes)
 const index = computed(() => {
   const reservedMatched = [...route?.matched].reverse()
   for (const r of reservedMatched) {
-    if (!r.meta.hidden) {
+    if (!r.meta.hidden)
       return ((r?.redirect as { name: string })?.name) ?? r.name
-    }
   }
+  return '/'
 })
-
 </script>
 
 <template>
-  <el-menu class="h-full border-none" :default-active="index">
-    <MenuItem v-for="route in allRoutes" :item="route">
-    </MenuItem>
+  <el-menu class="w-full border_right_none" :default-active="index" background-color="#041220" text-color="#fff">
+    <MenuItem v-for="route in allRoutes" :key="route.name" :item="route" />
   </el-menu>
 </template>
 
-
+<style lang="scss" scoped>
+.border_right_none{
+  border-right: none;
+}
+</style>

+ 24 - 20
src/layout/app/index.vue

@@ -1,35 +1,39 @@
 <script setup lang="ts">
-import AppHeader from './components/AppHeader/index.vue';
-import AppSider from './components/AppSider/index.vue';
+import AppHeader from './components/AppHeader/index.vue'
+import AppSider from './components/AppSider/index.vue'
 
 // import Breadcrumb from '~/components/Breadcrumb/index.vue';
 // import AppMain from './components/AppMain/index.vue';
 </script>
 
 <template>
-
-  <div class="flex flex-col w-screen h-screen">
-    <header class="w-full flex-none bg-hex-00A3FF">
-      <AppHeader></AppHeader>
-    </header>
-    <div class="w-full flex-auto flex flex-row overflow-hidden relative">
-      <aside class="w-210px flex-none h-full border">
-        <el-scrollbar view-class="h-full">
-          <AppSider></AppSider>
-        </el-scrollbar>
-      </aside>
-      <div class="divide-x"></div>
-      <main class="flex-auto flex flex-col overflow-hidden">
+  <div class="flex w-screen h-screen">
+    <aside class="w-216px flex-none h-full bg-hex-041220">
+      <el-scrollbar view-class="h-full flex flex-col">
+        <h1 class="text-xl text-white font-extrabold w-full text-center mt-8 mb-12">
+          蒙阴县教育数字平台
+        </h1>
+        <AppSider class="flex-auto" />
+      </el-scrollbar>
+    </aside>
+    <div class="h-full flex flex-col flex-auto ">
+      <header class="w-full flex-none bg-hex-041220">
+        <AppHeader class="bg-white border_lt overflow-hidden" />
+      </header>
+      <main class="w-full flex-auto flex flex-col overflow-hidden bg-hex-f6f9fd">
         <div class="w-full h-full">
-          <!-- <Breadcrumb class="flex-none mb-4"></Breadcrumb> -->
-
-          <el-scrollbar always wrap-class="w-full h-full box-border" view-class="relative h-full flex flex-col p-4">
-            <router-view></router-view>
+          <el-scrollbar always wrap-class="w-full h-full box-border" view-class="relative h-full w-full">
+            <router-view />
           </el-scrollbar>
         </div>
       </main>
     </div>
   </div>
-
 </template>
 
+<style lang="scss" scoped>
+.border_lt{
+  border-radius: 2.25rem 0px 0px 0px;
+}
+</style>
+

+ 77 - 1
src/pages/login.vue

@@ -1,7 +1,83 @@
 <script setup lang="ts">
+import type { FormInstance, FormRules } from 'element-plus'
+import bar from '~/assets/img/login/bar.webp'
+const route = useRoute()
+console.log('route :>> ', route)
+interface IForm {
+  name: string
+  pwd: string
+}
+const lastForm = localStorage.getItem('login-form')
+const form: IForm = reactive(
+  lastForm
+    ? (JSON.parse(atob(lastForm)))
+    : {
+        name: '',
+        pwd: '',
+      },
+)
 
+const rules: FormRules = reactive({
+
+})
+
+const formRef = $ref<FormInstance>()
+
+const isRemember = $ref(!!lastForm)
+
+function handleLogin() {
+  formRef?.validate((valid) => {
+    if (valid) {
+      localStorage.removeItem('login-form')
+      isRemember && localStorage.setItem('login-form', btoa(JSON.stringify(form)))
+
+      // TODO: request login api
+      //
+    }
+  })
+}
 </script>
 
 <template>
-  <h1>login</h1>
+  <div class="flex">
+    <div class="flex-auto flex_center min-w-580px">
+      <div class="w-470px flex flex-col">
+        <h1 class="text-5xl mb-24">
+          蒙阴县教育数字平台
+        </h1>
+        <el-form ref="formRef" label-position="top" :model="form" :rules="rules" size="large">
+          <el-form-item label="账户">
+            <el-input v-model="form.name" class="h-56px" clearable />
+          </el-form-item>
+          <el-form-item label="密码" class="mt-8">
+            <el-input v-model="form.pwd" type="password" show-password class="h-56px" />
+          </el-form-item>
+        </el-form>
+
+        <div class="flex justify-between my-6">
+          <el-checkbox v-model="isRemember" label="记住密码" size="large" />
+          <el-link type="primary" class="text-xs" :underline="false">
+            忘记密码
+          </el-link>
+        </div>
+
+        <div
+          class="rounded bg-hex-409eff hover:bg-hex-40bbff h-50px flex_center text-white text-sm font-semibold box_shadow tracking-wider cursor-pointer"
+          @click="handleLogin"
+        >
+          登录
+        </div>
+      </div>
+    </div>
+    <div class="h-full min-w-683px bg-hex-3F8CFF w-1/2 flex_center">
+      <img :src="bar" alt="" class="h-full">
+    </div>
+  </div>
 </template>
+
+<style lang="scss" scoped>
+._shadow {
+  text-shadow: 0px 7px 14px 0px rgba(143, 149, 178, 0.15);
+  box-shadow: 0px 7px 14px 0px rgba(143, 149, 178, 0.15), 0px 7px 14px 0px rgba(143, 149, 178, 0.15);
+}
+</style>

+ 49 - 0
src/pages/ptsz/xxsz/jd.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+let tableData = $ref([])
+
+let multipleSelection = $ref<unknown[]>([])
+function handleSelectionChange(val: unknown[]) {
+  multipleSelection = val
+}
+
+Promise.resolve([]).then((res: never[]) => {
+  tableData = res
+})
+
+const text = ref('')
+</script>
+
+<template>
+  <div class="card mt-7">
+    <div class="flex justify-between">
+      <div>
+        <el-button type="primary">
+          批量导入
+        </el-button>
+        <el-button type="primary">
+          新增
+        </el-button>
+      </div>
+      <div class="flex">
+        <input-search v-model="text" class="mr-4" />
+        <el-button type="danger">
+          删除
+        </el-button>
+      </div>
+    </div>
+
+    <el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="date" label="学校名称" width="180" />
+      <el-table-column prop="name" label="学校地址" width="180" />
+      <el-table-column prop="name" label="联系电话" width="180" />
+      <el-table-column prop="name" label="邮箱" width="180" />
+      <el-table-column prop="name" label="操作" width="180" />
+    </el-table>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import "~/styles/input-search.scss";
+@import "~/styles/button.scss";
+</style>

+ 10 - 6
src/router/index.ts

@@ -5,17 +5,16 @@ const modules: Record<string, { default: RouteRecordRaw }> = import.meta.glob('.
 
 export const constantRoutes: RouteRecordRaw[] = []
 export const asyncRoutes: RouteRecordRaw[] = []
-Object.keys(modules).forEach(path => {
+Object.keys(modules).forEach((path) => {
   const filename = path.split('/').at(-1)
-  if (filename?.startsWith('_')) {
+  if (filename?.startsWith('_'))
     constantRoutes.push(modules[path].default)
-  } else {
+  else
     asyncRoutes.push(modules[path].default)
-  }
 })
 
-console.log('constantRoutes :>> ', constantRoutes);
-console.log('asyncRoutes :>> ', asyncRoutes);
+console.log('constantRoutes :>> ', constantRoutes)
+console.log('asyncRoutes :>> ', asyncRoutes)
 
 const router = createRouter({
   history: createWebHashHistory(),
@@ -24,6 +23,11 @@ const router = createRouter({
 
 export default router
 
+export const asyncFilteredRoutes = asyncRoutes
+asyncFilteredRoutes.forEach((route) => {
+  router.addRoute(route)
+})
+
 router.beforeEach((to, from) => {
   console.groupCollapsed(`%c${from.name?.toString()} => ${to.name?.toString()}`, 'color:#0ff')
   console.log(`%c${from.meta.title} => ${to.meta.title}`, 'color:#0cc')

+ 1 - 0
src/router/routes.d.ts

@@ -25,4 +25,5 @@ interface RouteRecordRawWithMeta extends RouteRecordRaw {
   },
   children?: RouteRecordRawWithMeta[],
   name: string
+  [key:string]:unknown
 }

+ 0 - 12
src/router/routes/example.ts

@@ -1,12 +0,0 @@
-// import type { RouteRecordRaw } from 'vue-router'
-import type { RouteRecordRawWithMeta } from '../routes'
-
-export default <RouteRecordRawWithMeta>{
-  path: '',
-  component: () => import('~/pages/index.vue'),
-  name:'index',
-  meta: {
-    hidden: false,
-    sort: 1,
-  },
-}

+ 77 - 0
src/router/routes/ptsz.ts

@@ -0,0 +1,77 @@
+// generate by plop-starter
+import type { RouteRecordRawWithMeta } from '../routes'
+import AppLayout from '~/layout/app/index.vue'
+export default <RouteRecordRawWithMeta>{
+  path: '/ptsz',
+  name: 'PTSZ',
+  props: false,
+  component: AppLayout,
+  meta: {
+    hidden: false,
+    breadcrumb: true,
+    sort: 0,
+    title: '平台设置',
+  },
+  children: [
+    {
+      path: 'xxsz',
+      name: 'PTSZ_XXSZ',
+      props: false,
+      meta: {
+        hidden: true,
+        breadcrumb: false,
+        sort: 0,
+        title: '学校设置',
+      },
+      children: [
+        {
+          path: 'jd',
+          name: 'PTSZ_XXSZ_JD',
+          props: false,
+          meta: {
+            hidden: true,
+            breadcrumb: false,
+            sort: 0,
+            title: '局端',
+          },
+          children: [
+            {
+              path: '',
+              name: 'PTSZ_XXSZ_JD_INDEX',
+              props: false,
+              meta: {
+                hidden: false,
+                breadcrumb: true,
+                sort: 0,
+                title: '学校设置',
+              },
+              redirect: null,
+              component: () => import('~/pages/ptsz/xxsz/jd.vue'),
+            },
+            {
+              path: 'detail/:id',
+              name: 'PTSZ_XXSZ_JD_DETAIL',
+              props: true,
+              meta: {
+                hidden: true,
+                breadcrumb: true,
+                sort: 1,
+                title: '详情',
+              },
+              redirect: null,
+            },
+          ],
+          redirect: {
+            name: 'PTSZ_XXSZ_JD_INDEX',
+          },
+        },
+      ],
+      redirect: {
+        name: 'PTSZ_XXSZ_JD',
+      },
+    },
+  ],
+  redirect: {
+    name: 'PTSZ_XXSZ',
+  },
+}

+ 9 - 0
src/styles/button.scss

@@ -0,0 +1,9 @@
+:deep(.el-button) {
+  padding: 14px 24px;
+  border-radius: 8px;
+  height: auto;
+  font-size: 12px;
+  font-weight: 600;
+  text-shadow: 0px 1px 10px 0px rgba(0, 73, 198, 0.10);
+  // letter-spacing: 1px;
+}

+ 20 - 0
src/styles/input-search.scss

@@ -0,0 +1,20 @@
+:deep(.el-input) {
+  height: 42px;
+  background: #EBF3FF;
+  border-radius: 13px;
+
+  .el-input-group__prepend {
+    box-shadow: none;
+    background: #D0E2FF;
+    border-radius: 13px;
+    width: 42px;
+    padding: 0;
+    cursor: pointer;
+  }
+
+  .el-input__wrapper {
+    box-shadow: none;
+    background: #EBF3FF;
+    border-radius: 0 8px 8px 0;
+  }
+}

+ 8 - 0
src/utils/validate.ts

@@ -0,0 +1,8 @@
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path: string) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+

+ 1 - 1
windi.config.ts

@@ -3,7 +3,7 @@ import { defineConfig } from 'windicss/helpers'
 export default defineConfig({
   darkMode: 'media',
   shortcuts: {
-    card: 'rounded-xl bg-light-50 p-3 relative box-border',
+    card: 'rounded-xl bg-white p-3 relative box-border',
     divider: 'w-full h-0 border border-solid border-gray-100 my-2',
     divider_y: 'w-0 h-full border border-solid border-gray-100 mx-2',
     icon: 'w-24px h-24px fill-blue-600 bg-light-100 rounded-2px cursor-pointer  mx-4px box-border',