Initial commit of akmon project
This commit is contained in:
46
pages/mt/chat.uvue
Normal file
46
pages/mt/chat.uvue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view :class="['mt-chat', isLargeScreen ? 'large' : 'small']">
|
||||
<scroll-view class="mt-chat-history">
|
||||
<view v-for="msg in messages" :key="msg.id" :class="['mt-chat-msg', msg.role]">
|
||||
<text>{{ msg.content }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="mt-chat-input">
|
||||
<input v-model="input" :placeholder="t('mt.chat.placeholder')" @confirm="onSend" />
|
||||
<button @tap="onSend">{{ t('mt.chat.send') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref, onLoad } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { useChat } from '@/composables/use-chat'
|
||||
|
||||
const messages = ref([])
|
||||
const input = ref('')
|
||||
const { sendMessage, loadHistory } = useChat()
|
||||
|
||||
onLoad(() => {
|
||||
loadHistory().then(res => { messages.value = res })
|
||||
})
|
||||
|
||||
function onSend() {
|
||||
if (!input.value) return
|
||||
sendMessage(input.value).then(msg => {
|
||||
messages.value.push(msg)
|
||||
input.value = ''
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-chat {
|
||||
display: flex; flex-direction: column; height: 100vh;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-chat-history { flex: 1; overflow-y: auto; padding: 24rpx; }
|
||||
.mt-chat-msg { margin-bottom: 16rpx; }
|
||||
.mt-chat-msg.user { text-align: right; color: #1976d2; }
|
||||
.mt-chat-msg.assistant { text-align: left; color: #333; }
|
||||
.mt-chat-input { display: flex; gap: 12rpx; padding: 16rpx; border-top: 1px solid #eee; }
|
||||
}
|
||||
</style>
|
||||
69
pages/mt/comments.uvue
Normal file
69
pages/mt/comments.uvue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<view :class="['mt-comments', isLargeScreen ? 'large' : 'small']">
|
||||
<text class="mt-title">{{ t('mt.comments.title') }}</text>
|
||||
<scroll-view>
|
||||
<view v-for="comment in comments" :key="comment.id" class="mt-comment-item">
|
||||
<view class="mt-comment-header">
|
||||
<image :src="comment.author_avatar" class="mt-comment-avatar" />
|
||||
<text class="mt-comment-author">{{ comment.author_name }}</text>
|
||||
<text class="mt-comment-date">{{ comment.created_at }}</text>
|
||||
</view>
|
||||
<text class="mt-comment-content">{{ comment.content }}</text>
|
||||
<view class="mt-comment-actions">
|
||||
<button @tap="onLike(comment)">{{ t('mt.comments.like') }}</button>
|
||||
<button @tap="onReply(comment)">{{ t('mt.comments.reply') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="mt-comment-input">
|
||||
<input v-model="input" :placeholder="t('mt.comments.input')" @confirm="onSend" />
|
||||
<button @tap="onSend">{{ t('mt.comments.send') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref, onLoad } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { getComments, sendComment } from '@/composables/use-comments'
|
||||
|
||||
const comments = ref([])
|
||||
const input = ref('')
|
||||
const targetId = ref('')
|
||||
|
||||
onLoad((params) => {
|
||||
if (params.id) {
|
||||
targetId.value = params.id
|
||||
getComments(params.id).then(res => { comments.value = res })
|
||||
}
|
||||
})
|
||||
|
||||
function onSend() {
|
||||
if (!input.value) return
|
||||
sendComment(targetId.value, input.value).then(res => {
|
||||
comments.value.push(res)
|
||||
input.value = ''
|
||||
})
|
||||
}
|
||||
function onLike(comment) {
|
||||
// 点赞逻辑
|
||||
}
|
||||
function onReply(comment) {
|
||||
// 回复逻辑
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-comments {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-title { font-size: 40rpx; font-weight: bold; margin-bottom: 24rpx; }
|
||||
.mt-comment-item { border-bottom: 1px solid #eee; padding: 16rpx 0; }
|
||||
.mt-comment-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 8rpx; }
|
||||
.mt-comment-avatar { width: 48rpx; height: 48rpx; border-radius: 50%; }
|
||||
.mt-comment-author { font-weight: 500; margin-right: 8rpx; }
|
||||
.mt-comment-date { color: #aaa; font-size: 22rpx; }
|
||||
.mt-comment-content { margin-bottom: 8rpx; }
|
||||
.mt-comment-actions { display: flex; gap: 16rpx; }
|
||||
.mt-comment-input { display: flex; gap: 12rpx; padding: 16rpx; border-top: 1px solid #eee; }
|
||||
}
|
||||
</style>
|
||||
59
pages/mt/detail.uvue
Normal file
59
pages/mt/detail.uvue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<view :class="['mt-detail', isLargeScreen ? 'large' : 'small']">
|
||||
<!-- 标题 -->
|
||||
<text class="mt-title">{{ t('mt.detail.title') }}</text>
|
||||
<!-- 内容区 -->
|
||||
<scroll-view>
|
||||
<view class="mt-content">
|
||||
<!-- 资讯内容渲染区域 -->
|
||||
<text>{{ newsDetail.title }}</text>
|
||||
<rich-text :nodes="newsDetail.content" />
|
||||
</view>
|
||||
<!-- 评论入口 -->
|
||||
<button @tap="goToComments">{{ t('mt.detail.comments') }}</button>
|
||||
</scroll-view>
|
||||
<!-- 收藏/转发/点赞等操作栏 -->
|
||||
<view class="mt-actions">
|
||||
<button @tap="onFavorite">{{ t('mt.detail.favorite') }}</button>
|
||||
<button @tap="onShare">{{ t('mt.detail.share') }}</button>
|
||||
<button @tap="onLike">{{ t('mt.detail.like') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref, onLoad } from 'uni-app'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { getNewsDetail } from '@/composables/use-news'
|
||||
|
||||
const newsDetail = ref({})
|
||||
|
||||
onLoad((params) => {
|
||||
if (params.id) {
|
||||
getNewsDetail(params.id).then(res => {
|
||||
newsDetail.value = res
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function goToComments() {
|
||||
uni.navigateTo({ url: `/pages/mt/comments.uvue?id=${newsDetail.value.id}` })
|
||||
}
|
||||
function onFavorite() {
|
||||
// 收藏逻辑
|
||||
}
|
||||
function onShare() {
|
||||
// 转发逻辑
|
||||
}
|
||||
function onLike() {
|
||||
// 点赞逻辑
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-detail {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-title { font-size: 40rpx; font-weight: bold; margin-bottom: 24rpx; }
|
||||
.mt-content { margin-bottom: 32rpx; }
|
||||
.mt-actions { display: flex; gap: 24rpx; justify-content: flex-end; }
|
||||
}
|
||||
</style>
|
||||
261
pages/mt/index.uvue
Normal file
261
pages/mt/index.uvue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<view class="index-container">
|
||||
<!-- Banner 区域,可轮播 -->
|
||||
<swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" interval="4000" circular>
|
||||
<swiper-item v-for="(item, idx) in banners" :key="idx">
|
||||
<image class="banner-img" :src="item.cover" @click="onBannerClick(item)"></image>
|
||||
<view class="banner-title">{{ item.title }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 分类Tab -->
|
||||
<scroll-view class="category-tabs" scroll-x>
|
||||
<view v-for="(cat, idx) in categories" :key="cat.id" :class="['tab-item', currentCategory === cat.id ? 'active' : '']" @click="switchCategory(cat.id)">
|
||||
{{ cat.name }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 资讯列表 -->
|
||||
<list-view style="flex:1;" refresher-enabled=true @refresherrefresh="onRefresh" :refresher-triggered="refresherTriggered" enable-back-to-top="true">
|
||||
<list-item v-for="(news, idx) in newsList" :key="news.id" type=2>
|
||||
<view class="news-card" hover-class="news-card-hover" @click="goDetail(news, idx)">
|
||||
<image class="news-cover" :src="news.cover"></image>
|
||||
<view class="news-body">
|
||||
<text class="news-title">{{ news.title }}</text>
|
||||
<text class="news-summary">{{ news.summary }}</text>
|
||||
<view class="news-meta">
|
||||
<text class="news-author">{{ news.author_name }}</text>
|
||||
<text class="news-time">{{ news.published_at }}</text>
|
||||
<view class="news-tags">
|
||||
<text v-for="tag in news.tags" :key="tag" class="news-tag">{{ tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</list-item>
|
||||
<view v-if="loading" class="loading-text">{{ $t('status.loading') }}</view>
|
||||
<view v-if="!loading && newsList.length === 0" class="empty-text">{{ $t('status.noData') }}</view>
|
||||
</list-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
type Banner = {
|
||||
cover: string,
|
||||
title: string,
|
||||
id: string
|
||||
}
|
||||
type NewsItem = {
|
||||
id: string,
|
||||
title: string,
|
||||
summary: string,
|
||||
author_name: string,
|
||||
cover: string,
|
||||
published_at: string,
|
||||
tags: string[]
|
||||
}
|
||||
type Category = {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
banners: [] as Banner[],
|
||||
categories: [
|
||||
{ id: 'all', name: this.$t('title.news') },
|
||||
{ id: 'technology', name: this.$t('title.technology') },
|
||||
{ id: 'sports', name: this.$t('title.sports') },
|
||||
{ id: 'health', name: this.$t('title.health') },
|
||||
{ id: 'entertainment', name: this.$t('title.entertainment') }
|
||||
] as Category[],
|
||||
currentCategory: 'all',
|
||||
newsList: [] as NewsItem[],
|
||||
refresherTriggered: false,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchBanners();
|
||||
this.fetchNewsList();
|
||||
},
|
||||
methods: {
|
||||
fetchBanners() {
|
||||
// TODO: 替换为实际API
|
||||
this.banners = [
|
||||
{ cover: 'https://example.com/banner1.jpg', title: 'AI科技突破', id: 'b1' },
|
||||
{ cover: 'https://example.com/banner2.jpg', title: '体育赛事速递', id: 'b2' }
|
||||
];
|
||||
},
|
||||
fetchNewsList() {
|
||||
this.loading = true;
|
||||
// TODO: 替换为实际API,按currentCategory过滤
|
||||
setTimeout(() => {
|
||||
this.newsList = [
|
||||
{
|
||||
id: 'n1',
|
||||
title: 'AI助力医疗新进展',
|
||||
summary: '人工智能正在改变医疗行业,提升诊断效率。',
|
||||
author_name: '张三',
|
||||
cover: 'https://example.com/news1.jpg',
|
||||
published_at: '2025-06-29',
|
||||
tags: ['AI', '医疗']
|
||||
},
|
||||
{
|
||||
id: 'n2',
|
||||
title: '全球体育赛事回顾',
|
||||
summary: '本周全球体育赛事精彩纷呈,亮点不断。',
|
||||
author_name: '李四',
|
||||
cover: 'https://example.com/news2.jpg',
|
||||
published_at: '2025-06-28',
|
||||
tags: ['体育']
|
||||
}
|
||||
];
|
||||
this.loading = false;
|
||||
this.refresherTriggered = false;
|
||||
}, 800);
|
||||
},
|
||||
switchCategory(catId: string) {
|
||||
this.currentCategory = catId;
|
||||
this.fetchNewsList();
|
||||
},
|
||||
goDetail(news: NewsItem, idx: number) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mt/detail.uvue?id=${news.id}`
|
||||
});
|
||||
},
|
||||
onBannerClick(item: Banner) {
|
||||
// 可跳转到专题或详情
|
||||
uni.navigateTo({
|
||||
url: `/pages/mt/detail.uvue?id=${item.id}`
|
||||
});
|
||||
},
|
||||
onRefresh() {
|
||||
this.refresherTriggered = true;
|
||||
this.fetchNewsList();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.index-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
.banner-swiper {
|
||||
width: 100vw;
|
||||
height: 180px;
|
||||
}
|
||||
.banner-img {
|
||||
width: 100vw;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.banner-title {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
.category-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.tab-item {
|
||||
margin-right: 18px;
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
padding: 6px 10px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.tab-item.active {
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
.news-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #fff;
|
||||
margin: 10px 10px 0 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
||||
overflow: hidden;
|
||||
}
|
||||
.news-cover {
|
||||
width: 110px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
.news-body {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.news-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #222;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.news-summary {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.news-meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.news-author {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.news-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.news-tag {
|
||||
background: #f0f0f0;
|
||||
color: #007aff;
|
||||
font-size: 11px;
|
||||
border-radius: 10px;
|
||||
padding: 2px 8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.loading-text, .empty-text {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
font-size: 14px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media (min-width: 900px) {
|
||||
.index-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.banner-swiper, .banner-img {
|
||||
width: 900px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
218
pages/mt/list-news.uvue
Normal file
218
pages/mt/list-news.uvue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<list-view style="flex: 1;" refresher-enabled=true @refresherrefresh="onRefresherrefresh"
|
||||
:refresher-triggered="refresherTriggered" enable-back-to-top="true">
|
||||
<list-item class="banner" @click="bannerClick(banner)" type=1>
|
||||
<image class="banner-img" :src="banner.cover"></image>
|
||||
<text class="banner-title">{{ banner.title }}</text>
|
||||
</list-item>
|
||||
<sticky-header>
|
||||
<view
|
||||
style="width: 100%;height:44px;background-color: #f5f5f5;flex-direction: row;justify-content:center;align-items:center;">
|
||||
<text v-for="(name,index) in th_item" :key="index" @click="clickTH(index)" style="margin-right: 20px;">
|
||||
{{name}}
|
||||
</text>
|
||||
</view>
|
||||
</sticky-header>
|
||||
<list-item v-for="(value, index) in listData" :key="index" type=2>
|
||||
<view class="uni-list-cell" hover-class="uni-list-cell-hover" @click="goDetail(value, index)">
|
||||
<view class="uni-media-list">
|
||||
<share-element :share-key="'image_'+index">
|
||||
<image class="uni-media-list-logo" :src="value.cover"></image>
|
||||
</share-element>
|
||||
<view class="uni-media-list-body">
|
||||
<text class="uni-media-list-text-top">{{ value.title }}</text>
|
||||
<view class="uni-media-list-text-bottom">
|
||||
<text class="uni-media-list-text">{{ value.author_name }}</text>
|
||||
<text class="uni-media-list-text">{{ value.published_at }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</list-item>
|
||||
</list-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
type Banner = {
|
||||
cover : string | null,
|
||||
title : string | null,
|
||||
post_id : string | null
|
||||
}
|
||||
type Item = {
|
||||
author_name : string,
|
||||
cover : string,
|
||||
id : number,
|
||||
post_id : string,
|
||||
published_at : string,
|
||||
title : string
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
th_item: ["排序", "筛选"],
|
||||
refresherTriggered: false,
|
||||
banner: {} as Banner,
|
||||
listData: [] as Item[],
|
||||
last_id: '',
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.getBanner();
|
||||
this.getList();
|
||||
},
|
||||
onUnload() {
|
||||
},
|
||||
methods: {
|
||||
getBanner() {
|
||||
let data = {
|
||||
column: 'id,post_id,title,author_name,cover,published_at' //需要的字段名
|
||||
};
|
||||
uni.request<Banner>({
|
||||
url: 'https://unidemo.dcloud.net.cn/api/banner/36kr',
|
||||
data: data,
|
||||
success: data => {
|
||||
this.refresherTriggered = false
|
||||
if (data.statusCode == 200) {
|
||||
const result = data.data
|
||||
if (result != null) {
|
||||
this.banner = result;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log('fail', e);
|
||||
}
|
||||
});
|
||||
},
|
||||
getList() {
|
||||
let url = "https://unidemo.dcloud.net.cn/api/news?column=id,post_id,title,author_name,cover,published_at";
|
||||
/* if (this.last_id != "") {
|
||||
const minId = parseInt((this.last_id))
|
||||
const time = new Date().getTime() + '';
|
||||
const pageSize = 10;
|
||||
url = url + "&minId=" + minId + "&time=" + time + "&pageSize=" + pageSize;
|
||||
} */
|
||||
|
||||
uni.request<Item[]>({
|
||||
url: url,
|
||||
method: "GET",
|
||||
success: (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
console.log(res);
|
||||
const result = res.data
|
||||
if (result != null) {
|
||||
this.listData = result //因本接口没有更多分页数据,所以重新赋值。正常有分页的列表应该如下面push方式增加数组项
|
||||
// this.listData.push(...result)
|
||||
// this.last_id = this.listData[0].id + "";
|
||||
}
|
||||
this.refresherTriggered = false;
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log('fail', res);
|
||||
this.refresherTriggered = false
|
||||
}
|
||||
});
|
||||
},
|
||||
goDetail(e : Item, key : number) {
|
||||
const detail = e;
|
||||
const post_id = detail.post_id;
|
||||
let cover = detail.cover;
|
||||
// #ifdef APP-ANDROID
|
||||
cover = btoa(cover);
|
||||
// #endif
|
||||
const title = detail.title;
|
||||
uni.navigateTo({
|
||||
url: '/pages/template/list-news/detail/detail?post_id=' + post_id + "&cover=" + cover + "&title=" + title +"&shareKey=image_"+key
|
||||
});
|
||||
},
|
||||
bannerClick(e : Banner) {
|
||||
const detail = e;
|
||||
const post_id = detail.post_id;
|
||||
let cover = detail.cover ?? "";
|
||||
// #ifdef APP-ANDROID
|
||||
cover = btoa(cover);
|
||||
// #endif
|
||||
const title = detail.title;
|
||||
uni.navigateTo({
|
||||
url: '/pages/template/list-news/detail/detail?post_id=' + post_id + "&cover=" + cover + "&title=" + title
|
||||
});
|
||||
},
|
||||
clickTH(index : number) {
|
||||
uni.showModal({
|
||||
content: "点击表头项:" + index,
|
||||
showCancel: false
|
||||
});
|
||||
},
|
||||
onRefresherrefresh() {
|
||||
this.refresherTriggered = true
|
||||
this.getBanner();
|
||||
this.getList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.banner {
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.banner-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
max-height: 42px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
bottom: 15px;
|
||||
width: 90%;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.uni-media-list {
|
||||
padding: 11px 15px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-media-list-logo {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.uni-media-list-body {
|
||||
flex: 1;
|
||||
padding-left: 7px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.uni-media-list-text-top {
|
||||
/* height: 37px; */
|
||||
font-size: 14px;
|
||||
lines: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-media-list-text-bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.uni-media-list-text {
|
||||
color: #9D9D9F;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
41
pages/mt/search.uvue
Normal file
41
pages/mt/search.uvue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view :class="['mt-search', isLargeScreen ? 'large' : 'small']">
|
||||
<view class="mt-search-bar">
|
||||
<input v-model="keyword" :placeholder="t('mt.search.placeholder')" @confirm="onSearch" />
|
||||
<button @tap="onSearch">{{ t('mt.search.button') }}</button>
|
||||
</view>
|
||||
<scroll-view>
|
||||
<view v-for="item in searchResults" :key="item.id" class="mt-search-item" @tap="goToDetail(item.id)">
|
||||
<text>{{ item.title }}</text>
|
||||
<text class="mt-summary">{{ item.summary }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { searchNews } from '@/composables/use-news'
|
||||
|
||||
const keyword = ref('')
|
||||
const searchResults = ref([])
|
||||
|
||||
function onSearch() {
|
||||
searchNews(keyword.value).then(res => {
|
||||
searchResults.value = res
|
||||
})
|
||||
}
|
||||
function goToDetail(id: string) {
|
||||
uni.navigateTo({ url: `/pages/mt/detail.uvue?id=${id}` })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-search {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-search-bar { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
|
||||
.mt-search-item { padding: 16rpx 0; border-bottom: 1px solid #eee; }
|
||||
.mt-summary { color: #888; font-size: 24rpx; }
|
||||
}
|
||||
</style>
|
||||
49
pages/mt/settings.uvue
Normal file
49
pages/mt/settings.uvue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<view :class="['mt-settings', isLargeScreen ? 'large' : 'small']">
|
||||
<text class="mt-title">{{ t('mt.settings.title') }}</text>
|
||||
<view class="mt-setting-item">
|
||||
<text>{{ t('mt.settings.language') }}</text>
|
||||
<picker :range="languages" v-model="currentLang" @change="onLangChange" />
|
||||
</view>
|
||||
<view class="mt-setting-item">
|
||||
<text>{{ t('mt.settings.theme') }}</text>
|
||||
<switch v-model="isDark" @change="onThemeChange" />
|
||||
</view>
|
||||
<!-- admin入口 -->
|
||||
<button v-if="isAdmin" @tap="goToAdmin">{{ t('mt.settings.admin') }}</button>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { getUserRole } from '@/composables/use-auth'
|
||||
|
||||
const languages = ref(['zh-CN', 'en', 'ja'])
|
||||
const currentLang = ref('zh-CN')
|
||||
const isDark = ref(false)
|
||||
const isAdmin = ref(false)
|
||||
|
||||
onLoad(() => {
|
||||
// 加载用户设置
|
||||
isAdmin.value = getUserRole() === 'admin'
|
||||
})
|
||||
|
||||
function onLangChange(e) {
|
||||
// 切换语言逻辑
|
||||
}
|
||||
function onThemeChange(e) {
|
||||
// 切换主题逻辑
|
||||
}
|
||||
function goToAdmin() {
|
||||
uni.navigateTo({ url: '/pages/mt/admin.uvue' })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-settings {
|
||||
padding: 32rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-title { font-size: 40rpx; font-weight: bold; margin-bottom: 32rpx; }
|
||||
.mt-setting-item { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
|
||||
}
|
||||
</style>
|
||||
57
pages/mt/topic-detail.uvue
Normal file
57
pages/mt/topic-detail.uvue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<view :class="['mt-topic-detail', isLargeScreen ? 'large' : 'small']">
|
||||
<image :src="topic.cover_image_url" class="mt-topic-cover" />
|
||||
<text class="mt-topic-title">{{ topic.title }}</text>
|
||||
<text class="mt-topic-desc">{{ topic.description }}</text>
|
||||
<view class="mt-topic-actions">
|
||||
<button @tap="onSubscribe">{{ t('mt.topic.subscribe') }}</button>
|
||||
<button @tap="onFavorite">{{ t('mt.topic.favorite') }}</button>
|
||||
<button @tap="onShare">{{ t('mt.topic.share') }}</button>
|
||||
</view>
|
||||
<view class="mt-topic-contents">
|
||||
<text class="mt-topic-section">{{ t('mt.topic.contents') }}</text>
|
||||
<view v-for="item in topic.contents" :key="item.id" class="mt-topic-content-item" @tap="goToDetail(item.id)">
|
||||
<text>{{ item.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref, onLoad } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { getTopicDetail } from '@/composables/use-topics'
|
||||
|
||||
const topic = ref({ contents: [] })
|
||||
|
||||
onLoad((params) => {
|
||||
if (params.id) {
|
||||
getTopicDetail(params.id).then(res => { topic.value = res })
|
||||
}
|
||||
})
|
||||
|
||||
function onSubscribe() {
|
||||
// 订阅逻辑
|
||||
}
|
||||
function onFavorite() {
|
||||
// 收藏逻辑
|
||||
}
|
||||
function onShare() {
|
||||
// 分享逻辑
|
||||
}
|
||||
function goToDetail(id: string) {
|
||||
uni.navigateTo({ url: `/pages/mt/detail.uvue?id=${id}` })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-topic-detail {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-topic-cover { width: 100%; height: 240rpx; border-radius: 16rpx; margin-bottom: 24rpx; }
|
||||
.mt-topic-title { font-size: 40rpx; font-weight: bold; margin-bottom: 12rpx; }
|
||||
.mt-topic-desc { color: #888; font-size: 28rpx; margin-bottom: 24rpx; }
|
||||
.mt-topic-actions { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
|
||||
.mt-topic-section { font-size: 32rpx; font-weight: 500; margin-bottom: 12rpx; }
|
||||
.mt-topic-content-item { padding: 12rpx 0; border-bottom: 1px solid #eee; }
|
||||
}
|
||||
</style>
|
||||
42
pages/mt/topics.uvue
Normal file
42
pages/mt/topics.uvue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<view :class="['mt-topics', isLargeScreen ? 'large' : 'small']">
|
||||
<text class="mt-title">{{ t('mt.topics.title') }}</text>
|
||||
<scroll-view>
|
||||
<view v-for="topic in topics" :key="topic.id" class="mt-topic-item" @tap="goToTopic(topic.id)">
|
||||
<image :src="topic.cover_image_url" class="mt-topic-cover" />
|
||||
<view class="mt-topic-info">
|
||||
<text class="mt-topic-title">{{ topic.title }}</text>
|
||||
<text class="mt-topic-desc">{{ topic.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { ref, onLoad } from 'uni-app'
|
||||
import { t } from '@/i18n/mt'
|
||||
import { isLargeScreen } from '@/utils/responsive'
|
||||
import { getTopics } from '@/composables/use-topics'
|
||||
|
||||
const topics = ref([])
|
||||
|
||||
onLoad(() => {
|
||||
getTopics().then(res => { topics.value = res })
|
||||
})
|
||||
|
||||
function goToTopic(id: string) {
|
||||
uni.navigateTo({ url: `/pages/mt/topic-detail.uvue?id=${id}` })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-topics {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-title { font-size: 40rpx; font-weight: bold; margin-bottom: 24rpx; }
|
||||
.mt-topic-item { display: flex; gap: 16rpx; margin-bottom: 24rpx; align-items: center; }
|
||||
.mt-topic-cover { width: 120rpx; height: 120rpx; border-radius: 12rpx; }
|
||||
.mt-topic-info { flex: 1; }
|
||||
.mt-topic-title { font-size: 32rpx; font-weight: 500; }
|
||||
.mt-topic-desc { color: #888; font-size: 24rpx; }
|
||||
}
|
||||
</style>
|
||||
51
pages/mt/video.uvue
Normal file
51
pages/mt/video.uvue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<view :class="['mt-video', isLargeScreen ? 'large' : 'small']">
|
||||
<video :src="videoUrl" controls class="mt-video-player" />
|
||||
<text class="mt-video-title">{{ videoTitle }}</text>
|
||||
<view class="mt-video-actions">
|
||||
<button @tap="onFavorite">{{ $t('mt.video.favorite') }}</button>
|
||||
<button @tap="onShare">{{ $t('mt.video.share') }}</button>
|
||||
<button @tap="onLike">{{ $t('mt.video.like') }}</button>
|
||||
</view>
|
||||
<view class="mt-video-desc">{{ videoDesc }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import { responsiveState } from '@/utils/utils'
|
||||
import { ChatDataService } from '@/utils/chatDataService.uts'
|
||||
|
||||
const videoUrl = ref('')
|
||||
const videoTitle = ref('')
|
||||
const videoDesc = ref('')
|
||||
|
||||
|
||||
|
||||
function onFavorite() {
|
||||
// 收藏逻辑
|
||||
}
|
||||
function onShare() {
|
||||
// 分享逻辑
|
||||
}
|
||||
function onLike() {
|
||||
// 点赞逻辑
|
||||
}
|
||||
onLoad((params) => {
|
||||
if (params.url) {
|
||||
const u = params.url as string
|
||||
// resolve to cached local path; fallback to url if fails
|
||||
;(async () => { videoUrl.value = await ChatDataService.resolveVideoSource(u) })()
|
||||
}
|
||||
if (params.title) videoTitle.value = params.title
|
||||
if (params.desc) videoDesc.value = params.desc
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mt-video {
|
||||
padding: 24rpx;
|
||||
&.large { max-width: 900rpx; margin: 0 auto; }
|
||||
.mt-video-player { width: 100%; height: 400rpx; border-radius: 12rpx; margin-bottom: 24rpx; }
|
||||
.mt-video-title { font-size: 36rpx; font-weight: bold; margin-bottom: 12rpx; }
|
||||
.mt-video-actions { display: flex; gap: 16rpx; margin-bottom: 16rpx; }
|
||||
.mt-video-desc { color: #888; font-size: 26rpx; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user