使用Vue3编写短链接生成器页面
孙泽辉 Lv5

最近发现一个程序员挑战网站,有项目需求(算是题目),让你去实现出来,然后可以和站内网友分享,很有意思。挑了个短链接生成器做一下玩玩。

介绍

这个是刚才提到的网站:

ProjectLearn - Learn to Code by Creating Projects

其实这个只是个收集项目的网站,里面搜罗了各种关于实战类的项目。

image

我做的是这个。

sunzehui’s solution to Link shortener website - DevProjects (codementor.io)

上面是我的项目地址,右上角按钮可以在线预览以及在Github中查看

image

自我感觉写的还行。(右上角被挡住的是切换主题)

实现

谈一下关键源码吧。

最近发现一款在线编辑器,老好用了,可以注册后一起看

只需要在Github项目前面输入gitpod.io/#,例如我的

Starting — Gitpod

因为项目很小只有一个页面,所以我只有App.vue

点击按钮后动画

点击按钮后,文本框和输入框上移,信息框淡入。

用按钮控制信息框显示,控制变量panelShow,用自带的transition组件添加淡入动画(修改透明度)。

CSS 为搜索框添加平移过渡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

<div class="container flex items-center justify-center">
<div class="flex flex-col -translate-y-12 wrapper">
<div
class="transition-all duration-300"
:class="{ 'set-top': panelShow, 'set-center': !panelShow }"
>
<按钮以及文本框 @click="panelShow = true"/>
</div>
<transition name="content">
<信息框 v-if="panelShow"/>
</transition>
</div>
</div>

<style>
/* 点击按钮后搜索框上移 */
.set-top {
@apply -translate-y-8;
}
/* 未搜索时搜索框至于中间 */
.set-center {
@apply translate-y-0;
}
</style>

封装request

这么小的程序没必要封装其实,我用compositionAPI稍微写了写(参考了AttoJS@useRequest)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在 App.vue 中使用
const longUrl = ref("");
const { showMessage } = useMessage();
const { execute, loading, response } = useServerApi<ShortResponse>(longUrl, {
onSuccess(data) {
if (data.status >= 200 && data.status <= 200) {
showMessage(MType.SUCCESS, "created! 🎉");
}
},
onError(error) {
showMessage(MType.ERROR, error.message || "error! please retry! 😪");
},
});

下面是实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// src/composable/useServerApi.ts
import axios, { type AxiosResponse } from "axios";
import { shortUrlToken } from "@/config";
import type { MaybeRef } from "@vueuse/core";

export function useServerApi<T>(
long_url: MaybeRef<string>,
options?: ServerApiOptions
) {
// 暴露出响应内容
const response = ref<T>();
const loading = ref(false);
// 手动执行函数
const execute = async () => {
loading.value = true;
let result = null;
try {
result = await axios.post<T>(
"https://api-ssl.bitly.com/v4/shorten",
{
domain: "bit.ly",
long_url: unref(long_url),
},
{
headers: {
Authorization: `Bearer ${shortUrlToken}`,
},
}
);
response.value = result.data;
// 调用传入的成功回调函数
options?.onSuccess(result);
return result;
} catch (error) {
// 失败调用传入的失败回调函数
options?.onError(error);
throw error;
} finally {
loading.value = false;
}
};
return {
execute,
loading,
response,
};
}

展示消息框

上面请求处理函数中的代码有用到useMessage这个composable,这个参考了elementUI的方式,不过我这里用了闭包保证只创建一个消息框,修改信息共用一个组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import message from "@/components/message.vue";
import type { MType } from "@/types/message";
import { createApp } from "vue";

export function useMessage() {
// 创建 message 组件
const messageComponent = createApp(message);
const fragment = document.createDocumentFragment();
const vm = messageComponent.mount(fragment as any);
document.body.appendChild(fragment);

let timeId: null | number = null;
return {
showMessage(type: MType, content: string, time = 2000) {
// 防抖,保证第一个消息框不会被覆盖
if (timeId) clearTimeout(timeId);
// 这里 vm 调用的是消息框组件导出的 setState
// 目的就是更新组件内的信息,免去重复创建
// @ts-ignore
vm.setState({
show: true,
content,
type,
});
// 展示结束后关闭
// @ts-ignore
timeId = setTimeout(() => vm.setState({ show: false }), time);
},
};
}

展示加载框

这个加载框不是动画,是我手动模拟的卡片翻转,用true/false轮番交替。具体看daisyUI上面的swap吧。

Swap — Tailwind CSS Components (daisyui.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<script lang="ts" setup>
const props = defineProps({
loading: {
type: Boolean,
default: false,
},
});
const status = ref(false);
let timeId: NodeJS.Timer | null = null;
watch(
() => props.loading,
(val) => {
if (val) {
timeId = setInterval(() => {
status.value = !status.value;
}, 1000);
} else {
if (timeId) clearInterval(timeId);
}
},
{ immediate: true }
);
onBeforeUnmount(() => {
if (timeId) clearInterval(timeId);
});
</script>
<template>
<label
v-show="props.loading"
class="fixed -translate-x-1/2 -translate-y-1/2 top-1/3 left-1/2 swap swap-flip text-9xl"
>
<input type="checkbox" hidden v-show="false" v-model="status" />
<div class="swap-on">😈</div>
<div class="swap-off">😇</div>
</label>
</template>

主题切换

这完全是daisyUI的功劳,刚开始用没注意,没想到写一次组件,竟然支持那么多的主题,而且还能自定义主题!

只要修改<html data-theme="dark">标签上的data-theme属性即可。

为了方便操作,官方推荐使用theme-change这个包

saadeghi/theme-change: Change CSS theme with toggle, buttons or select using CSS custom properties and localStorage (github.com)

用法非常简单,在页面加载后调用一下即可。

总结

水了几个commit,再水一篇博客,爽歪歪

下次还写!

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Total words 85.5k