This commit is contained in:
cxc
2023-02-13 16:47:43 +08:00
commit 221c466bdb
37 changed files with 4786 additions and 0 deletions

15
.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# 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?

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

3
.vscode/extensions.json vendored Normal file
View File

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

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# gen_short_link
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

1
env.d.ts vendored Normal file
View File

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

13
index.html Normal file
View File

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

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "gen_short_link",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"axios": "^1.3.2",
"element-plus": "^2.2.30",
"modern-normalize": "^1.1.0",
"qrcode.vue": "^3.3.3",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.13",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"prettier": "^2.7.1",
"sass": "^1.58.0",
"tailwindcss": "^3.2.6",
"typescript": "~4.7.4",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}
}

2822
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

75
src/App.vue Normal file
View File

@ -0,0 +1,75 @@
<script setup lang="ts">
// export default defineComponent({
// components: { Gen_short_link }
//
// });
</script>
<template>
<!-- <GenShortLink class="w-full h-full"></GenShortLink>-->
<RouterView />
</template>
<style scoped>
/*header {*/
/* line-height: 1.5;*/
/* max-height: 100vh;*/
/*}*/
/*.logo {*/
/* display: block;*/
/* margin: 0 auto 2rem;*/
/*}*/
/*nav {*/
/* width: 100%;*/
/* font-size: 12px;*/
/* text-align: center;*/
/* margin-top: 2rem;*/
/*}*/
/*nav a.router-link-exact-active {*/
/* color: var(--color-text);*/
/*}*/
/*nav a.router-link-exact-active:hover {*/
/* background-color: transparent;*/
/*}*/
/*nav a {*/
/* display: inline-block;*/
/* padding: 0 1rem;*/
/* border-left: 1px solid var(--color-border);*/
/*}*/
/*nav a:first-of-type {*/
/* border: 0;*/
/*}*/
/*@media (min-width: 1024px) {*/
/* header {*/
/* display: flex;*/
/* place-items: center;*/
/* padding-right: calc(var(--section-gap) / 2);*/
/* }*/
/* .logo {*/
/* margin: 0 2rem 0 0;*/
/* }*/
/* header .wrapper {*/
/* display: flex;*/
/* place-items: flex-start;*/
/* flex-wrap: wrap;*/
/* }*/
/* nav {*/
/* text-align: left;*/
/* margin-left: -1rem;*/
/* font-size: 1rem;*/
/* padding: 1rem 0;*/
/* margin-top: 1rem;*/
/* }*/
/*}*/
</style>

38
src/api/shortlink.ts Normal file
View File

@ -0,0 +1,38 @@
import request from "@/utils/request";
import type { ShortLink } from "@/models/shortLink";
import type { LogListParams } from "@/models/LogListParams";
export const listShortLink = (params: { pageNum: number; pageSize: number }) =>
request({
method: "get",
url: "/shortLink/list",
params
});
export const addShortLink = (data: ShortLink) =>
request({
method: "post",
url: "/shortLink",
data
});
export const updateShortLink = (data: ShortLink) =>
request({
method: "put",
url: "/shortLink",
data
});
export const getDetailById = (id: number) =>
request({ method: "get", url: `/shortLink/getInfo/${id}` });
export const deleteShortLink = (ids: string) =>
request({
url: `/shortLink/${ids}`,
method: "delete"
});
export const listLogList = (params: LogListParams) =>
request({
url: `/shortLink/getShortLinkLogList`,
method: "get",
params
});

75
src/assets/base.css Normal file
View File

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

View File

@ -0,0 +1,540 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834");
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix")
format("embedded-opentype"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834")
format("woff"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834")
format("truetype"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont")
format("svg");
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown > p,
.markdown > blockquote,
.markdown > .highlight,
.markdown > ol,
.markdown > ul {
width: 80%;
}
.markdown ul > li {
list-style: circle;
}
.markdown > ul li,
.markdown blockquote ul > li {
margin-left: 20px;
padding-left: 4px;
}
.markdown > ul li p,
.markdown > ol li p {
margin: 0.6em 0;
}
.markdown ol > li {
list-style: decimal;
}
.markdown > ol li,
.markdown blockquote ol > li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown > table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown > table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown > table th,
.markdown > table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown > table th {
background: #f7f7f7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown > br,
.markdown > p > br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #dd4a68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -0,0 +1,253 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>iconfont Demo</title>
<link
rel="shortcut icon"
href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"
type="image/x-icon"
/>
<link
rel="icon"
type="image/svg+xml"
href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"
/>
<link
rel="stylesheet"
href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css"
/>
<link rel="stylesheet" href="demo.css" />
<link rel="stylesheet" href="iconfont.css" />
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967ff, #b500fe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo">
<a
href="https://www.iconfont.cn/"
title="iconfont 首页"
target="_blank"
>
<img
width="200"
src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg"
/>
</a>
</h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a
href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=3892861"
target="_blank"
class="nav-more"
>查看项目</a
>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe71c;</span>
<div class="name">qrcode</div>
<div class="code-name">&amp;#xe71c;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr />
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>
注意:新版 iconfont 支持两种方式引用多色图标SVG symbol
引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)
</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">
第一步:拷贝项目下面生成的 <code>@font-face</code>
</h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1676268957528') format('woff2'),
url('iconfont.woff?t=1676268957528') format('woff'),
url('iconfont.ttf?t=1676268957528') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>
"iconfont" 是你项目下的
font-family。可以通过编辑项目查看默认是 "iconfont"。
</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-qrcode"></span>
<div class="name">qrcode</div>
<div class="code-name">.icon-qrcode</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr />
<p>
font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode
书写不直观,语意不明确的问题。
</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>
相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon
是什么。
</li>
<li>
因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class
里面的 Unicode 引用。
</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">
第一步:引入项目下面生成的 fontclass 代码:
</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>
" iconfont" 是你项目下的
font-family。可以通过编辑项目查看默认是 "iconfont"。
</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qrcode"></use>
</svg>
<div class="name">qrcode</div>
<div class="code-name">#icon-qrcode</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr />
<p>
这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a
href=""
>文章</a
>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:
</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>
通过一些技巧,支持像字体那样,通过 <code>font-size</code>,
<code>color</code> 来调整样式。
</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$(".tab-container .content:first").show();
$("#tabs li").click(function (e) {
var tabContent = $(".tab-container .content");
var index = $(this).index();
if ($(this).hasClass("active")) {
return;
} else {
$("#tabs li").removeClass("active");
$(this).addClass("active");
tabContent.hide().eq(index).fadeIn();
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
@font-face {
font-family: "iconfont"; /* Project id 3892861 */
src: url("iconfont.woff2?t=1676268957528") format("woff2"),
url("iconfont.woff?t=1676268957528") format("woff"),
url("iconfont.ttf?t=1676268957528") format("truetype");
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-qrcode:before {
content: "\e71c";
}

View File

@ -0,0 +1,69 @@
(window._iconfont_svg_string_3892861 =
'<svg><symbol id="icon-qrcode" viewBox="0 0 1024 1024"><path d="M224 416.096V224l192-0.096 0.096 192.096L224 416.096zM416.096 160H223.904A64 64 0 0 0 160 223.904v192.192A64 64 0 0 0 223.904 480h192.192A64 64 0 0 0 480 416.096V223.904A64 64 0 0 0 416.096 160zM224 800.096V608l192-0.096 0.096 192.096L224 800.096zM416.096 544H223.904A64 64 0 0 0 160 607.904v192.192A64 64 0 0 0 223.904 864h192.192A64 64 0 0 0 480 800.096v-192.192A64 64 0 0 0 416.096 544zM608 416.096V224l192-0.096 0.096 192.096-192.096 0.096zM800.096 160h-192.192A64 64 0 0 0 544 223.904v192.192A64 64 0 0 0 607.904 480h192.192A64 64 0 0 0 864 416.096V223.904A64 64 0 0 0 800.096 160zM704 608a32 32 0 0 0-32 32v192a32 32 0 0 0 64 0v-192a32 32 0 0 0-32-32M576 608a32 32 0 0 0-32 32v192a32 32 0 0 0 64 0v-192a32 32 0 0 0-32-32M832 544a32 32 0 0 0-32 32v256a32 32 0 0 0 64 0v-256a32 32 0 0 0-32-32" fill="#3E3A39" ></path></symbol></svg>'),
(function (n) {
var t = (t = document.getElementsByTagName("script"))[t.length - 1],
e = t.getAttribute("data-injectcss"),
t = t.getAttribute("data-disable-injectsvg");
if (!t) {
var o,
i,
a,
d,
c,
s = function (t, e) {
e.parentNode.insertBefore(t, e);
};
if (e && !n.__iconfont__svg__cssinject__) {
n.__iconfont__svg__cssinject__ = !0;
try {
document.write(
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
);
} catch (t) {
console && console.log(t);
}
}
(o = function () {
var t,
e = document.createElement("div");
(e.innerHTML = n._iconfont_svg_string_3892861),
(e = e.getElementsByTagName("svg")[0]) &&
(e.setAttribute("aria-hidden", "true"),
(e.style.position = "absolute"),
(e.style.width = 0),
(e.style.height = 0),
(e.style.overflow = "hidden"),
(e = e),
(t = document.body).firstChild
? s(e, t.firstChild)
: t.appendChild(e));
}),
document.addEventListener
? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
? setTimeout(o, 0)
: ((i = function () {
document.removeEventListener("DOMContentLoaded", i, !1), o();
}),
document.addEventListener("DOMContentLoaded", i, !1))
: document.attachEvent &&
((a = o),
(d = n.document),
(c = !1),
r(),
(d.onreadystatechange = function () {
"complete" == d.readyState &&
((d.onreadystatechange = null), l());
}));
}
function l() {
c || ((c = !0), a());
}
function r() {
try {
d.documentElement.doScroll("left");
} catch (t) {
return void setTimeout(r, 50);
}
l();
}
})(window);

View File

@ -0,0 +1,16 @@
{
"id": "3892861",
"name": "shortlink",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "7008991",
"name": "qrcode",
"font_class": "qrcode",
"unicode": "e71c",
"unicode_decimal": 59164
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
src/assets/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 308 B

39
src/assets/main.css Normal file
View File

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

24
src/main.ts Normal file
View File

@ -0,0 +1,24 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "./assets/main.css";
import "modern-normalize/modern-normalize.css";
// import "@/assets/iconfont/iconfont.js";
import "@/assets/iconfont/iconfont.css";
// @ts-ignore
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const app = createApp(App);
app.use(router);
app.use(ElementPlus, {
locale: zhCn,
});
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.mount("#app");

View File

@ -0,0 +1,7 @@
export interface LogListParams {
urlSuffix: string;
pageNum: number;
pageSize: number;
startTime?: string;
endTime?: string;
}

8
src/models/shortLink.ts Normal file
View File

@ -0,0 +1,8 @@
export interface ShortLink {
id?: number;
jumpUrl?: string;
name?: string;
urlPrefix?: string;
urlSuffix?: string;
validityTime?: string;
}

View File

@ -0,0 +1,5 @@
export interface ShortLinkListResp {
rows: ShortLinkListResp[];
total: number;
msg: string;
}

21
src/router/index.ts Normal file
View File

@ -0,0 +1,21 @@
import { createRouter, createWebHistory } from "vue-router";
import GenShortLink from "@/views/GenShortLink.vue";
import AccessRecords from "@/views/AccessRecords.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "ShortLinkList",
component: GenShortLink,
},
{
path: "/log-list/:suffix",
name: "AccessRecords",
component: AccessRecords,
},
],
});
export default router;

6
src/utils/errorCode.ts Normal file
View File

@ -0,0 +1,6 @@
export const errorCode: any = {
"401": "认证失败,无法访问系统资源",
"403": "当前操作没有权限",
"404": "访问资源不存在",
default: "系统未知错误,请反馈给管理员",
};

51
src/utils/request.ts Normal file
View File

@ -0,0 +1,51 @@
import axios from "axios";
import { ElMessage, ElNotification } from "element-plus";
import { errorCode } from "@/utils/errorCode";
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
// baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
baseURL: "/dev-api",
timeout: 100000,
});
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode["default"];
if (code === 500) {
ElMessage.error(msg);
return Promise.reject(new Error(msg));
} else if (code !== 200) {
ElNotification.error({
title: msg,
});
return Promise.reject("error");
} else {
return Promise.resolve(res);
}
},
(error) => {
console.log("err" + error);
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage.error({ message, duration: 5 * 1000 });
// ElMessage({
// message: message,
// type: "error",
// duration: 5 * 1000,
// });
return Promise.reject(error);
}
);
export default service;

122
src/views/AccessRecords.vue Normal file
View File

@ -0,0 +1,122 @@
<template>
<div id="app-container">
<el-button size="small" @click="back" type="primary" plain icon="back"
>返回</el-button
>
<el-form
class="query-form"
inline
size="small"
:model="queryParams"
ref="queryParamsRef"
>
<el-form-item>
<el-date-picker
v-model="queryParams.startTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择开始时间"
/>
</el-form-item>
<el-form-item>
<el-date-picker
v-model="queryParams.endTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择结束时间"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleSearch"
>搜索
</el-button>
<el-button type="primary" icon="refresh" @click="resetQuery"
>重置
</el-button>
</el-form-item>
</el-form>
<el-table :data="logList" v-loading>
<el-table-column label="访问时间" prop="visitTime"></el-table-column>
<el-table-column label="访问IP" prop="visitIp"></el-table-column>
<el-table-column label="访问地区" prop="visitAddress"></el-table-column>
<el-table-column label="访问设备" prop="visitDevice"></el-table-column>
<el-table-column label="访问后缀" prop="urlSuffix"></el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
:page-size="queryParams.pageSize"
:current-page="queryParams.pageNum"
@update:current-page="handleCurrentChange"
layout="prev, pager, next"
:total="total"
/>
</div>
</div>
</template>
<script setup name="AccessRecords" lang="ts">
import { reactive, ref, toRefs } from "vue";
import { listLogList } from "@/api/shortlink";
import type { LogListParams } from "@/models/LogListParams";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
const logList = ref([]);
const total = ref(0);
const data = reactive<{ queryParams: LogListParams }>({
queryParams: {
urlSuffix: route.params.suffix as string,
pageNum: 1,
pageSize: 10,
},
});
const { queryParams } = toRefs(data);
const getList = async () => {
const resp = await listLogList(queryParams.value);
logList.value = resp.data.rows;
total.value = resp.data.total;
};
const queryParamsRef = ref();
const resetQuery = () => {
queryParams.value = {
urlSuffix: route.params.suffix as string,
pageNum: 1,
pageSize: 10,
};
if (queryParamsRef.value) {
queryParamsRef.value.resetFields();
}
getList();
};
const handleSearch = () => {
queryParams.value.pageNum = 1;
getList();
};
const handleCurrentChange = (page: number) => {
queryParams.value.pageNum = page;
getList();
};
const back = () => {
router.push("/");
};
getList();
</script>
<style scoped lang="scss">
#app-container {
padding: 20px;
.query-form {
margin-top: 10px;
}
.pagination {
margin-top: 20px;
margin-right: 10px;
display: flex;
justify-content: end;
}
}
</style>

383
src/views/GenShortLink.vue Normal file
View File

@ -0,0 +1,383 @@
<template>
<div id="app-container" class="p-3">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button @click="handleAdd" type="primary" size="small" icon="plus"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="delete"
@click="handleDelete()"
type="danger"
size="small"
:disabled="multiple"
>批量删除
</el-button>
</el-col>
</el-row>
<el-table
class="short-link-table"
:data="shortLinkList"
@selection-change="handleSelectionChange"
v-loading
>
<el-table-column align="center" type="selection" width="50" />
<el-table-column
align="center"
label="名称"
prop="name"
width="120"
></el-table-column>
<el-table-column align="center" label="短链接网址" prop="jumpUrl">
<template #default="{ row }">
<el-row :gutter="5">
<el-col :span="18">
<div>
<a
class="short-link"
:href="`//${row.urlPrefix}/dlj/${row.urlSuffix}`"
>
{{ row.urlPrefix }}/dlj/{{ row.urlSuffix }}
</a>
</div>
<div class="jump-url">{{ row.jumpUrl }}</div>
</el-col>
<el-col :span="6">
<el-row :gutter="5">
<el-col :span="1.5">
<i
class="iconfont icon-qrcode"
:style="{
cursor: 'pointer',
}"
@click="handleShowQrCode(row)"
></i>
</el-col>
<el-col :span="1.5">
<el-icon
:style="{
cursor: 'pointer',
}"
@click="copyToClipboard(row.jumpUrl)"
>
<CopyDocument />
</el-icon>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
</el-table-column>
<el-table-column label="访问次数" width="160" align="center">
<template #default="{ row }">
<div class="visit-count">
<div>
<span class="title">今日</span>
<span class="count">{{ row.todayVisit }}</span>
</div>
<div>
<span class="title">累计</span>
<span class="count">{{ row.historyVisit }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column
align="center"
label="过期时间"
prop="validityTime"
></el-table-column>
<el-table-column align="center" label="操作">
<template #default="{ row }">
<el-button
@click="handleUpdate(row.id)"
link
size="small"
type="warning"
>编辑
</el-button>
<el-button
@click="handleDelete(row.id)"
link
size="small"
type="danger"
>删除
</el-button>
<el-button
@click="goLogList(row.urlSuffix)"
link
size="small"
type="primary"
>访问记录
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
layout="prev, pager, next"
:page-size="queryParams.pageSize"
:current-page="queryParams.pageNum"
@update:current-page="handleCurrentChange"
:total="total"
/>
</div>
<el-dialog v-model="showEditDialog" title="新增短链接">
<el-form :model="form" ref="formRef" :rules="rules" :label-width="100">
<el-form-item label="链接标题" prop="name">
<el-input placeholder="请输入链接标题" v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="跳转链接" prop="jumpUrl">
<el-input
placeholder="请输入跳转链接"
v-model="form.jumpUrl"
></el-input>
</el-form-item>
<el-form-item label="URL前缀" prop="urlPrefix">
<el-input
placeholder="请输入URL前缀"
v-model="form.urlPrefix"
></el-input>
</el-form-item>
<el-form-item label="有效期" prop="validityTime">
<el-date-picker
value-format="YYYY-MM-DD HH:mm:ss"
v-model="form.validityTime"
type="datetime"
placeholder="请选择过期时间"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="showEditDialog = false"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="showQrCodeDialog" width="320">
<qrcode-vue
class="qrcode-canvas"
:value="qrcodeValue"
:size="280"
ref="qrcodeRef"
:margin="1"
></qrcode-vue>
<el-button
type="primary"
@click="downloadQrCode"
:style="{
width: '100%',
marginTop: '10px',
}"
>下载二维码
</el-button>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="GenShortLink">
import { addShortLink, deleteShortLink, getDetailById, listShortLink, updateShortLink } from "@/api/shortlink";
import { reactive, ref, toRefs } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { ShortLink } from "@/models/shortLink";
import QrcodeVue from "qrcode.vue";
import { useRouter } from "vue-router";
const qrcodeRef = ref();
const shortLinkList = ref<ShortLink[]>([]);
const showEditDialog = ref(false);
const editDialogTitle = ref<string>("");
const showQrCodeDialog = ref(false);
const qrcodeValue = ref("");
const qrcodeFileName = ref("");
const formRef = ref();
const data = reactive<{
queryParams: { pageNum: number; pageSize: number };
form: ShortLink;
rules: any;
}>({
queryParams: {
pageNum: 1,
pageSize: 10
},
form: {},
rules: {
name: [
{
required: true,
message: "请输入短链接名称",
trigger: "blur"
}
],
jumpUrl: [
{
required: true,
message: "请输入短链接",
trigger: "blur"
}
],
urlPrefix: [
{
required: true,
message: "请输入URL前缀",
trigger: "blur"
}
],
validityTime: [
{
required: true,
message: "请选择过期时间",
trigger: "change"
}
]
}
});
const { form, queryParams, rules } = toRefs(data);
const total = ref<number>(0);
const router = useRouter();
const getList = async () => {
const resp = await listShortLink(queryParams.value);
shortLinkList.value = resp.data.rows;
total.value = resp.data.total;
};
const handleShowQrCode = (data: ShortLink) => {
qrcodeValue.value = `${data.urlPrefix}/dlj/${data.urlSuffix}`;
qrcodeFileName.value = `${data.urlSuffix}.png`;
showQrCodeDialog.value = true;
};
const goLogList = (suffix: string) => {
router.push({
path: `/log-list/${suffix}`
});
};
const reset = () => {
form.value = {};
if (formRef.value) {
formRef.value.resetFields();
}
};
// 多选框选中数据
const linkIds = ref<number[]>([]);
const multiple = ref(true);
const single = ref(true);
const downloadQrCode = () => {
console.log(qrcodeRef.value);
const qrcodeCanvas: HTMLCanvasElement | null =
document.querySelector(".qrcode-canvas");
qrcodeCanvas?.toBlob((blob: Blob | null) => {
if (blob != null) {
let aLink = document.createElement("a");
aLink.download = qrcodeFileName.value;
aLink.href = URL.createObjectURL(blob);
aLink.click();
} else {
ElMessage.error("下载二维码失败");
}
});
};
const handleSelectionChange = (selection: ShortLink[]) => {
linkIds.value = selection.map((item: ShortLink) => item.id!);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const copyToClipboard = (data: string) => {
navigator.clipboard.writeText(data);
ElMessage.success("短链接已拷贝到剪贴板");
};
const handleAdd = () => {
reset();
editDialogTitle.value = "新增短链接";
showEditDialog.value = true;
};
const handleUpdate = async (id: number) => {
reset();
const { data } = await getDetailById(id);
form.value = data.data;
editDialogTitle.value = "修改短链接";
showEditDialog.value = true;
};
const handleDelete = (id?: string) => {
let ids: string;
if (id) {
ids = id;
} else {
ids = linkIds.value.join(",");
}
ElMessageBox.confirm("确认删除该短链接吗", "删除短链接", {
type: "warning"
}).then(async () => {
await deleteShortLink(ids);
ElMessage.success("删除短链接成功");
getList();
});
};
const submitForm = async () => {
if (form.value.id) {
await updateShortLink(form.value);
ElMessage.success("短链接修改成功");
showEditDialog.value = false;
getList();
} else {
await addShortLink(form.value);
ElMessage.success("短链接添加成功");
showEditDialog.value = false;
getList();
}
};
const handleCurrentChange = (page: number) => {
queryParams.value.pageNum = page;
getList();
};
getList();
</script>
<style scoped lang="scss">
#app-container {
padding: 20px;
a.short-link {
//overflow: hidden;
//text-overflow: ellipsis;
//white-space: nowrap;
}
.jump-url {
margin-top: 5px;
font-size: 12px;
//width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.visit-count {
display: flex;
flex-direction: column;
padding: 0 20px;
& > div {
width: 100%;
display: flex;
justify-content: space-between;
}
}
.short-link-table {
margin-top: 20px;
}
.pagination {
margin-top: 20px;
margin-right: 10px;
display: flex;
justify-content: end;
}
}
</style>

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
/** @type {import("tailwindcss").Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
theme: {
extend: {}
},
plugins: []
};

8
tsconfig.config.json Normal file
View File

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

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}

24
vite.config.ts Normal file
View File

@ -0,0 +1,24 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
"/dev-api": {
target: "http://192.168.110.10:1618",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, "")
}
}
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
}
});