Files
yunzerwebsiteallinone/uniapp/pages/tools/notepad/edit.vue
T
2026-07-15 18:14:17 +08:00

291 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<view class="form-card">
<view class="field">
<text class="field-label">标题</text>
<input
class="field-input title-input"
v-model="form.title"
type="text"
placeholder="输入标题"
placeholder-class="placeholder"
maxlength="80"
/>
</view>
<view class="field content-field">
<text class="field-label">内容</text>
<textarea
class="field-textarea"
v-model="form.content"
placeholder="记录你的想法..."
placeholder-class="placeholder"
:auto-height="false"
maxlength="5000"
:show-confirm-bar="false"
/>
<text class="char-count">{{ form.content.length }}/5000</text>
</view>
<view class="switch-row" @tap="form.pinned = !form.pinned">
<view class="switch-left">
<FaIcon name="thumbtack" :color="form.pinned ? '#3c9cff' : '#909399'" :size="16" />
<text class="switch-label">置顶笔记</text>
</view>
<view class="switch-dot" :class="{ on: form.pinned }" />
</view>
</view>
<view v-if="noteId && meta.updatedAt" class="meta">
<text>最近更新{{ formatDate(meta.updatedAt, true) }}</text>
</view>
<view class="footer">
<view v-if="noteId" class="btn-danger" @tap="onDelete">删除</view>
<view class="btn-primary" :class="{ disabled: saving }" @tap="onSave">
{{ saving ? '保存中...' : '保存' }}
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getNote, createNote, updateNote, deleteNote } from '@/api/note.js'
import { formatDate } from '@/utils/date.js'
const noteId = ref('')
const saving = ref(false)
const form = reactive({
title: '',
content: '',
pinned: false
})
const meta = reactive({
updatedAt: 0
})
onLoad(async (query) => {
if (query?.id) {
noteId.value = query.id
uni.setNavigationBarTitle({ title: '编辑笔记' })
await loadNote(query.id)
} else {
uni.setNavigationBarTitle({ title: '新建笔记' })
}
})
async function loadNote(id) {
try {
const note = await getNote(id)
if (!note) {
uni.showToast({ title: '笔记不存在', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
return
}
form.title = note.title
form.content = note.content
form.pinned = !!note.pinned
meta.updatedAt = note.updatedAt
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
}
async function onSave() {
if (saving.value) return
if (!form.title.trim() && !form.content.trim()) {
uni.showToast({ title: '请输入标题或内容', icon: 'none' })
return
}
saving.value = true
try {
if (noteId.value) {
await updateNote(noteId.value, {
title: form.title,
content: form.content,
pinned: form.pinned
})
} else {
await createNote({
title: form.title,
content: form.content,
pinned: form.pinned
})
}
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
} finally {
saving.value = false
}
}
function onDelete() {
uni.showModal({
title: '删除笔记',
content: '删除后无法恢复,确定继续吗?',
success: async (res) => {
if (!res.confirm) return
try {
await deleteNote(noteId.value)
uni.showToast({ title: '已删除', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
})
}
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.page {
min-height: 100vh;
background: $color-bg-page;
padding: 24rpx $page-padding-x;
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
.form-card {
@include card;
padding: 8rpx 0;
}
.field {
padding: 24rpx 28rpx;
& + & {
border-top: 1rpx solid $color-divider;
}
}
.field-label {
display: block;
font-size: 24rpx;
color: $color-text-muted;
margin-bottom: 12rpx;
}
.field-input,
.field-textarea {
width: 100%;
font-size: 30rpx;
color: $color-text;
}
.title-input {
font-weight: 600;
}
.content-field {
position: relative;
}
.field-textarea {
height: 480rpx;
line-height: 1.7;
}
.placeholder {
color: $color-text-muted;
}
.char-count {
display: block;
text-align: right;
font-size: 22rpx;
color: $color-text-muted;
margin-top: 12rpx;
}
.switch-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx;
border-top: 1rpx solid $color-divider;
}
.switch-left {
display: flex;
align-items: center;
gap: 12rpx;
}
.switch-label {
font-size: 28rpx;
color: $color-text;
}
.switch-dot {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 1rpx solid $color-border;
background: $color-card;
&.on {
background: $color-primary;
border-color: $color-primary;
box-shadow: inset 0 0 0 6rpx #fff;
}
}
.meta {
margin-top: 20rpx;
padding: 0 8rpx;
font-size: 24rpx;
color: $color-text-muted;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 20rpx;
padding: 20rpx $page-padding-x;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: $color-card;
border-top: 1rpx solid $color-border;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.btn-primary,
.btn-danger {
flex: 1;
height: 88rpx;
border-radius: $radius-md;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
}
.btn-primary {
background: $color-primary;
color: #fff;
&:active {
background: $color-primary-dark;
}
&.disabled {
opacity: 0.6;
}
}
.btn-danger {
background: $color-bg-page;
color: #f56c6c;
border: 1rpx solid #fde2e2;
}
</style>