You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 lines
5.0 KiB
Vue

<template>
<view class="page-container">
<view class="page-toolbar">
<button class="btn btn-ghost back-btn" @click="goBack">
<uni-icons type="arrow-left" size="18" color="#1e3a8a"></uni-icons>
返回
</button>
<view class="toolbar-title-wrap">
<text class="toolbar-title">待办事项</text>
</view>
</view>
<view class="add-bar">
<input
v-model="draft"
class="add-input"
confirm-type="done"
placeholder="请输入待办内容"
@confirm="addTodo"
/>
<button class="btn btn-primary add-btn" @click="addTodo">添加</button>
</view>
<scroll-view class="todo-list" scroll-y>
<view v-if="todos.length === 0" class="empty-text">暂无待办事项</view>
<view v-for="item in todos" :key="item.id" class="todo-item">
<view class="todo-check" @click="toggleTodo(item.id)">
<uni-icons
:type="item.done ? 'checkbox-filled' : 'smallcircle'"
size="22"
:color="item.done ? '#2563eb' : '#94a3b8'"
></uni-icons>
</view>
<input
class="todo-input"
:class="{ 'todo-input-done': item.done }"
:value="item.text"
@blur="onTodoBlur(item.id, $event)"
/>
<button class="btn btn-ghost del-btn" @click="removeTodo(item.id)">
</button>
</view>
</scroll-view>
</view>
</template>
<script setup>
import {
createDutyTodo,
loadDutyTodos,
saveDutyTodos,
} from "@/utils/dutyTodos.js";
import { onShow } from "@dcloudio/uni-app";
import { ref } from "vue";
const draft = ref("");
const todos = ref([]);
const persist = (next) => {
todos.value = saveDutyTodos(next);
};
const addTodo = () => {
const text = String(draft.value || "").trim();
if (!text) {
uni.showToast({
title: "请输入待办内容",
icon: "none",
});
return;
}
persist([createDutyTodo(text), ...todos.value]);
draft.value = "";
};
const updateTodo = (id, value) => {
const text = String(value || "").trim();
persist(
todos.value
.map((item) => (item.id === id ? { ...item, text } : item))
.filter((item) => item.text),
);
};
const onTodoBlur = (id, event) => {
updateTodo(id, event?.detail?.value);
};
const toggleTodo = (id) => {
persist(
todos.value.map((item) =>
item.id === id ? { ...item, done: !item.done } : item,
),
);
};
const removeTodo = (id) => {
persist(todos.value.filter((item) => item.id !== id));
};
const goBack = () => {
uni.navigateBack({
fail: () => {
uni.navigateTo({ url: "/pages/index/index" });
},
});
};
onShow(() => {
todos.value = loadDutyTodos();
});
</script>
<style lang="scss" scoped>
.page-container {
height: var(--pad-h);
overflow: hidden;
display: flex;
flex-direction: column;
background: #eef3f9;
}
.page-toolbar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
padding: calc(env(safe-area-inset-top) + 10px) 20px 10px;
background: #fff;
border-bottom: 1px solid #d7e2ee;
box-shadow: 0 8px 24px rgba(30, 58, 138, 0.06);
z-index: 20;
}
.page-toolbar button {
width: auto;
flex: none;
}
.back-btn {
width: auto;
min-width: 84px;
height: 40px;
padding: 0 14px;
margin: 0;
gap: 4px;
flex-shrink: 0;
}
.toolbar-title-wrap {
flex-shrink: 0;
padding-left: 10px;
border-left: 3px solid #1d4ed8;
}
.toolbar-title {
font-size: 18px;
font-weight: 700;
color: #1e3a8a;
}
.add-bar {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 10px;
padding: 14px 20px;
background: #fff;
border-bottom: 1px solid #d7e2ee;
}
.add-input {
flex: 1;
height: 42px;
padding: 0 12px;
background: #f4f7fb;
border: 1px solid #d7e2ee;
border-radius: 10px;
font-size: 15px;
color: #1e293b;
}
.add-btn {
width: 88px;
height: 42px;
}
.todo-list {
flex: 1;
min-height: 0;
padding: 12px 20px 20px;
}
.empty-text {
padding: 48px 0;
text-align: center;
color: #94a3b8;
font-size: 14px;
}
.todo-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding: 10px 12px;
background: #fff;
border: 1px solid #d7e2ee;
border-radius: 12px;
}
.todo-check {
flex-shrink: 0;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.todo-input {
flex: 1;
min-width: 0;
height: 36px;
font-size: 15px;
color: #1e293b;
}
.todo-input-done {
color: #94a3b8;
text-decoration: line-through;
}
.del-btn {
width: auto;
min-width: 64px;
height: 32px;
padding: 0 12px;
font-size: 13px;
}
.btn {
width: auto;
min-width: 84px;
height: 32px;
margin: 0;
border-radius: 10px;
font-size: 14px;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.btn::after {
border: none;
}
.btn-primary {
background: #2563eb;
color: #fff;
}
.btn-ghost {
background: #fff;
color: #1e3a8a;
border: 1px solid #c7d7ea;
}
</style>