首页+登录页
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 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?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
88
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
const mapStores: typeof import('pinia')['mapStores']
|
||||||
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useId: typeof import('vue')['useId']
|
||||||
|
const useLink: typeof import('vue-router')['useLink']
|
||||||
|
const useModel: typeof import('vue')['useModel']
|
||||||
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
34
components.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
DAside: typeof import('vue-devui/layout/index.es.js')['Aside']
|
||||||
|
DAvatar: typeof import('vue-devui/avatar/index.es.js')['Avatar']
|
||||||
|
DButton: typeof import('vue-devui/button/index.es.js')['Button']
|
||||||
|
DCard: typeof import('vue-devui/card/index.es.js')['Card']
|
||||||
|
DContent: typeof import('vue-devui/layout/index.es.js')['Content']
|
||||||
|
DFooter: typeof import('vue-devui/layout/index.es.js')['Footer']
|
||||||
|
DForm: typeof import('vue-devui/form/index.es.js')['Form']
|
||||||
|
DFormItem: typeof import('vue-devui/form/index.es.js')['FormItem']
|
||||||
|
DHeader: typeof import('vue-devui/layout/index.es.js')['Header']
|
||||||
|
DIcon: typeof import('vue-devui/icon/index.es.js')['Icon']
|
||||||
|
DInput: typeof import('vue-devui/input/index.es.js')['Input']
|
||||||
|
DLayout: typeof import('vue-devui/layout/index.es.js')['Layout']
|
||||||
|
DMenu: typeof import('vue-devui/menu/index.es.js')['Menu']
|
||||||
|
DMenuItem: typeof import('vue-devui/menu/index.es.js')['MenuItem']
|
||||||
|
DModal: typeof import('vue-devui/modal/index.es.js')['Modal']
|
||||||
|
DSelect: typeof import('vue-devui/select/index.es.js')['Select']
|
||||||
|
DTab: typeof import('vue-devui/tabs/index.es.js')['Tab']
|
||||||
|
DTabs: typeof import('vue-devui/tabs/index.es.js')['Tabs']
|
||||||
|
HomeSide: typeof import('./src/components/homeSide.vue')['default']
|
||||||
|
Menu: typeof import('./src/components/menu.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
}
|
||||||
|
}
|
18
env.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
/// <reference types="axios" />
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
let $http: any;
|
||||||
|
let $cookies: any;
|
||||||
|
let $msg: any;
|
||||||
|
let $store:any
|
||||||
|
interface Window {
|
||||||
|
// 扩展Windows环境下的全局$http对象
|
||||||
|
$http: any;
|
||||||
|
$cookies: any;
|
||||||
|
$msg: any;
|
||||||
|
$store:any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { };
|
||||||
|
|
71
index.html
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
! function (e, t, a) {
|
||||||
|
function r() {
|
||||||
|
for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[
|
||||||
|
e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x +
|
||||||
|
"px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e]
|
||||||
|
.scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999");
|
||||||
|
requestAnimationFrame(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
function n() {
|
||||||
|
var t = "function" == typeof e.onclick && e.onclick;
|
||||||
|
e.onclick = function (e) {
|
||||||
|
t && t(), o(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function o(e) {
|
||||||
|
var a = t.createElement("div");
|
||||||
|
a.className = "heart", s.push({
|
||||||
|
el: a,
|
||||||
|
x: e.clientX - 5,
|
||||||
|
y: e.clientY - 5,
|
||||||
|
scale: 1,
|
||||||
|
alpha: 1,
|
||||||
|
color: c()
|
||||||
|
}), t.body.appendChild(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
function i(e) {
|
||||||
|
var a = t.createElement("style");
|
||||||
|
a.type = "text/css";
|
||||||
|
try {
|
||||||
|
a.appendChild(t.createTextNode(e))
|
||||||
|
} catch (t) {
|
||||||
|
a.styleSheet.cssText = e
|
||||||
|
}
|
||||||
|
t.getElementsByTagName("head")[0].appendChild(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
function c() {
|
||||||
|
return "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math
|
||||||
|
.random()) + ")"
|
||||||
|
}
|
||||||
|
var s = [];
|
||||||
|
e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e
|
||||||
|
.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (e) {
|
||||||
|
setTimeout(e, 1e3 / 60)
|
||||||
|
}, i(
|
||||||
|
".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"
|
||||||
|
), n(), r()
|
||||||
|
}(window, document);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
49
package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "blog",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@devui-design/icons": "^1.4.0",
|
||||||
|
"@heroicons/vue": "^2.2.0",
|
||||||
|
"@unocss/reset": "^66.3.3",
|
||||||
|
"axios": "^1.11.0",
|
||||||
|
"devui-theme": "^0.0.7",
|
||||||
|
"es-toolkit": "^1.39.8",
|
||||||
|
"less": "^4.4.0",
|
||||||
|
"ng-devui": "^18.0.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"qs": "^6.14.0",
|
||||||
|
"qweather-icons": "^1.7.0",
|
||||||
|
"unocss": "^66.3.3",
|
||||||
|
"unplugin-auto-import": "^19.3.0",
|
||||||
|
"unplugin-vue-components": "^28.8.0",
|
||||||
|
"vite-svg-loader": "^5.1.0",
|
||||||
|
"vue": "^3.5.18",
|
||||||
|
"vue-devui": "^1.6.33",
|
||||||
|
"vue-router": "^4.5.1",
|
||||||
|
"vue3-cookies": "^1.0.6",
|
||||||
|
"vue3-perfect-scrollbar": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node22": "^22.0.2",
|
||||||
|
"@types/node": "^22.16.5",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
|
"vite": "^7.0.6",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.0",
|
||||||
|
"vue-tsc": "^3.0.4"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 3.2 KiB |
18
src/App.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<PerfectScrollbar>
|
||||||
|
<router-view></router-view>
|
||||||
|
</PerfectScrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 空白项目入口
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.ps {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
23
src/Layout.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<d-layout>
|
||||||
|
<d-header class="dheader-1">
|
||||||
|
<menu-h />
|
||||||
|
</d-header>
|
||||||
|
<d-content class="dcontent-1">
|
||||||
|
<router-view />
|
||||||
|
</d-content>
|
||||||
|
<d-footer class="dfooter-1">footer</d-footer>
|
||||||
|
</d-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 空白项目入口
|
||||||
|
import menuH from '@/components/menu.vue';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dcontent-1 {
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
}
|
||||||
|
</style>
|
0
src/api/file/index.ts
Normal file
56
src/api/mix/index.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import request from "@/util/request";
|
||||||
|
|
||||||
|
//getWeather
|
||||||
|
export function getWeather(params: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: "/mix/wea",
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//getLocation
|
||||||
|
export function getLocation(params: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: "/mix/location",
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//getIp
|
||||||
|
export function getIp() {
|
||||||
|
return request({
|
||||||
|
url: "/mix/ip",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//getIcon
|
||||||
|
export function getIcon(data: any) {
|
||||||
|
return request({
|
||||||
|
url: "/mix/ico",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//getHoli
|
||||||
|
export function getHoli() {
|
||||||
|
return request({
|
||||||
|
url: "/mix/nextholi",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//getJq
|
||||||
|
export function getJq(params: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: "/mix/carl",
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//getBdhot
|
||||||
|
export function getBdhot() {
|
||||||
|
return request({
|
||||||
|
url: "/mix/bdhot",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
17
src/api/nav/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import request from "@/util/request";
|
||||||
|
|
||||||
|
// getNavList
|
||||||
|
export function getNavList() {
|
||||||
|
return request({
|
||||||
|
url: "/navi",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// addNav
|
||||||
|
export function addNav(data: any) {
|
||||||
|
return request({
|
||||||
|
url: "/navi",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
18
src/api/user/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import request from "@/util/request";
|
||||||
|
|
||||||
|
//login
|
||||||
|
export function login(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: "/user/login",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// register
|
||||||
|
export function register(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: "/user/register",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
12
src/assets/base.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ps__thumb-y {
|
||||||
|
background-color: #f6cbe7 !important;
|
||||||
|
}
|
BIN
src/assets/font/LCDML.woff2
Normal file
BIN
src/assets/font/yapi.otf
Normal file
BIN
src/assets/images/05.jpeg
Normal file
After Width: | Height: | Size: 854 KiB |
6
src/assets/main.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@import "./base.css";
|
||||||
|
@import "@devui-design/icons/icomoon/devui-icon.css";
|
||||||
|
@import "qweather-icons/font/qweather-icons.css";
|
||||||
|
@import "@devui-design/icons/icomoon/devui-icon.css";
|
||||||
|
@import "vue-devui/style.css";
|
||||||
|
@import "vue3-perfect-scrollbar/style.css";
|
114
src/components/homeSide.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pr-8">
|
||||||
|
<d-card class="mt-10 bg-white">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<icon-time class="w-5 mr-2"></icon-time>
|
||||||
|
节日天气
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="w-full text-center text-[#ec66ab] font-500 text-2xl font-[yj]">{{ t }}</div>
|
||||||
|
<div class="mt-2 text-center">{{ d }}</div>
|
||||||
|
</template>
|
||||||
|
</d-card>
|
||||||
|
|
||||||
|
<d-card class="mt-10 bg-white">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<icon-date class="w-5 mr-2"></icon-date>
|
||||||
|
农历节气
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="w-full text-center text-[#ec66ab] font-500">{{ jq.yearTips }}年 {{ jq.lunarCalendar }}</div>
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<img class="mt-2 w-[90%] rounded" :src="jqImg" alt=""></img>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-center">{{ jq.solarTerms }}</div>
|
||||||
|
</template>
|
||||||
|
</d-card>
|
||||||
|
|
||||||
|
<d-card class="mt-10 bg-white">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<icon-news class="w-5 mr-2"></icon-news>
|
||||||
|
百度新闻
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="py-1" v-for="i in bdNews" :key="i.id">
|
||||||
|
<a class="devui-link flex justify-between" :href="i.url" target="_blank">
|
||||||
|
<span>{{ i.index }}. {{ i.title }}</span>
|
||||||
|
<span class="text-primary">{{ i.hot }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-center">
|
||||||
|
<a class="devui-link" href="https://www.baidu.com/s?ie=utf-8&wd=百度新闻" target="_blank">更多</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</d-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
//mark import
|
||||||
|
import { formatTime } from '@/util/index';
|
||||||
|
|
||||||
|
//mark data
|
||||||
|
const t = ref<any>('')
|
||||||
|
const d = ref<any>('')
|
||||||
|
let timer: any = null
|
||||||
|
const jq = ref<any>("")
|
||||||
|
const jqImg = ref<any>("")
|
||||||
|
const bdNews = ref<any>([])
|
||||||
|
//mark method
|
||||||
|
function getTime() {
|
||||||
|
const date = new Date();
|
||||||
|
t.value = formatTime(date, 'hh:mm:ss')
|
||||||
|
d.value = formatTime(date, 'YYYY 年 MM 月 DD 日')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJq() {
|
||||||
|
const res = await $http.mix.getJq({
|
||||||
|
date: formatTime(new Date(), "YYYYMMDD")
|
||||||
|
})
|
||||||
|
console.log('>>> --> getJq --> res:', res.data)
|
||||||
|
jq.value = res.data
|
||||||
|
const j = res.data.solarTerms.slice(0, 2)
|
||||||
|
jqImg.value = 'https://www.hxyouzi.com/img/jq/' + j + '.png'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBdhot() {
|
||||||
|
const res = await $http.mix.getBdhot()
|
||||||
|
console.log('>>> --> getBdhot --> res:', res.data)
|
||||||
|
// 取前5条
|
||||||
|
bdNews.value = res.data.slice(0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
//mark 周期、内置函数等
|
||||||
|
onMounted(() => {
|
||||||
|
getJq()
|
||||||
|
getBdhot()
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
getTime()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
@font-face {
|
||||||
|
font-family: 'yj';
|
||||||
|
src: url('@/assets/font/LCDML.woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-card__shadow--hover:hover, .devui-card__shadow--always) {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
212
src/components/menu.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="nav"
|
||||||
|
class="main-nav flex justify-between bg-white border-b border-gray-100 shadow-[0_2px_10px_rgba(173,21,21,0.05)]">
|
||||||
|
<!-- 网站Logo -->
|
||||||
|
<div class="px-5 flex items-center" slot="brand" @click="goHome">
|
||||||
|
<img src="@/logo/红色字体.png" alt="柚子的网站" class="h-9 align-middle" />
|
||||||
|
</div>
|
||||||
|
<!-- 主导航菜单 -->
|
||||||
|
<d-menu mode="horizontal" router class="ml-5 h-14 text-[16px]" :default-select-keys="[key]">
|
||||||
|
<d-menu-item key="home">
|
||||||
|
<d-icon :component="homeSvg" class="w-5 mr-1"></d-icon>
|
||||||
|
首页
|
||||||
|
</d-menu-item>
|
||||||
|
<d-menu-item key="gallery">
|
||||||
|
<d-icon :component="picSvg" class="w-5 mr-1"></d-icon>
|
||||||
|
画廊
|
||||||
|
</d-menu-item>
|
||||||
|
<d-menu-item key="article">
|
||||||
|
<d-icon :component="artiSvg" class="w-5 mr-1 "></d-icon>
|
||||||
|
文章
|
||||||
|
</d-menu-item>
|
||||||
|
<d-menu-item key="widget">
|
||||||
|
<d-icon :component="settingSvg" class="w-5 mr-1 "></d-icon>
|
||||||
|
工具
|
||||||
|
</d-menu-item>
|
||||||
|
<d-menu-item key="appshare">
|
||||||
|
<d-icon :component="downSvg" class="w-5 mr-1 "></d-icon>
|
||||||
|
软件分享
|
||||||
|
</d-menu-item>
|
||||||
|
<d-menu-item key="plink">
|
||||||
|
<d-icon :component="linkSvg" class="w-5 mr-1 "></d-icon>
|
||||||
|
友链
|
||||||
|
</d-menu-item>
|
||||||
|
</d-menu>
|
||||||
|
<!-- 用户区域 -->
|
||||||
|
<div class="!text-[#ec66ab] user-area flex items-center px-3">
|
||||||
|
<span class="flex items-center location-info ">
|
||||||
|
<d-icon color="#ec66ab" class="mr-1" name="location-new"></d-icon>
|
||||||
|
{{ locationInfo }}
|
||||||
|
</span>
|
||||||
|
<span class="mx-3 text-gray-300">|</span>
|
||||||
|
<span class="weather-info mr-2">{{ wea }}</span>
|
||||||
|
<i :class="'qiIcon qi-' + weaIcon + '-fill'"></i>
|
||||||
|
<span class="weather-info ml-4">{{ temp }}°C</span>
|
||||||
|
<d-avatar v-if="userinfo" :img-src="userinfo.ava_url" class="ml-20 cursor-pointer" alt="用户的头" />
|
||||||
|
<d-avatar v-else class="ml-20 cursor-pointer" @click="toLogin"></d-avatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 从@/icon/menu引入所有的svg文件
|
||||||
|
import artiSvg from '@/icon/menu/arti.svg';
|
||||||
|
import downSvg from '@/icon/menu/download.svg';
|
||||||
|
import homeSvg from '@/icon/menu/home.svg';
|
||||||
|
import linkSvg from '@/icon/menu/link.svg';
|
||||||
|
import picSvg from '@/icon/menu/pic.svg';
|
||||||
|
import settingSvg from '@/icon/menu/setting.svg';
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const key = ref("home");
|
||||||
|
const locationInfo = ref("获取位置中...");
|
||||||
|
const latitude = ref<number | null>(null);
|
||||||
|
const longitude = ref<number | null>(null);
|
||||||
|
const wea = ref("")
|
||||||
|
const weaIcon = ref<string>("")
|
||||||
|
const temp = ref<number>(0);
|
||||||
|
const userinfo: any = ref(null)
|
||||||
|
const nav: any = useTemplateRef('nav')
|
||||||
|
const navx = $store.nav.useNavStore()
|
||||||
|
// 获取地理位置
|
||||||
|
const getLocation = () => {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
locationInfo.value = "您的浏览器不支持地理位置定位";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
async (position) => {
|
||||||
|
latitude.value = position.coords.latitude;
|
||||||
|
longitude.value = position.coords.longitude;
|
||||||
|
const jw = `${longitude.value.toFixed(2)},${latitude.value.toFixed(2)}`;
|
||||||
|
|
||||||
|
// 这里可以添加调用后端API获取具体位置名称的逻辑
|
||||||
|
handdleJw(jw);
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
async (error) => {
|
||||||
|
switch (error.code) {
|
||||||
|
case error.PERMISSION_DENIED:
|
||||||
|
locationInfo.value = "用户拒绝了位置请求";
|
||||||
|
break;
|
||||||
|
case error.POSITION_UNAVAILABLE:
|
||||||
|
locationInfo.value = "位置信息不可用";
|
||||||
|
break;
|
||||||
|
case error.TIMEOUT:
|
||||||
|
locationInfo.value = "获取位置超时";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const res = await $http.mix.getIp();
|
||||||
|
latitude.value = res.data.lat;
|
||||||
|
longitude.value = res.data.lon;
|
||||||
|
const jw = `${longitude.value?.toFixed(2)},${latitude.value?.toFixed(2)}`;
|
||||||
|
|
||||||
|
// 这里可以添加调用后端API获取具体位置名称的逻辑
|
||||||
|
handdleJw(jw);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableHighAccuracy: false,
|
||||||
|
timeout: 5000,
|
||||||
|
maximumAge: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handdleJw(jw: string) {
|
||||||
|
const zxs = ['北京', '重庆', '天津', '上海']
|
||||||
|
// 根据经纬度获取物理位置
|
||||||
|
const loc = await $http.mix.getLocation({ location: jw });
|
||||||
|
console.log(loc);
|
||||||
|
if (loc.code == 200) {
|
||||||
|
const data = loc.data;
|
||||||
|
if (zxs.includes(data.adm2)) {
|
||||||
|
locationInfo.value = `${data.country}${data.adm1}${data.name}`;
|
||||||
|
} else {
|
||||||
|
locationInfo.value = `${data.adm1}${data.adm2}${data.name}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$msg.console.error(loc.msg);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await $http.mix.getWeather({ location: jw });
|
||||||
|
console.log(res);
|
||||||
|
if (res.code == 200) {
|
||||||
|
wea.value = res.data.text;
|
||||||
|
weaIcon.value = res.data.icon;
|
||||||
|
temp.value = res.data.temp;
|
||||||
|
} else {
|
||||||
|
$msg.console.error(loc.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => route.name, (newVal) => {
|
||||||
|
key.value = newVal as string
|
||||||
|
})
|
||||||
|
|
||||||
|
function goHome() {
|
||||||
|
if (route.name == 'home') return
|
||||||
|
router.push({ name: 'home' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLogin() {
|
||||||
|
if ($cookies.get('token')) return
|
||||||
|
router.push('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(route.name);
|
||||||
|
userinfo.value = $cookies.get('userinfo');
|
||||||
|
console.log('>>>>>>>>>>', userinfo.value);
|
||||||
|
key.value = route.name as string;
|
||||||
|
getLocation(); // 组件挂载时获取位置
|
||||||
|
|
||||||
|
const h: number = nav.value.clientHeight
|
||||||
|
navx.setNavH(h)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* :deep(svg) {
|
||||||
|
color: red;
|
||||||
|
} */
|
||||||
|
:deep(.devui-menu-horizontal .devui-menu-item:hover span .icon) {
|
||||||
|
color: var(--devui-brand, #5e7ce0) !important;
|
||||||
|
fill: var(--devui-brand, #5e7ce0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-menu-item-select span svg) {
|
||||||
|
color: var(--devui-brand, #5e7ce0) !important;
|
||||||
|
fill: var(--devui-brand, #5e7ce0) !important;
|
||||||
|
line-height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-menu-item-select span) {
|
||||||
|
color: var(--devui-brand, #5e7ce0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-menu-horizontal) {
|
||||||
|
padding: 14px 20px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-menu-item span) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.devui-icon__container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
4
src/config/cookies.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { useCookies } from "vue3-cookies";
|
||||||
|
const { cookies } = useCookies();
|
||||||
|
const ck = cookies;
|
||||||
|
export default ck;
|
12
src/config/http.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const files = import.meta.glob("@/api/*/index.ts", {
|
||||||
|
eager: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const http: Record<string, any> = {};
|
||||||
|
// console.log(files);
|
||||||
|
for (const i in files) {
|
||||||
|
const t = i.split("/");
|
||||||
|
const name = t[t.length - 2];
|
||||||
|
http[name] = files[i];
|
||||||
|
}
|
||||||
|
export default http;
|
7
src/config/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function useConfig() {
|
||||||
|
const files = import.meta.glob("./*.ts", { eager: true }) as Record<string, any>;
|
||||||
|
Object.keys(files).forEach(key => {
|
||||||
|
const name = key.replace(/(\.\/|\.ts)/g, "");
|
||||||
|
(window as Record<string, any>)["$" + name] = files[key].default as any;
|
||||||
|
});
|
||||||
|
}
|
5
src/config/msg.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Message } from 'vue-devui';
|
||||||
|
|
||||||
|
const msg:any = Message;
|
||||||
|
|
||||||
|
export default msg;
|
12
src/config/store.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const files = import.meta.glob("@/stores/*.ts", {
|
||||||
|
eager: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const store: Record<string, any> = {};
|
||||||
|
// console.log("stores封装",files);
|
||||||
|
for (const i in files) {
|
||||||
|
const t = i.split("/");
|
||||||
|
const name = t[t.length - 1].split(".")[0];
|
||||||
|
store[name] = files[i];
|
||||||
|
}
|
||||||
|
export default store;
|
3
src/icon/date.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 754 B |
12
src/icon/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const icons = import.meta.glob("./*.svg", {
|
||||||
|
eager: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon: Record<string, string> = {};
|
||||||
|
// console.log(icons);
|
||||||
|
for (const i in icons) {
|
||||||
|
const t = i.split("/");
|
||||||
|
const name = t[1].split(".")[0];
|
||||||
|
icon[name] = icons[i] as string;
|
||||||
|
}
|
||||||
|
export default icon;
|
5
src/icon/menu/arti.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z" clip-rule="evenodd" />
|
||||||
|
<path d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z" />
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 653 B |
3
src/icon/menu/download.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M10.5 3.75a6 6 0 0 0-5.98 6.496A5.25 5.25 0 0 0 6.75 20.25H18a4.5 4.5 0 0 0 2.206-8.423 3.75 3.75 0 0 0-4.133-4.303A6.001 6.001 0 0 0 10.5 3.75Zm2.25 6a.75.75 0 0 0-1.5 0v4.94l-1.72-1.72a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l3-3a.75.75 0 1 0-1.06-1.06l-1.72 1.72V9.75Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 436 B |
5
src/icon/menu/home.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z" />
|
||||||
|
<path d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z" />
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 539 B |
6
src/icon/menu/link.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path d="M11.7 2.805a.75.75 0 0 1 .6 0A60.65 60.65 0 0 1 22.83 8.72a.75.75 0 0 1-.231 1.337 49.948 49.948 0 0 0-9.902 3.912l-.003.002c-.114.06-.227.119-.34.18a.75.75 0 0 1-.707 0A50.88 50.88 0 0 0 7.5 12.173v-.224c0-.131.067-.248.172-.311a54.615 54.615 0 0 1 4.653-2.52.75.75 0 0 0-.65-1.352 56.123 56.123 0 0 0-4.78 2.589 1.858 1.858 0 0 0-.859 1.228 49.803 49.803 0 0 0-4.634-1.527.75.75 0 0 1-.231-1.337A60.653 60.653 0 0 1 11.7 2.805Z" />
|
||||||
|
<path d="M13.06 15.473a48.45 48.45 0 0 1 7.666-3.282c.134 1.414.22 2.843.255 4.284a.75.75 0 0 1-.46.711 47.87 47.87 0 0 0-8.105 4.342.75.75 0 0 1-.832 0 47.87 47.87 0 0 0-8.104-4.342.75.75 0 0 1-.461-.71c.035-1.442.121-2.87.255-4.286.921.304 1.83.634 2.726.99v1.27a1.5 1.5 0 0 0-.14 2.508c-.09.38-.222.753-.397 1.11.452.213.901.434 1.346.66a6.727 6.727 0 0 0 .551-1.607 1.5 1.5 0 0 0 .14-2.67v-.645a48.549 48.549 0 0 1 3.44 1.667 2.25 2.25 0 0 0 2.12 0Z" />
|
||||||
|
<path d="M4.462 19.462c.42-.419.753-.89 1-1.395.453.214.902.435 1.347.662a6.742 6.742 0 0 1-1.286 1.794.75.75 0 0 1-1.06-1.06Z" />
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |
3
src/icon/menu/pic.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 517 B |
4
src/icon/menu/setting.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M11.828 2.25c-.916 0-1.699.663-1.85 1.567l-.091.549a.798.798 0 0 1-.517.608 7.45 7.45 0 0 0-.478.198.798.798 0 0 1-.796-.064l-.453-.324a1.875 1.875 0 0 0-2.416.2l-.243.243a1.875 1.875 0 0 0-.2 2.416l.324.453a.798.798 0 0 1 .064.796 7.448 7.448 0 0 0-.198.478.798.798 0 0 1-.608.517l-.55.092a1.875 1.875 0 0 0-1.566 1.849v.344c0 .916.663 1.699 1.567 1.85l.549.091c.281.047.508.25.608.517.06.162.127.321.198.478a.798.798 0 0 1-.064.796l-.324.453a1.875 1.875 0 0 0 .2 2.416l.243.243c.648.648 1.67.733 2.416.2l.453-.324a.798.798 0 0 1 .796-.064c.157.071.316.137.478.198.267.1.47.327.517.608l.092.55c.15.903.932 1.566 1.849 1.566h.344c.916 0 1.699-.663 1.85-1.567l.091-.549a.798.798 0 0 1 .517-.608 7.52 7.52 0 0 0 .478-.198.798.798 0 0 1 .796.064l.453.324a1.875 1.875 0 0 0 2.416-.2l.243-.243c.648-.648.733-1.67.2-2.416l-.324-.453a.798.798 0 0 1-.064-.796c.071-.157.137-.316.198-.478.1-.267.327-.47.608-.517l.55-.091a1.875 1.875 0 0 0 1.566-1.85v-.344c0-.916-.663-1.699-1.567-1.85l-.549-.091a.798.798 0 0 1-.608-.517 7.507 7.507 0 0 0-.198-.478.798.798 0 0 1 .064-.796l.324-.453a1.875 1.875 0 0 0-.2-2.416l-.243-.243a1.875 1.875 0 0 0-2.416-.2l-.453.324a.798.798 0 0 1-.796.064 7.462 7.462 0 0 0-.478-.198.798.798 0 0 1-.517-.608l-.091-.55a1.875 1.875 0 0 0-1.85-1.566h-.344ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 1.5 KiB |
3
src/icon/news.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 0 1-2.25 2.25M16.5 7.5V18a2.25 2.25 0 0 0 2.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 0 0 2.25 2.25h13.5M6 7.5h3v3H6v-3Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 477 B |
3
src/icon/time.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 247 B |
BIN
src/logo/柚子娘-绿.png
Normal file
After Width: | Height: | Size: 606 KiB |
BIN
src/logo/柚子娘.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
src/logo/红色字体.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
src/logo/绿色字体.png
Normal file
After Width: | Height: | Size: 31 KiB |
28
src/main.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import "@/assets/main.css";
|
||||||
|
import { useConfig } from "@/config";
|
||||||
|
import icon from "@/icon/index.ts";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import "virtual:uno.css";
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
// 自定义主题配置 - 设置主色和二级色
|
||||||
|
import { ThemeServiceInit, infinityTheme, sweetTheme } from "devui-theme";
|
||||||
|
|
||||||
|
import { PerfectScrollbarPlugin } from "vue3-perfect-scrollbar";
|
||||||
|
|
||||||
|
// ThemeServiceInit({ customTheme }, "customTheme");
|
||||||
|
const themeService = ThemeServiceInit({ infinityTheme }, "infinityTheme");
|
||||||
|
themeService?.applyTheme(sweetTheme);
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
useConfig();
|
||||||
|
for (const key in icon) {
|
||||||
|
// console.log(key, icon[key]);
|
||||||
|
app.component("icon-" + key, icon[key] as any);
|
||||||
|
}
|
||||||
|
app.use(PerfectScrollbarPlugin);
|
||||||
|
app.mount("#app");
|
100
src/router/index.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
|
import layout from "@/Layout.vue";
|
||||||
|
import Home from '@/views/Home.vue';
|
||||||
|
|
||||||
|
// 导入视图组件 - 修改为@开头的懒加载路径
|
||||||
|
|
||||||
|
const Gallery = () => import("@/views/Gallery.vue");
|
||||||
|
const Article = () => import("@/views/Article.vue");
|
||||||
|
const Widget = () => import("@/views/Widget.vue");
|
||||||
|
const AppShare = () => import("@/views/AppShare.vue");
|
||||||
|
const Plink = () => import("@/views/Plink.vue");
|
||||||
|
const Login = () => import("@/views/login/Login.vue");
|
||||||
|
const NotFound = () => import("@/views/NotFound.vue");
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
|
||||||
|
meta: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: layout,
|
||||||
|
redirect: {name:'home'},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
name: "home",
|
||||||
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
title:'首页'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/gallery",
|
||||||
|
name: "gallery",
|
||||||
|
component: Gallery,
|
||||||
|
meta: {
|
||||||
|
title:'画廊'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/article",
|
||||||
|
name: "article",
|
||||||
|
component: Article,
|
||||||
|
meta: {
|
||||||
|
title:'文章'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/widget",
|
||||||
|
name: "widget",
|
||||||
|
component: Widget,
|
||||||
|
meta: {
|
||||||
|
title:'工具'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/appshare",
|
||||||
|
name: "appShare",
|
||||||
|
component: AppShare,
|
||||||
|
meta: {
|
||||||
|
title:'软件分享'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/plink",
|
||||||
|
name: "plink",
|
||||||
|
component: Plink,
|
||||||
|
meta: {
|
||||||
|
title:'友链'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: Login,
|
||||||
|
meta: {
|
||||||
|
title:'登录'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/404",
|
||||||
|
name: "404",
|
||||||
|
component: NotFound,
|
||||||
|
meta: {
|
||||||
|
title:'错误页'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
10
src/stores/nav.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const useNavStore = defineStore('nav', () => {
|
||||||
|
const navH:Ref<number,number> = ref(0)
|
||||||
|
|
||||||
|
function setNavH(v:number) {
|
||||||
|
navH.value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { navH , setNavH}
|
||||||
|
})
|
161
src/util/index.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 将res的key对应的值复制给dzdata的同名key
|
||||||
|
/**
|
||||||
|
* 将源对象res的属性复制到目标对象dzdata中。
|
||||||
|
* @param {Object} res - 源对象,其属性将被复制。
|
||||||
|
* @param {Object} dzdata - 目标对象,将接收源对象的属性。
|
||||||
|
* @example
|
||||||
|
* ObjectCopy({a: 1, b: 2}, {c: 3}); // dzdata 变为 {a: 1, b: 2, c: 3}
|
||||||
|
*/
|
||||||
|
export function ObjectCopy<T extends Record<string, any>>(res: Partial<T>, dzdata: T): void {
|
||||||
|
Object.keys(dzdata).forEach(key => {
|
||||||
|
if (res.hasOwnProperty(key)) {
|
||||||
|
(dzdata as Record<string, any>)[key] = res[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 时间格式化
|
||||||
|
* @param {*} time 传入时间参数,支持字符串和时间戳
|
||||||
|
* @param {*} f 对应格式 "YYYY-MM-DD hh:mm:ss" "YYYY年MM月DD日hh时mm分ss秒"
|
||||||
|
* 格式可以自定义,对应的字符串对应的时间会更新,输入单个字符表示可以不补零
|
||||||
|
* @return {*} 返回格式化的日期
|
||||||
|
*/
|
||||||
|
export function formatTime(time: string | number | Date, f: string): string | undefined {
|
||||||
|
const date = new Date(time);
|
||||||
|
if (!(date instanceof Date && !isNaN(date.getTime()))) {
|
||||||
|
console.error("不合法的日期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
const hour = date.getHours();
|
||||||
|
const minute = date.getMinutes();
|
||||||
|
const second = date.getSeconds();
|
||||||
|
|
||||||
|
const pad = (num: number): string => num.toString().padStart(2, "0");
|
||||||
|
const monthAdd0 = pad(month);
|
||||||
|
const dayAdd0 = pad(day);
|
||||||
|
const hourAdd0 = pad(hour);
|
||||||
|
const minuteAdd0 = pad(minute);
|
||||||
|
const secondAdd0 = pad(second);
|
||||||
|
|
||||||
|
let str = f.toString();
|
||||||
|
str = str.replace("YYYY", year.toString());
|
||||||
|
|
||||||
|
if (f.includes("M")) {
|
||||||
|
str = f.includes("MM") ? str.replace("MM", monthAdd0) : str.replace("M", month.toString());
|
||||||
|
}
|
||||||
|
if (f.includes("D")) {
|
||||||
|
str = f.includes("DD") ? str.replace("DD", dayAdd0) : str.replace("D", day.toString());
|
||||||
|
}
|
||||||
|
if (f.includes("h")) {
|
||||||
|
str = f.includes("hh") ? str.replace("hh", hourAdd0) : str.replace("h", hour.toString());
|
||||||
|
}
|
||||||
|
if (f.includes("m")) {
|
||||||
|
str = f.includes("mm") ? str.replace("mm", minuteAdd0) : str.replace("m", minute.toString());
|
||||||
|
}
|
||||||
|
if (f.includes("s")) {
|
||||||
|
str = f.includes("ss") ? str.replace("ss", secondAdd0) : str.replace("s", second.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 补零函数 为时间服务 不足两位的自动在数字前面加上0
|
||||||
|
* @param {*} n 待补零数字
|
||||||
|
* @return {*} 补零后数字
|
||||||
|
*/
|
||||||
|
function timeAdd0(n: number): string {
|
||||||
|
return n.toString().padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepclone<T>(obj: T): T {
|
||||||
|
if (obj === null || typeof obj !== "object") {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newobj: any = obj instanceof Array ? [] : {};
|
||||||
|
|
||||||
|
if (window.JSON) {
|
||||||
|
newobj = JSON.parse(JSON.stringify(obj));
|
||||||
|
} else {
|
||||||
|
for (const i in obj) {
|
||||||
|
newobj[i] = typeof obj[i] === "object" ? deepclone(obj[i]) : obj[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newobj as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据日期格式化时间。
|
||||||
|
* @param {Date} date - 需要格式化的日期对象。
|
||||||
|
* @param {string} format - 时间格式字符串。
|
||||||
|
* @returns {string} 格式化后的时间字符串。
|
||||||
|
* @example
|
||||||
|
* formatTimeBydate(new Date(), 'yyyy-MM-dd HH:mm:ss');
|
||||||
|
*/
|
||||||
|
export function formatTimeBydate(this: Date, f: string): string | undefined {
|
||||||
|
return formatTime(this, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
||||||
|
const _this = this;
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
fn.apply(_this, args);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function throttle<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
||||||
|
const _this = this;
|
||||||
|
if (!timer) {
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
fn.apply(_this, args);
|
||||||
|
timer = null;
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典查询 通个一个字段返回另一个字段的值
|
||||||
|
/**
|
||||||
|
* @description 字典查询
|
||||||
|
* @param {*} dict 字典数组
|
||||||
|
* @param {*} ckey 查询字段
|
||||||
|
* @param {*} cvalue 查询值
|
||||||
|
* @param {*} rkey 返回字段
|
||||||
|
* @return {*} 返回值
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getDictValue<T extends Record<string, any>>(dict: T[], ckey: keyof T, cvalue: T[keyof T], rkey: keyof T): T[keyof T] | string {
|
||||||
|
let result = "";
|
||||||
|
dict.forEach(item => {
|
||||||
|
if (item[ckey] === cvalue) {
|
||||||
|
result = item[rkey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割数组
|
||||||
|
export function chunkArrayInGroups<T>(arr: T[], size: number): T[][] {
|
||||||
|
return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size));
|
||||||
|
}
|
||||||
|
// 重置对象
|
||||||
|
export function resetObject<T extends Record<string, any>>(obj: T): void {
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
(obj as Record<string, any>)[key] = null;
|
||||||
|
});
|
||||||
|
}
|
58
src/util/request.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from "axios";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useCookies } from "vue3-cookies";
|
||||||
|
|
||||||
|
const { cookies } = useCookies();
|
||||||
|
|
||||||
|
// const baseURL: string = "http://127.0.0.1:7777" + "/api";
|
||||||
|
const baseURL: string = "https://www.hxyouzi.com" + "/api";
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加请求拦截器
|
||||||
|
request.interceptors.request.use(
|
||||||
|
(config: InternalAxiosRequestConfig) => {
|
||||||
|
const token = cookies.get("token");
|
||||||
|
if (token && typeof token === "string" && token.trim() !== "") {
|
||||||
|
config.headers["Authorization"] = "Bearer " + token;
|
||||||
|
} else delete config.headers["Authorization"];
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
function (error: AxiosError): string {
|
||||||
|
// 对请求错误做些什么
|
||||||
|
return error.message || "Request error";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
function (response: AxiosResponse): any {
|
||||||
|
// 2xx 范围内的状态码都会触发该函数。
|
||||||
|
// 对响应数据做点什么
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
async function (error: AxiosError): Promise<string> {
|
||||||
|
// 超出 2xx 范围的状态码都会触发该函数。
|
||||||
|
// 对响应错误做点什么
|
||||||
|
console.log("Response error", error);
|
||||||
|
|
||||||
|
// if (error.response?.status === 401) {
|
||||||
|
// window.$msg.warning("无效的token");
|
||||||
|
// cookies.remove("token");
|
||||||
|
// cookies.remove("userinfo");
|
||||||
|
// router.replace("/login");
|
||||||
|
// return "Unauthorized";
|
||||||
|
// }
|
||||||
|
|
||||||
|
return error.message || "Response error";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default request;
|
14
src/views/AppShare.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="appshare-page">
|
||||||
|
<h1>软件分享页</h1>
|
||||||
|
<!-- 软件分享内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 软件分享页逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 软件分享页样式 */
|
||||||
|
</style>
|
14
src/views/Article.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="article-page">
|
||||||
|
<h1>文章页</h1>
|
||||||
|
<!-- 文章内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 文章页逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 文章页样式 */
|
||||||
|
</style>
|
14
src/views/Gallery.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gallery-page">
|
||||||
|
<h1>画廊页</h1>
|
||||||
|
<!-- 画廊内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 画廊页逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 画廊页样式 */
|
||||||
|
</style>
|
240
src/views/Home.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home-page" :style="contentStyle">
|
||||||
|
<d-layout>
|
||||||
|
<d-content class="main-content">
|
||||||
|
<div class="pt-8 px-12">
|
||||||
|
<d-input class="devui-input-demo__mt" size="lg" v-model="searchWord" @keyup.enter="search" placeholder="请输入">
|
||||||
|
<template #prepend>
|
||||||
|
<d-select class="w-48" size="lg" v-model="broswer" :options="options"></d-select>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<d-icon name="search" style="font-size: inherit;" @click="search" />
|
||||||
|
</template>
|
||||||
|
</d-input>
|
||||||
|
</div>
|
||||||
|
<!-- 图片网格展示区域 -->
|
||||||
|
<PerfectScrollbar class="" :style="navStyle">
|
||||||
|
<div class="navcard grid-cols-4 gap-6 p-12">
|
||||||
|
<d-card class="bg-[white] h-25" v-for="(item, index) in navlist" :key="index"
|
||||||
|
@click="goExtra(item.menu_link)">
|
||||||
|
<template #content>
|
||||||
|
<div class="mt-2 w-full flex flex-col items-center cursor-pointer">
|
||||||
|
<div :style="{ background: item.color }"
|
||||||
|
class="w-8 h-8 rounded-full text-white flex items-center justify-center" v-if="item.icon_error">
|
||||||
|
{{ item.first }}</div>
|
||||||
|
<img v-else width="32" :src="item.menu_icon" @error="imgErr(index)" class="grid-image" />
|
||||||
|
<div class="mt-1 w-full text-center">{{ item.menu_name || "" }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</d-card>
|
||||||
|
<d-card class="bg-[white] h-25">
|
||||||
|
<div @click="addNav" class="w-full h-full flex flex-col items-center justify-center cursor-pointer">
|
||||||
|
<div :style="{ background: getRandomDarkColor() }"
|
||||||
|
class="w-12 h-12 rounded-full text-2xl text-white flex items-center justify-center">
|
||||||
|
+
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</d-card>
|
||||||
|
</div>
|
||||||
|
</PerfectScrollbar>
|
||||||
|
|
||||||
|
</d-content>
|
||||||
|
<d-aside class="daside w-120">
|
||||||
|
<homeSide></homeSide>
|
||||||
|
</d-aside>
|
||||||
|
</d-layout>
|
||||||
|
|
||||||
|
<!-- 新增导航弹窗 -->
|
||||||
|
<d-modal class="!w-120" v-model="visible" title="新增导航">
|
||||||
|
<d-form ref="formNav" layout="vertical" :data="navData">
|
||||||
|
<d-form-item field="username">
|
||||||
|
<d-input @blur="getIcon" v-model="navData.menu_link" placeholder="请输入单行链接(必填)" />
|
||||||
|
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item field="password">
|
||||||
|
<d-input v-model="navData.menu_name" placeholder="请输入导航名称(必填)" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item class="form-operation-wrap">
|
||||||
|
<div class="flex">
|
||||||
|
<d-input v-model="navData.menu_icon" placeholder="请输入图标链接" />
|
||||||
|
<img class="ml-5" v-if="navData.menu_icon" width="30" height="30" :src="navData.menu_icon" alt="">
|
||||||
|
<div v-else class="ml-5 w-[30px] h-[30px]"></div>
|
||||||
|
</div>
|
||||||
|
</d-form-item>
|
||||||
|
</d-form>
|
||||||
|
<div class="mt-10 w-full flex justify-between">
|
||||||
|
<d-button @click="navCancel" variant="text" class="w-[49%] hover:bg-[#8a6684] hover:!text-white">取消</d-button>
|
||||||
|
<span class="text-[20px]"> | </span>
|
||||||
|
<d-button @click="navSubmit" variant="text" class="w-[49%] hover:bg-[#5c866a] hover:!text-white"
|
||||||
|
color="primary">确定</d-button>
|
||||||
|
</div>
|
||||||
|
</d-modal>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import homeSide from '@/components/homeSide.vue'
|
||||||
|
import { getDictValue } from '@/util/index.ts'
|
||||||
|
// 新增导航弹窗
|
||||||
|
const visible: any = ref(false)
|
||||||
|
const navData: any = reactive({
|
||||||
|
menu_link: '',
|
||||||
|
menu_name: '',
|
||||||
|
menu_icon: ''
|
||||||
|
})
|
||||||
|
const formNav: any = ref(null)
|
||||||
|
|
||||||
|
|
||||||
|
// 首页逻辑
|
||||||
|
const nav: any = $store.nav.useNavStore()
|
||||||
|
const contentStyle: any = ref({})
|
||||||
|
const navStyle: any = ref({})
|
||||||
|
const searchWord: any = ref('')
|
||||||
|
const broswer: any = ref('bing')
|
||||||
|
const options = ref([
|
||||||
|
{
|
||||||
|
name: '必应',
|
||||||
|
value: 'bing',
|
||||||
|
url: 'https://cn.bing.com/search?q='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '百度',
|
||||||
|
value: 'baidu',
|
||||||
|
url: 'https://www.baidu.com/s?wd='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '谷歌',
|
||||||
|
value: 'google',
|
||||||
|
url: 'https://www.google.com/search?q='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '翻译',
|
||||||
|
value: 'trans',
|
||||||
|
url: 'https://translate.volcengine.com?text='
|
||||||
|
},
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
// 图片数据
|
||||||
|
const navlist: any = ref([])
|
||||||
|
|
||||||
|
async function getNavList() {
|
||||||
|
const res = await $http.nav.getNavList()
|
||||||
|
|
||||||
|
res.data.forEach((i: any) => {
|
||||||
|
i.icon_error = false
|
||||||
|
i.first = i.menu_name.at(0)
|
||||||
|
i.color = getRandomDarkColor()
|
||||||
|
});
|
||||||
|
navlist.value = res.data
|
||||||
|
console.log("&&&&&&&&&&&&&&", navlist.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function imgErr(index: number) {
|
||||||
|
navlist.value[index].icon_error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function goExtra(link: string) {
|
||||||
|
window.open(link, "_BLANK")
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
const res = getDictValue(options.value, "value", broswer.value, "url")
|
||||||
|
if (res) {
|
||||||
|
window.open(res + searchWord.value, "_BLANK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRandomDarkColor = (min: number = 30, max: number = 128): string => {
|
||||||
|
// 确保最大值不超过255且最小值不小于0
|
||||||
|
min = Math.max(0, min);
|
||||||
|
max = Math.min(255, max);
|
||||||
|
|
||||||
|
// 确保最大值大于最小值
|
||||||
|
if (max <= min) {
|
||||||
|
max = min + 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机RGB值 (深色)
|
||||||
|
const r = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
const g = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
const b = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
// 转换为十六进制
|
||||||
|
const toHex = (c: number) => {
|
||||||
|
const hex = c.toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNav() {
|
||||||
|
if (!$cookies.get("token")) {
|
||||||
|
$msg.error("请先登录");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIcon() {
|
||||||
|
if (!navData.menu_link) return
|
||||||
|
const res = await $http.mix.getIcon({
|
||||||
|
url: navData.menu_link
|
||||||
|
})
|
||||||
|
console.log('>>> --> getIcon --> res:', res)
|
||||||
|
if (res.code == 200) {
|
||||||
|
navData.menu_icon = res.data.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navCancel() {
|
||||||
|
visible.value = false
|
||||||
|
formNav.value.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navSubmit() {
|
||||||
|
console.log('>>> --> navSubmit --> navData:', navData)
|
||||||
|
if (!navData.menu_link || !navData.menu_name) {
|
||||||
|
$msg.error('请输入链接和名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await $http.nav.addNav(navData)
|
||||||
|
console.log('>>> --> navSubmit --> res:', res)
|
||||||
|
|
||||||
|
if (res.code == 200) {
|
||||||
|
$msg.success('添加成功')
|
||||||
|
visible.value = false
|
||||||
|
formNav.value.resetFields()
|
||||||
|
getNavList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// console.log("&&&&&&&&&&&&&&", nav.navH);
|
||||||
|
contentStyle.value = {
|
||||||
|
height: `calc(100vh - ${nav.navH}px)`
|
||||||
|
}
|
||||||
|
navStyle.value = {
|
||||||
|
height: `calc(100vh - ${nav.navH}px - 110px)`
|
||||||
|
}
|
||||||
|
getNavList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
/* 首页样式 */
|
||||||
|
.navcard {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-input-slot__prepend) {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
</style>
|
11
src/views/NotFound.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
404
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 404页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
14
src/views/Plink.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="plink-page">
|
||||||
|
<h1>友链页</h1>
|
||||||
|
<!-- 友链内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 友链页逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 友链页样式 */
|
||||||
|
</style>
|
14
src/views/Widget.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="widget-page">
|
||||||
|
<h1>工具页</h1>
|
||||||
|
<!-- 工具内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 工具页逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 工具页样式 */
|
||||||
|
</style>
|
145
src/views/login/Login.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-page w-[100vw] h-[100vh] relative">
|
||||||
|
<d-card class="w-120 !absolute top-[12%] right-8 bg-[#ffffff60] rounded-[10px]">
|
||||||
|
<d-tabs v-model="tid" type="pills">
|
||||||
|
<d-tab id="tab1" title="登录">
|
||||||
|
<d-form ref="formLogin" layout="vertical" :data="loginData" :rules="rules">
|
||||||
|
<d-form-item field="username" >
|
||||||
|
<d-input v-model="loginData.username" placeholder="请输入用户名" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item field="password" >
|
||||||
|
<d-input v-model="loginData.password" show-password placeholder="请输入密码" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item class="form-operation-wrap">
|
||||||
|
<d-button class="w-full" variant="solid" @click="login">登 录</d-button>
|
||||||
|
</d-form-item>
|
||||||
|
</d-form>
|
||||||
|
</d-tab>
|
||||||
|
<d-tab id="tab2" title="注册">
|
||||||
|
<d-form ref="formReg" layout="vertical" :data="regData" :rules="rrules">
|
||||||
|
<d-form-item field="username" >
|
||||||
|
<d-input v-model="regData.username" placeholder="请输入用户名" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item field="password" >
|
||||||
|
<d-input v-model="regData.password" show-password placeholder="请输入用密码" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item field="nickname" >
|
||||||
|
<d-input v-model="regData.nickname" placeholder="请输入昵称" />
|
||||||
|
</d-form-item>
|
||||||
|
<d-form-item class="form-operation-wrap">
|
||||||
|
<d-button class="w-full" variant="solid" @click="register">注册</d-button>
|
||||||
|
</d-form-item>
|
||||||
|
</d-form>
|
||||||
|
</d-tab>
|
||||||
|
</d-tabs>
|
||||||
|
</d-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// 登录注册逻辑
|
||||||
|
const tid = ref("tab1");
|
||||||
|
const formLogin: any = ref(null);
|
||||||
|
const loginData = reactive({
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const formReg: any = ref(null);
|
||||||
|
const regData = reactive({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
nickname: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const rules: any = reactive({
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ validator: validateUsername, trigger: 'blur' },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ validator: validatePassword, trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const rrules: any = reactive({
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ validator: validateUsername, trigger: 'blur' },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ validator: validatePassword, trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
function login() {
|
||||||
|
formLogin.value.validate(async (is: boolean,b: any) => {
|
||||||
|
if (!is) {
|
||||||
|
$msg.error('信息填写不正确,请检查后再提交')
|
||||||
|
} else {
|
||||||
|
const res = await $http.user.login(loginData)
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
$msg.error('登录失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$cookies.set('token',res.data.token,'7d')
|
||||||
|
$cookies.set('userinfo',res.data.userinfo,'7d')
|
||||||
|
$msg.success(res.msg)
|
||||||
|
router.push({ path: "/" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function register() {
|
||||||
|
formReg.value.validate(async (is: boolean,b: any) => {
|
||||||
|
if (!is) {
|
||||||
|
$msg.error('信息填写不正确,请检查后再提交')
|
||||||
|
} else {
|
||||||
|
const res = await $http.user.register(regData)
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
$msg.error('注册失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$msg.success(res.msg)
|
||||||
|
tid.value = 'tab1'
|
||||||
|
formReg.value.resetForm()
|
||||||
|
loginData.username = regData.username
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUsername(rule: any, value: string, callback: Function) {
|
||||||
|
if (/^[a-zA-Z][a-zA-Z0-9]{3,15}$/.test(value)) return callback()
|
||||||
|
else return callback(new Error('请输入4-16位字母或数字,且以字母开头'))
|
||||||
|
}
|
||||||
|
function validatePassword(rule: any, value: string, callback: Function) {
|
||||||
|
if (/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,12}$/.test(value)) return callback()
|
||||||
|
else return callback(new Error('密码长度为6-12位,且必须包含数字和字母'))
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
/* 登录页面样式 */
|
||||||
|
.login-page {
|
||||||
|
background: transparent url("@/assets/images/05.jpeg") 0 0/cover no-repeat;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-tabs__nav) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
li a span {
|
||||||
|
// width: 20%;
|
||||||
|
font-size: 18px !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.devui-form__item--horizontal) {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
12
tsconfig.app.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue","auto-imports.d.ts", "components.d.ts"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
10
uno.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "unocss";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ...UnoCSS options
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
primary: "#ec66ab",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
40
vite.config.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
import UnoCSS from "unocss/vite";
|
||||||
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
|
import svgLoader from "vite-svg-loader";
|
||||||
|
import Components from "unplugin-vue-components/vite";
|
||||||
|
import { DevUiResolver } from "unplugin-vue-components/resolvers";
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
UnoCSS(),
|
||||||
|
svgLoader(),
|
||||||
|
AutoImport({
|
||||||
|
include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||||
|
imports: ["vue", "pinia", "vue-router"],
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [DevUiResolver()],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
esbuild: {
|
||||||
|
pure: ["console.log"], // 删除 console.log
|
||||||
|
drop: ["debugger"], // 删除 debugger
|
||||||
|
},
|
||||||
|
|
||||||
|
base: "/blog/",
|
||||||
|
server: {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: 8080,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|