引言
在当今数字化时代,在线工具网站为用户提供了便捷的服务和功能,本文分享了我使用UniApp和Django开发的一款多平台在线工具网站。通过这个项目,我探索了跨平台开发与强大的后端框架结合的优势,实现了用户友好的界面和稳健的功能。
技术选型
我选择UniApp作为前端开发工具,主要因为它能够高效地实现同时支持多个平台(Web、小程序、App)的开发需求。而Django作为后端框架,则因其快速开发能力、自带后台管理系统、优秀的安全性和灵活的数据管理而备受推崇。
项目环境
前端框架:uniapp
前端编辑器:HbuilderX 版本号4.24
编辑器官网:HBuilderX-高效极客技巧
后端解释器版本:python 3.10.5
后端框架:django==3.0
后端编辑器:vscode 版本1.92.0
项目创建
创建前端uniapp项目创建可参考
https://www.cnblogs.com/yunyin/articles/15729438.html
后端django项目创建可参考:
https://www.itheima.com/news/20221219/164219.html
项目概述
我的在线工具网站旨在为用户提供各种实用工具和服务,包括但不限于实用工具、查询工具、休闲娱乐工具、在线转换、在线游戏、在线加密等功能。用户可以通过Web浏览器、微信小程序或安卓访问和使用这些工具。前端开发(UniApp)页面设计与布局在UniApp中,我采用了简洁直观的页面设计,保证了用户操作的流畅性和友好性。每个工具都有清晰的功能入口,以便用户快速上手。
功能实现
项目采用屏幕自适应,当屏幕宽度小于600px的时候,则一行三个工具,如果屏幕尺寸够大,则一行四个工具,可以完美在移动端适配
主界面代码:
<template>
<view class="loading" v-if="!dataLoaded">
<view class="item item-1"></view>
<view class="item item-2"></view>
<view class="item item-3"></view>
<view class="item item-4"></view>
</view>
<!-- 主要容器 -->
<view class="container" v-if="dataLoaded">
<view class="search-box">
<image class="search-icon" src="/static/search.png">
</image>
<input v-model="keyword" @input="handleSearch" placeholder="请输入搜索关键词" class="search-input" />
</view>
<!-- 循环渲染多个盒子 -->
<view v-for="(category, index) in filteredToolCategories" :key="index" class="category-box">
<view class="category-name">{{ category.title }}</view>
<!-- 使用循环渲染每个工具 -->
<view class="tool-list">
<view v-for="(tool, idx) in category.tools" :key="idx" class="tool-item"
@click="handleToolClick(tool.link)">
<image :src="tool.icon" class="tool-icon"></image>
<text class="tool-name">{{ tool.name }}</text>
</view>
</view>
</view>
<!-- 底部米白色色块 -->
<view class="footer-block">
<!-- 底部文字信息 -->
<view class="footer-text">
本站工具严禁用于非法用途,本站不承担任何因使用工具造成的损失及责任。
</view>
<!-- 底部导航链接 -->
<view class="footer-links">
<navigator url="/pages/about/about" open-type="navigate">关于我们</navigator>
<navigator url="/pages/serviceAgreement/serviceAgreement" open-type="navigate">服务条款</navigator>
<navigator url="/pages/PrivacyPolicy/PrivacyPolicy" open-type="navigate">隐私政策</navigator>
<a href="https://support.qq.com/products/660601" target="_blank">反馈建议</a>
</view>
<!-- 版权信息 -->
<view class="footer-text">Copyright © 2024 TT在线工具 All Rights Reserved 滇ICP备2024034889号-1 </view>
</view>
</view>
</template>
script:
<script>
export default {
data() {
return {
toolCategories: [],
keyword: '', // 查找工具
dataLoaded: false, // 标记数据是否加载完成
// 弹出提示
modalConfig: {
showModal: 'false', //弹出组件是否显示
title: '提示', // 弹窗标题
content: '提示内容', // 弹窗内容
}
};
},
computed: {
filteredToolCategories() {
if (!this.keyword) {
return this.toolCategories;
}
return this.toolCategories.map(category => {
return {
...category,
tools: category.tools.filter(tool => tool.name.includes(this.keyword))
};
}).filter(category => category.tools.length > 0);
}
},
created() {
this.fetchToolData();
},
// 加载完成后调用方法
mounted() {
// 判断是否为ture 是的话调用函数 否则不调用
if (this.modalConfig.showModal === 'true') {
this.showModalDialog();
} else {
console.log('加载动画函数未被调用');
}
},
methods: {
async fetchToolData() {
try {
const res = await uni.request({
url: 'https://ttgj.top/tools/',
method: 'GET',
});
const data = res.data;
// 对分类根据返回的sort字段进行排序
data.sort((a, b) => a.category.sort - b.category.sort);
data.forEach(item => {
const title = item.category.title;
const name = item.name;
const link = item.link;
const icon = item.icon;
let existingCategory = this.toolCategories.find(category => category.title === title);
if (!existingCategory) {
existingCategory = {
title: title,
tools: []
};
this.toolCategories.push(existingCategory);
}
existingCategory.tools.push({
name: name,
link: link,
icon: icon
});
});
this.dataLoaded = true; // 数据加载完成后显示页面内容
} catch (error) {
console.error('获取工具列表错误:', error);
}
},
handleSearch() {
// 由于已在 computed 中实现过滤,此处无需额外代码
},
handleToolClick(link) {
console.log('跳转值:', link);
//如果小于768则认为移动端
const isMobile = window.innerWidth < 768;
//判断isMobile是否为true 是的话执行这个条件
if (isMobile) {
if (link.startsWith('http')) {
console.log('http链接', link);
window.open(link, '_blank'); // 新标签页打开外部链接
} else {
// 移动端,使用uni.navigateTo方法
uni.navigateTo({
url: "/pages/" + link,
animationType: 'push',
});
}
//不为true执行这个条件
} else {
// 非移动端(假设是PC端),使用window.open方法
if (link.startsWith('http')) {
console.log('http链接', link);
window.open(link, '_blank'); // 新标签页打开外部链接
} else {
console.log('path路径');
const currentOrigin = window.location.origin; // 获取当前页面的 origin,例如:http://localhost:5173
window.open(currentOrigin + '/pages/' + link, '_blank'); // 新标签页打开内部链接
}
}
},
showModalDialog() {
uni.showModal({
title: this.modalConfig.title,
content: this.modalConfig.content,
showCancel: true,
cancelText: '取消',
confirmText: '确认',
success: (res) => {
if (res.confirm) {
console.log('用户点击了确认按钮');
// 可以在这里添加确认按钮的逻辑处理
} else if (res.cancel) {
console.log('用户点击了取消按钮');
// 可以在这里添加取消按钮的逻辑处理
}
// 关闭模态弹窗后,将 isModalVisible 设置为 false
this.isModalVisible = false;
}
});
}
}
};
</script>
style:
<style scoped lang="scss">
.container {
display: flex;
flex-wrap: wrap;
padding: 50rpx;
}
.search-box {
margin-bottom: 20px;
width: 100%;
display: flex;
align-items: center;
background-color: #F9F7F7;
border-radius: 15rpx;
padding: 10rpx 20rpx;
}
.search-icon {
width: 35rpx;
height: 35rpx;
margin-right: 10rpx;
/* 设置搜索图标与输入框之间的间距 */
}
.search-input {
flex: 1;
font-size: 35rpx;
color: #333333;
border: none;
background: none;
outline: none;
}
.category-box {
width: 100%;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
// 分类名字
.category-name {
font-size: 16px;
color: #F9F7F7;
margin-bottom: 10px;
background-color: #71C9CE;
padding: 5px 10px;
display: inline-block;
border-radius: 5px;
}
// 工具列表父元素
.tool-list {
display: flex;
flex-wrap: wrap;
}
// 工具名字和图标
.tool-item {
width: calc(25% - 10px);
/*表示将父容器的宽度分成四等份(每份占25%),然后再从每份中减去10像素的间距*/
/* 一行四个工具,间隔为10px */
margin-right: 10px;
margin-bottom: 10px;
margin-top: 20px;
/* 添加工具和标题之间 */
display: flex;
flex-direction: column;
align-items: center;
}
// 当页面宽度小于500px 则执行这个代码
@media (max-width: 500px) {
.tool-item {
width: calc(33% - 10px);
/*表示将父容器的宽度分成四等份(每份占33%),然后再从每份中减去10像素的间距*/
/* 一行三个工具,间隔为10px */
margin-right: 10px;
margin-bottom: 10px;
margin-top: 20px;
/* 添加工具和标题之间 */
display: flex;
flex-direction: column;
align-items: center;
}
}
// 工具图标
.tool-icon {
width: 40px;
height: 40px;
margin-bottom: 5px;
}
// 工具名字
.tool-name {
font-size: 14px;
text-align: center;
white-space: nowrap;
/* 防止换行 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 如果文本超出部分被隐藏,则显示省略号 */
display: inline-block;
}
// 当页面宽度小于500px 则执行这个代码
@media (max-width: 500px) {
.tool-name {
font-size: 14px;
text-align: center;
width: 99%;
/* 宽度超过99%则显示省略号 */
white-space: nowrap;
/* 防止换行 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 如果文本超出部分被隐藏,则显示省略号 */
display: inline-block;
}
}
.footer-block {
bottom: 0;
/* 固定在底部 */
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.1);
/* 添加轻微阴影效果 */
padding: 20px;
text-align: center;
margin: 0 auto;
/* 居中 */
width: 100%;
/* 控制宽度,左右展开 */
}
.footer-text {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.footer-links {
display: flex;
/* 使用 Flexbox 布局 */
justify-content: center;
/* 居中对齐 */
gap: 20px;
/* 设置子元素之间的间距 */
margin-bottom: 10px;
}
a {
text-decoration: none;
font-size: 14px;
color: #007bff;
}
a:hover {
text-decoration: underline;
/* 悬停时下划线效果 */
}
.footer-links navigator {
font-size: 14px;
color: #007bff;
/* 链接颜色 */
}
.footer-links navigator:hover {
text-decoration: underline;
/* 悬停时下划线效果 */
}
.loading {
position: absolute;
width: 200px;
height: 200px;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.item {
width: 100px;
height: 100px;
position: absolute;
}
.item-1 {
background-color: #FA5667;
top: 0;
left: 0;
z-index: 1;
animation: item-1_move 1.8s cubic-bezier(.6, .01, .4, 1) infinite;
}
.item-2 {
background-color: #7A45E5;
top: 0;
right: 0;
animation: item-2_move 1.8s cubic-bezier(.6, .01, .4, 1) infinite;
}
.item-3 {
background-color: #1B91F7;
bottom: 0;
right: 0;
z-index: 1;
animation: item-3_move 1.8s cubic-bezier(.6, .01, .4, 1) infinite;
}
.item-4 {
background-color: #FAC24C;
bottom: 0;
left: 0;
animation: item-4_move 1.8s cubic-bezier(.6, .01, .4, 1) infinite;
}
@keyframes item-1_move {
0%,
100% {
transform: translate(0, 0)
}
25% {
transform: translate(0, 100px)
}
50% {
transform: translate(100px, 100px)
}
75% {
transform: translate(100px, 0)
}
}
@keyframes item-2_move {
0%,
100% {
transform: translate(0, 0)
}
25% {
transform: translate(-100px, 0)
}
50% {
transform: translate(-100px, 100px)
}
75% {
transform: translate(0, 100px)
}
}
@keyframes item-3_move {
0%,
100% {
transform: translate(0, 0)
}
25% {
transform: translate(0, -100px)
}
50% {
transform: translate(-100px, -100px)
}
75% {
transform: translate(-100px, 0)
}
}
@keyframes item-4_move {
0%,
100% {
transform: translate(0, 0)
}
25% {
transform: translate(100px, 0)
}
50% {
transform: translate(100px, -100px)
}
75% {
transform: translate(0, -100px)
}
}
</style>
运行效果:
后端开发(Django)
数据库设计
我使用Django自带的ORM设计了数据库,包括工具分类名、工具名字及图片,确保了数据的安全和可靠性。以下是一个简化的模型示例:
from django.db import models
class ToolCategory(models.Model):
title = models.CharField(max_length=100,verbose_name='分类名')
sort = models.IntegerField(default=1, verbose_name='排序')
class Meta:
# admin后台显示的子标题
verbose_name='分类' #增加按钮显示增加用户以及标题
verbose_name_plural='工具分类列表'
def __str__(self):
return self.title
class Tool(models.Model):
name = models.CharField(max_length=100,verbose_name='工具名')
icon = models.ImageField(upload_to='tool_ico',verbose_name='图标') #从本地上传
link = models.CharField(max_length=100,default='pathname/pathname',verbose_name='工具路径-链接')
category = models.ForeignKey(ToolCategory, on_delete=models.CASCADE,verbose_name='工具分类')
# 其他字段,如描述、使用方法等
class Meta:
verbose_name='工具' #增加按钮显示增加用户以及标题
verbose_name_plural='工具列表'
def __str__(self):
return self.name
API开发:
通过Django的视图函数和路由设置,我设计了一组RESTful API来处理前端请求,并与数据库进行交互。以下是一个简化的视图函数示例:
from django.shortcuts import render
# Create your views here.
from rest_framework import viewsets, mixins
from rest_framework.response import Response
from .models import Tool, ToolCategory
from .serializers import ToolSerializer, ToolCategorySerializer # 假设你创建了这些序列化器
class ToolCategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = ToolCategory.objects.all()
serializer_class = ToolCategorySerializer
class ToolViewSet(viewsets.ModelViewSet):
queryset = Tool.objects.all().select_related('category') # 预加载关联的ToolCategory对象
serializer_class = ToolSerializer
def get_queryset(self):
# 你可以在这里添加额外的查询逻辑,比如过滤等
return super().get_queryset()#在后端数据库添加数据后返回工具名字、工具图片、工具分类等信息:
成果展示
与用户体验经过不懈努力,我的在线工具网站已正式上线并稳定运行。该网站支持多平台访问,无论是PC端还是移动端,用户都能轻松使用。网站的用户体验得到了广大用户的积极反馈,特别是其界面设计的友好性和功能的完备性,更是获得了用户的高度认可。
总结与展望
通过此次项目,我不仅在技术上取得了显著的进步,还深刻体会到了UniApp和Django这两个框架在实际项目中的强大应用价值。它们为我提供了灵活的开发方式和高效的开发效率,使得我能够更专注于实现项目的核心功能。展望未来,我计划继续对在线工具网站进行优化和扩展,引入更多实用的功能和服务,以进一步提升用户体验,满足用户多样化的需求。
结语
如果你也对使用UniApp和Django开发项目充满兴趣,或者想要了解更多关于我的项目细节和技术实现,我非常欢迎你的联系。让我们携手共进,共同探索更多前沿技术和创新应用!
项目访问方式
为了让你更便捷地体验我的在线工具网站,以下是访问方式:
项目网站地址:TT工具
微信小程序访问二维码:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。