Commit ed1df273 by 吴春元

提交

parents
unpackage
node_modules
.hbuilderx
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/emergency-app.iml" filepath="$PROJECT_DIR$/.idea/emergency-app.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="e6b5ebd7-6e68-4ea7-96fc-deb6be1cf064" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectId" id="2syJVkZnfHDKjALs5jpTkkHI9zT" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="RunOnceActivity.cidr.known.project.marker" value="true" />
<property name="android.project.structure.last.selected" value="SDK Location" />
<property name="android.project.structure.proportion" value="0.15" />
<property name="android.sdk.path" value="$USER_HOME$/Library/Android/sdk" />
<property name="cidr.known.project.marker" value="true" />
<property name="dart.analysis.tool.window.visible" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="settings.editor.selected.configurable" value="AndroidSdkUpdater" />
<property name="show.migrate.to.gradle.popup" value="false" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e6b5ebd7-6e68-4ea7-96fc-deb6be1cf064" name="Changes" comment="" />
<created>1739421911220</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1739421911220</updated>
</task>
<servers />
</component>
</project>
\ No newline at end of file
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
globalData: {
tabIndex: 0
}
}
</script>
<style lang="scss">
@import 'tailwindcss/base';
@import 'tailwindcss/utilities';
</style>
\ No newline at end of file
# uni-app-vue3-tailwind-hbuilder-template
- [uni-app-vue3-tailwind-hbuilder-template](#uni-app-vue3-tailwind-hbuilder-template)
- [官方文档](#官方文档)
- [HbuilderX 智能提示工具](#hbuilderx-智能提示工具)
- [Related projects](#related-projects)
- [插件核心](#插件核心)
- [模板 template](#模板-template)
- [CLI 工具](#cli-工具)
- [Bugs \& Issues](#bugs--issues)
这是一个使用 `hbuilderx` + `uni-app` + `vue3` + `tailwind` 构建的小程序模板。可以直接在 `hbuilderx` 中导入运行。
## 官方文档
<https://weapp-tw.icebreaker.top/>
## HbuilderX 智能提示工具
DCloud-HBuilderX团队提供了对应的插件,可以去
<https://ext.dcloud.net.cn/plugin?id=8560> 进行下载,即可产生智能提示。
## Related projects
### 插件核心
[weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss) 提供转义功能,欢迎 `fork`/`star`
### 模板 template
<https://weapp-tw.icebreaker.top/docs/community/templates>
### CLI 工具
[weapp-ide-cli](https://github.com/sonofmagic/utils/tree/main/packages/weapp-ide-cli): 一个微信开发者工具命令行,快速方便的直接启动 ide 进行登录,开发,预览,上传代码等等功能。
## Bugs & Issues
目前这个插件正在快速的开发中,如果遇到 `Bug` 或者想提出 `Issue`
[欢迎提交到此处](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues)
<template>
<!--增加audio标签支持-->
<audio
:id="node.attr.id"
:class="node.classStr"
:style="node.styleStr"
:src="node.attr.src"
:loop="node.attr.loop"
:poster="node.attr.poster"
:name="node.attr.name"
:author="node.attr.author"
controls></audio>
</template>
<script>
export default {
name: 'wxParseAudio',
props: {
node: {
type: Object,
default() {
return {};
},
},
},
};
</script>
<template>
<image
:mode="node.attr.mode"
:lazy-load="node.attr.lazyLoad"
:class="node.classStr"
:style="newStyleStr || node.styleStr"
:data-src="node.attr.src"
:src="node.attr.src"
@tap="wxParseImgTap"
@load="wxParseImgLoad"
/>
</template>
<script>
export default {
name: 'wxParseImg',
data() {
return {
newStyleStr: '',
preview: true,
};
},
props: {
node: {
type: Object,
default() {
return {};
},
},
},
methods: {
wxParseImgTap(e) {
if (!this.preview) return;
const { src } = e.currentTarget.dataset;
if (!src) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
parent = parent.$parent;
}
parent.preview(src, e);
},
// 图片视觉宽高计算函数区
wxParseImgLoad(e) {
const { src } = e.currentTarget.dataset;
if (!src) return;
const { width, height } = e.mp.detail;
const recal = this.wxAutoImageCal(width, height);
const { imageheight, imageWidth } = recal;
const { padding, mode } = this.node.attr;
const { styleStr } = this.node;
const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px; padding: 0 ${+padding}px;`;
},
// 计算视觉优先的图片宽高
wxAutoImageCal(originalWidth, originalHeight) {
// 获取图片的原始长宽
const { padding } = this.node.attr;
const windowWidth = this.node.$screen.width - (2 * padding);
const results = {};
if (originalWidth < 60 || originalHeight < 60) {
const { src } = this.node.attr;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.removeImageUrl(src);
this.preview = false;
}
// 判断按照那种方式进行缩放
if (originalWidth > windowWidth) {
// 在图片width大于手机屏幕width时候
results.imageWidth = windowWidth;
results.imageheight = windowWidth * (originalHeight / originalWidth);
} else {
// 否则展示原来的数据
results.imageWidth = originalWidth;
results.imageheight = originalHeight;
}
return results;
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--table类型-->
<block v-else-if="node.tag == 'table'">
<view :class="node.classStr" class="table" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate1';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate0',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;// TODO currentTarget才有dataset
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view :class="(node.tag == 'li' ? node.classStr : (node.node==='text'?'text':''))">
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<!-- <view :class="node.classStr" :style="node.styleStr"> -->
<view :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate2';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate1',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate11';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate10',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
{{node.text}}
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
{{node.text}}
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
{{node.text}}
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate11',
props: {
node: {},
},
components: {
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate3';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate2',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate4';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate3',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate5';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate4',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate6';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate5',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate7';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate6',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate8';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate7',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate9';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate8',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<view>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<block v-if="node.tag == 'button'">
<button type="default" size="mini">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</button>
</block>
<!--li类型-->
<block v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--video类型-->
<block v-else-if="node.tag == 'video'">
<wx-parse-video :node="node" />
</block>
<!--audio类型-->
<block v-else-if="node.tag == 'audio'">
<wx-parse-audio :node="node" />
</block>
<!--img类型-->
<block v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</block>
<!--a类型-->
<block v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--br类型-->
<block v-else-if="node.tag == 'br'">
<text>\n</text>
</block>
<!--其他标签-->
<block v-else>
<view :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{node.text}}</block>
</view>
</template>
<script>
import wxParseTemplate from './wxParseTemplate10';
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
export default {
name: 'wxParseTemplate9',
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<template>
<!--增加video标签支持,并循环添加-->
<view :class="node.classStr" :style="node.styleStr">
<video :class="node.classStr" class="video-video" :src="node.attr.src"></video>
</view>
</template>
<script>
export default {
name: 'wxParseVideo',
props: {
node: {},
},
};
</script>
/**
* html2Json 改造来自: https://github.com/Jxck/html2json
*
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
import wxDiscode from './wxDiscode';
import HTMLParser from './htmlparser';
function makeMap(str) {
const obj = {};
const items = str.split(',');
for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
return obj;
}
// Block Elements - HTML 5
const block = makeMap('br,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
// Inline Elements - HTML 5
const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
// Elements that you can, intentionally, leave open
// (and which close themselves)
const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
function removeDOCTYPE(html) {
const isDocument = /<body.*>([^]*)<\/body>/.test(html);
return isDocument ? RegExp.$1 : html;
}
function trimHtml(html) {
return html
.replace(/<!--.*?-->/gi, '')
.replace(/\/\*.*?\*\//gi, '')
.replace(/[ ]+</gi, '<')
.replace(/<script[^]*<\/script>/gi, '')
.replace(/<style[^]*<\/style>/gi, '');
}
function getScreenInfo() {
const screen = {};
wx.getSystemInfo({
success: (res) => {
screen.width = res.windowWidth;
screen.height = res.windowHeight;
},
});
return screen;
}
function html2json(html, customHandler, imageProp, host) {
// 处理字符串
html = removeDOCTYPE(html);
html = trimHtml(html);
html = wxDiscode.strDiscode(html);
// 生成node节点
const bufArray = [];
const results = {
nodes: [],
imageUrls: [],
};
const screen = getScreenInfo();
function Node(tag) {
this.node = 'element';
this.tag = tag;
this.$screen = screen;
}
HTMLParser(html, {
start(tag, attrs, unary) {
// node for this element
const node = new Node(tag);
if (bufArray.length !== 0) {
const parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
}
if (block[tag]) {
node.tagType = 'block';
} else if (inline[tag]) {
node.tagType = 'inline';
} else if (closeSelf[tag]) {
node.tagType = 'closeSelf';
}
node.attr = attrs.reduce((pre, attr) => {
const { name } = attr;
let { value } = attr;
if (name === 'class') {
node.classStr = value;
}
// has multi attibutes
// make it array of attribute
if (name === 'style') {
node.styleStr = value;
}
if (value.match(/ /)) {
value = value.split(' ');
}
// if attr already exists
// merge it
if (pre[name]) {
if (Array.isArray(pre[name])) {
// already array, push to last
pre[name].push(value);
} else {
// single value, make it array
pre[name] = [pre[name], value];
}
} else {
// not exist, put it
pre[name] = value;
}
return pre;
}, {});
// 优化样式相关属性
if (node.classStr) {
node.classStr += ` ${node.tag}`;
} else {
node.classStr = node.tag;
}
if (node.tagType === 'inline') {
node.classStr += ' inline';
}
// 对img添加额外数据
if (node.tag === 'img') {
let imgUrl = node.attr.src;
imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
Object.assign(node.attr, imageProp, {
src: imgUrl || '',
});
if (imgUrl) {
results.imageUrls.push(imgUrl);
}
}
// 处理a标签属性
if (node.tag === 'a') {
node.attr.href = node.attr.href || '';
}
// 处理font标签样式属性
if (node.tag === 'font') {
const fontSize = [
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'-webkit-xxx-large',
];
const styleAttrs = {
color: 'color',
face: 'font-family',
size: 'font-size',
};
if (!node.styleStr) node.styleStr = '';
Object.keys(styleAttrs).forEach((key) => {
if (node.attr[key]) {
const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
node.styleStr += `${styleAttrs[key]}: ${value};`;
}
});
}
// 临时记录source资源
if (node.tag === 'source') {
results.source = node.attr.src;
}
if (customHandler.start) {
customHandler.start(node, results);
}
if (unary) {
// if this tag doesn't have end tag
// like <img src="hoge.png"/>
// add to parents
const parent = bufArray[0] || results;
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
} else {
bufArray.unshift(node);
}
},
end(tag) {
// merge into parent tag
const node = bufArray.shift();
if (node.tag !== tag) {
console.error('invalid state: mismatch end tag');
}
// 当有缓存source资源时于于video补上src资源
if (node.tag === 'video' && results.source) {
node.attr.src = results.source;
delete results.source;
}
if (customHandler.end) {
customHandler.end(node, results);
}
if (bufArray.length === 0) {
results.nodes.push(node);
} else {
const parent = bufArray[0];
if (!parent.nodes) {
parent.nodes = [];
}
parent.nodes.push(node);
}
},
chars(text) {
if (!text.trim()) return;
const node = {
node: 'text',
text,
};
if (customHandler.chars) {
customHandler.chars(node, results);
}
if (bufArray.length === 0) {
results.nodes.push(node);
} else {
const parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
}
},
});
return results;
}
export default html2json;
/**
*
* htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
// Regular Expressions for parsing tags and attributes
const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
function makeMap(str) {
const obj = {};
const items = str.split(',');
for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
return obj;
}
// Empty Elements - HTML 5
const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
// Block Elements - HTML 5
const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
// Inline Elements - HTML 5
const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
// Elements that you can, intentionally, leave open
// (and which close themselves)
const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
// Attributes that have their values filled in disabled="disabled"
const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
function HTMLParser(html, handler) {
let index;
let chars;
let match;
let last = html;
const stack = [];
stack.last = () => stack[stack.length - 1];
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
let pos;
if (!tagName) {
pos = 0;
} else {
// Find the closest opened tag of the same type
tagName = tagName.toLowerCase();
for (pos = stack.length - 1; pos >= 0; pos -= 1) {
if (stack[pos] === tagName) break;
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (let i = stack.length - 1; i >= pos; i -= 1) {
if (handler.end) handler.end(stack[i]);
}
// Remove the open elements from the stack
stack.length = pos;
}
}
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() === tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) stack.push(tagName);
if (handler.start) {
const attrs = [];
rest.replace(attr, function genAttr(matches, name) {
const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
attrs.push({
name,
value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
while (html) {
chars = true;
if (html.indexOf('</') === 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
}
// start tag
} else if (html.indexOf('<') === 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
let text = '';
while (index === 0) {
text += '<';
html = html.substring(1);
index = html.indexOf('<');
}
text += index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) handler.chars(text);
}
if (html === last) throw new Error(`Parse Error: ${html}`);
last = html;
}
// Clean up any remaining tags
parseEndTag();
}
export default HTMLParser;
// HTML 支持的数学符号
function strNumDiscode(str) {
str = str.replace(/&forall;/g, '∀');
str = str.replace(/&part;/g, '∂');
str = str.replace(/&exist;/g, '∃');
str = str.replace(/&empty;/g, '∅');
str = str.replace(/&nabla;/g, '∇');
str = str.replace(/&isin;/g, '∈');
str = str.replace(/&notin;/g, '∉');
str = str.replace(/&ni;/g, '∋');
str = str.replace(/&prod;/g, '∏');
str = str.replace(/&sum;/g, '∑');
str = str.replace(/&minus;/g, '−');
str = str.replace(/&lowast;/g, '∗');
str = str.replace(/&radic;/g, '√');
str = str.replace(/&prop;/g, '∝');
str = str.replace(/&infin;/g, '∞');
str = str.replace(/&ang;/g, '∠');
str = str.replace(/&and;/g, '∧');
str = str.replace(/&or;/g, '∨');
str = str.replace(/&cap;/g, '∩');
str = str.replace(/&cup;/g, '∪');
str = str.replace(/&int;/g, '∫');
str = str.replace(/&there4;/g, '∴');
str = str.replace(/&sim;/g, '∼');
str = str.replace(/&cong;/g, '≅');
str = str.replace(/&asymp;/g, '≈');
str = str.replace(/&ne;/g, '≠');
str = str.replace(/&le;/g, '≤');
str = str.replace(/&ge;/g, '≥');
str = str.replace(/&sub;/g, '⊂');
str = str.replace(/&sup;/g, '⊃');
str = str.replace(/&nsub;/g, '⊄');
str = str.replace(/&sube;/g, '⊆');
str = str.replace(/&supe;/g, '⊇');
str = str.replace(/&oplus;/g, '⊕');
str = str.replace(/&otimes;/g, '⊗');
str = str.replace(/&perp;/g, '⊥');
str = str.replace(/&sdot;/g, '⋅');
return str;
}
// HTML 支持的希腊字母
function strGreeceDiscode(str) {
str = str.replace(/&Alpha;/g, 'Α');
str = str.replace(/&Beta;/g, 'Β');
str = str.replace(/&Gamma;/g, 'Γ');
str = str.replace(/&Delta;/g, 'Δ');
str = str.replace(/&Epsilon;/g, 'Ε');
str = str.replace(/&Zeta;/g, 'Ζ');
str = str.replace(/&Eta;/g, 'Η');
str = str.replace(/&Theta;/g, 'Θ');
str = str.replace(/&Iota;/g, 'Ι');
str = str.replace(/&Kappa;/g, 'Κ');
str = str.replace(/&Lambda;/g, 'Λ');
str = str.replace(/&Mu;/g, 'Μ');
str = str.replace(/&Nu;/g, 'Ν');
str = str.replace(/&Xi;/g, 'Ν');
str = str.replace(/&Omicron;/g, 'Ο');
str = str.replace(/&Pi;/g, 'Π');
str = str.replace(/&Rho;/g, 'Ρ');
str = str.replace(/&Sigma;/g, 'Σ');
str = str.replace(/&Tau;/g, 'Τ');
str = str.replace(/&Upsilon;/g, 'Υ');
str = str.replace(/&Phi;/g, 'Φ');
str = str.replace(/&Chi;/g, 'Χ');
str = str.replace(/&Psi;/g, 'Ψ');
str = str.replace(/&Omega;/g, 'Ω');
str = str.replace(/&alpha;/g, 'α');
str = str.replace(/&beta;/g, 'β');
str = str.replace(/&gamma;/g, 'γ');
str = str.replace(/&delta;/g, 'δ');
str = str.replace(/&epsilon;/g, 'ε');
str = str.replace(/&zeta;/g, 'ζ');
str = str.replace(/&eta;/g, 'η');
str = str.replace(/&theta;/g, 'θ');
str = str.replace(/&iota;/g, 'ι');
str = str.replace(/&kappa;/g, 'κ');
str = str.replace(/&lambda;/g, 'λ');
str = str.replace(/&mu;/g, 'μ');
str = str.replace(/&nu;/g, 'ν');
str = str.replace(/&xi;/g, 'ξ');
str = str.replace(/&omicron;/g, 'ο');
str = str.replace(/&pi;/g, 'π');
str = str.replace(/&rho;/g, 'ρ');
str = str.replace(/&sigmaf;/g, 'ς');
str = str.replace(/&sigma;/g, 'σ');
str = str.replace(/&tau;/g, 'τ');
str = str.replace(/&upsilon;/g, 'υ');
str = str.replace(/&phi;/g, 'φ');
str = str.replace(/&chi;/g, 'χ');
str = str.replace(/&psi;/g, 'ψ');
str = str.replace(/&omega;/g, 'ω');
str = str.replace(/&thetasym;/g, 'ϑ');
str = str.replace(/&upsih;/g, 'ϒ');
str = str.replace(/&piv;/g, 'ϖ');
str = str.replace(/&middot;/g, '·');
return str;
}
function strcharacterDiscode(str) {
// 加入常用解析
str = str.replace(/&nbsp;/g, ' ');
str = str.replace(/&ensp;/g, ' ');
str = str.replace(/&emsp;/g, ' ');
str = str.replace(/&quot;/g, "'");
str = str.replace(/&amp;/g, '&');
str = str.replace(/&lt;/g, '<');
str = str.replace(/&gt;/g, '>');
str = str.replace(/&#8226;/g, '•');
return str;
}
// HTML 支持的其他实体
function strOtherDiscode(str) {
str = str.replace(/&OElig;/g, 'Œ');
str = str.replace(/&oelig;/g, 'œ');
str = str.replace(/&Scaron;/g, 'Š');
str = str.replace(/&scaron;/g, 'š');
str = str.replace(/&Yuml;/g, 'Ÿ');
str = str.replace(/&fnof;/g, 'ƒ');
str = str.replace(/&circ;/g, 'ˆ');
str = str.replace(/&tilde;/g, '˜');
str = str.replace(/&ensp;/g, '');
str = str.replace(/&emsp;/g, '');
str = str.replace(/&thinsp;/g, '');
str = str.replace(/&zwnj;/g, '');
str = str.replace(/&zwj;/g, '');
str = str.replace(/&lrm;/g, '');
str = str.replace(/&rlm;/g, '');
str = str.replace(/&ndash;/g, '–');
str = str.replace(/&mdash;/g, '—');
str = str.replace(/&lsquo;/g, '‘');
str = str.replace(/&rsquo;/g, '’');
str = str.replace(/&sbquo;/g, '‚');
str = str.replace(/&ldquo;/g, '“');
str = str.replace(/&rdquo;/g, '”');
str = str.replace(/&bdquo;/g, '„');
str = str.replace(/&dagger;/g, '†');
str = str.replace(/&Dagger;/g, '‡');
str = str.replace(/&bull;/g, '•');
str = str.replace(/&hellip;/g, '…');
str = str.replace(/&permil;/g, '‰');
str = str.replace(/&prime;/g, '′');
str = str.replace(/&Prime;/g, '″');
str = str.replace(/&lsaquo;/g, '‹');
str = str.replace(/&rsaquo;/g, '›');
str = str.replace(/&oline;/g, '‾');
str = str.replace(/&euro;/g, '€');
str = str.replace(/&trade;/g, '™');
str = str.replace(/&larr;/g, '←');
str = str.replace(/&uarr;/g, '↑');
str = str.replace(/&rarr;/g, '→');
str = str.replace(/&darr;/g, '↓');
str = str.replace(/&harr;/g, '↔');
str = str.replace(/&crarr;/g, '↵');
str = str.replace(/&lceil;/g, '⌈');
str = str.replace(/&rceil;/g, '⌉');
str = str.replace(/&lfloor;/g, '⌊');
str = str.replace(/&rfloor;/g, '⌋');
str = str.replace(/&loz;/g, '◊');
str = str.replace(/&spades;/g, '♠');
str = str.replace(/&clubs;/g, '♣');
str = str.replace(/&hearts;/g, '♥');
str = str.replace(/&diams;/g, '♦');
str = str.replace(/&#39;/g, "'");
return str;
}
function strDiscode(str) {
str = strNumDiscode(str);
str = strGreeceDiscode(str);
str = strcharacterDiscode(str);
str = strOtherDiscode(str);
return str;
}
function urlToHttpUrl(url, domain) {
if (/^\/\//.test(url)) {
return `https:${url}`;
} else if (/^\//.test(url)) {
return `https://${domain}${url}`;
}
return url;
}
export default {
strDiscode,
urlToHttpUrl,
};
## uParse 适用于 uni-app/mpvue 的富文本解析组件
> 支持 Html、Markdown 解析,Fork自: [mpvue-wxParse](https://github.com/F-loat/mpvue-wxParse)
## 属性
| 名称 | 类型 | 默认值 | 描述 |
| -----------------|--------------- | ------------- | ---------------- |
| loading | Boolean | false | 数据加载状态 |
| className | String | — | 自定义 class 名称 |
| content | String | — | 渲染内容 |
| noData | String | 数据不能为空 | 空数据时的渲染展示 |
| startHandler | Function | 见源码 | 自定义 parser 函数 |
| endHandler | Function | null | 自定义 parser 函数 |
| charsHandler | Function | null | 自定义 parser 函数 |
| imageProp | Object | 见下文 | 图片相关参数 |
### 自定义 parser 函数具体介绍
* 传入的参数为当前节点 `node` 对象及解析结果 `results` 对象,例如 `startHandler(node, results)`
* 无需返回值,通过对传入的参数直接操作来完成需要的改动
* 自定义函数会在原解析函数处理之后执行
### imageProp 对象具体属性
| 名称 | 类型 | 默认值 | 描述 |
| -----------------|--------------- | ------------- | ------------------ |
| mode | String | 'aspectFit' | 图片裁剪、缩放的模式 |
| padding | Number | 0 | 图片内边距 |
| lazyLoad | Boolean | false | 图片懒加载 |
| domain | String | '' | 图片服务域名 |
## 事件
| 名称 | 参数 | 描述 |
| -----------------|----------------- | ---------------- |
| preview | 图片地址,原始事件 | 预览图片时触发 |
| navigate | 链接地址,原始事件 | 点击链接时触发 |
## 基本使用方法
``` vue
<template>
<div>
<u-parse :content="article" @preview="preview" @navigate="navigate" />
</div>
</template>
<script>
import uParse from '@/components/u-parse/u-parse.vue'
export default {
components: {
uParse
},
data () {
return {
article: '<div>我是HTML代码</div>'
}
},
methods: {
preview(src, e) {
// do something
},
navigate(href, e) {
// do something
}
}
}
</script>
<style>
@import url("@/components/u-parse/u-parse.css");
</style>
```
## 渲染 Markdown
> 先将 markdown 转换为 html 即可
```
npm install marked
```
``` js
import marked from 'marked'
import uParse from '@/components/u-parse/u-parse.vue'
export default {
components: {
uParse
},
data () {
return {
article: marked(`#hello, markdown!`)
}
}
}
```
/**
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
.wxParse {
width: 100%;
font-family: Helvetica, sans-serif;
font-size: 30upx;
color: #666;
line-height: 1.8;
}
.wxParse view {
word-break: hyphenate;
}
.wxParse .inline {
display: inline;
margin: 0;
padding: 0;
}
.wxParse .div {
margin: 0;
padding: 0;
}
.wxParse .h1 .text {
font-size: 2em;
margin: 0.67em 0;
}
.wxParse .h2 .text {
font-size: 1.5em;
margin: 0.83em 0;
}
.wxParse .h3 .text {
font-size: 1.17em;
margin: 1em 0;
}
.wxParse .h4 .text {
margin: 1.33em 0;
}
.wxParse .h5 .text {
font-size: 0.83em;
margin: 1.67em 0;
}
.wxParse .h6 .text {
font-size: 0.67em;
margin: 2.33em 0;
}
.wxParse .h1 .text,
.wxParse .h2 .text,
.wxParse .h3 .text,
.wxParse .h4 .text,
.wxParse .h5 .text,
.wxParse .h6 .text,
.wxParse .b,
.wxParse .strong {
font-weight: bolder;
}
.wxParse .p {
margin: 1em 0;
}
.wxParse .i,
.wxParse .cite,
.wxParse .em,
.wxParse .var,
.wxParse .address {
font-style: italic;
}
.wxParse .pre,
.wxParse .tt,
.wxParse .code,
.wxParse .kbd,
.wxParse .samp {
font-family: monospace;
}
.wxParse .pre {
overflow: auto;
background: #f5f5f5;
padding: 16upx;
white-space: pre;
margin: 1em 0upx;
}
.wxParse .code {
display: inline;
background: #f5f5f5;
}
.wxParse .big {
font-size: 1.17em;
}
.wxParse .small,
.wxParse .sub,
.wxParse .sup {
font-size: 0.83em;
}
.wxParse .sub {
vertical-align: sub;
}
.wxParse .sup {
vertical-align: super;
}
.wxParse .s,
.wxParse .strike,
.wxParse .del {
text-decoration: line-through;
}
.wxParse .strong,
.wxParse .s {
display: inline;
}
.wxParse .a {
color: deepskyblue;
}
.wxParse .video {
text-align: center;
margin: 22upx 0;
}
.wxParse .video-video {
width: 100%;
}
.wxParse .img {
display: inline-block;
width: 0;
height: 0;
max-width: 100%;
overflow: hidden;
}
.wxParse .blockquote {
margin: 10upx 0;
padding: 22upx 0 22upx 22upx;
font-family: Courier, Calibri, "宋体";
background: #f5f5f5;
border-left: 6upx solid #dbdbdb;
}
.wxParse .blockquote .p {
margin: 0;
}
.wxParse .ul, .wxParse .ol {
display: block;
margin: 1em 0;
padding-left: 33upx;
}
.wxParse .ol {
list-style-type: disc;
}
.wxParse .ol {
list-style-type: decimal;
}
.wxParse .ol>weixin-parse-template,.wxParse .ul>weixin-parse-template {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.wxParse .ol>.li,.wxParse .ul>.li {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.wxParse .ul .ul, .wxParse .ol .ul {
list-style-type: circle;
}
.wxParse .ol .ol .ul, .wxParse .ol .ul .ul, .wxParse .ul .ol .ul, .wxParse .ul .ul .ul {
list-style-type: square;
}
.wxParse .u {
text-decoration: underline;
}
.wxParse .hide {
display: none;
}
.wxParse .del {
display: inline;
}
.wxParse .figure {
overflow: hidden;
}
.wxParse .table {
width: 100%;
}
.wxParse .thead, .wxParse .tfoot, .wxParse .tr {
display: flex;
flex-direction: row;
}
.wxParse .tr {
width:100%;
display: flex;
border-right: 2upx solid #e0e0e0;
border-bottom: 2upx solid #e0e0e0;
}
.wxParse .th,
.wxParse .td {
display: flex;
width: 1276upx;
overflow: auto;
flex: 1;
padding: 11upx;
border-left: 2upx solid #e0e0e0;
}
.wxParse .td:last {
border-top: 2upx solid #e0e0e0;
}
.wxParse .th {
background: #f0f0f0;
border-top: 2upx solid #e0e0e0;
}
<!--**
* forked from:https://github.com/F-loat/mpvue-wxParse
*
* github地址: https://github.com/dcloudio/uParse
*
* for: uni-app框架下 富文本解析
*/-->
<template>
<!--基础元素-->
<div class="wxParse" :class="className" v-if="!loading">
<block v-for="(node,index) of nodes" :key="index">
<wxParseTemplate :node="node" />
</block>
</div>
</template>
<script>
import HtmlToJson from './libs/html2json';
import wxParseTemplate from './components/wxParseTemplate0';
export default {
name: 'wxParse',
props: {
loading: {
type: Boolean,
default: false,
},
className: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
noData: {
type: String,
default: '<div style="color: red;">数据不能为空</div>',
},
startHandler: {
type: Function,
default() {
return (node) => {
node.attr.class = null;
node.attr.style = null;
};
},
},
endHandler: {
type: Function,
default: null,
},
charsHandler: {
type: Function,
default: null,
},
imageProp: {
type: Object,
default() {
return {
mode: 'aspectFit',
padding: 0,
lazyLoad: false,
domain: '',
};
},
},
},
components: {
wxParseTemplate,
},
data() {
return {
imageUrls: [],
};
},
computed: {
nodes() {
const {
content,
noData,
imageProp,
startHandler,
endHandler,
charsHandler,
} = this;
const parseData = content || noData;
const customHandler = {
start: startHandler,
end: endHandler,
chars: charsHandler,
};
const results = HtmlToJson(parseData, customHandler, imageProp, this);
this.imageUrls = results.imageUrls;
console.log(results)
return results.nodes;
},
},
methods: {
navigate(href, $event) {
this.$emit('navigate', href, $event);
},
preview(src, $event) {
if (!this.imageUrls.length) return;
wx.previewImage({
current: src,
urls: this.imageUrls,
});
this.$emit('preview', src, $event);
},
removeImageUrl(src) {
const { imageUrls } = this;
imageUrls.splice(imageUrls.indexOf(src), 1);
},
},
};
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif
\ No newline at end of file
{
"name" : "uni-app-vue3-tailwind-hbuilder-template",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios" : {},
/* SDK配置 */
"sdkConfigs" : {}
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
// 引入 request 文件
import request from '@/network/request.js'
// 新闻列表
export function getNewsList(params) {
return request({
url: "/cms/content/list",
method: "get",
data: params,
});
}
// 新闻点击数量
export function getHit(contentId) {
return request({
url: "/cms/content/" + contentId + "/hit",
data: "get",
});
}
//新闻详情
export function getNewsDetail(params) {
return request({
url: "/cms/content/get",
method: "get",
data: params,
});
}
export function getTab(categoryFlag) {
return request({
url: "/cms/category/list?categoryFlag="+categoryFlag,
method: "post",
// data: params,
});
}
// // 获取学习列表详细信息
// export const studyInfoById = (id) => {
// return request({
// url: `/study/studyInfo/${id}`,
// method: 'get',
// })
// }
\ No newline at end of file
import {
baseUrl
} from "../config.js";
// import {
// getToken
// } from '@/utils/auth'; // 导入获取 token 的函数
// 上传图片的异步函数
const uploadImg = async function(tempFilePaths) {
const baseUrls = baseUrl; // 基础 URL 从配置中获取
// const token = getToken(); // 使用 getToken 函数获取 token
const successfulUploads = []; // 数组用于记录所有成功上传的文件路径
if (!tempFilePaths.length) return []; // 如果没有选择图片就退出,返回空数组
// 遍历选择的图片路径
for (const path of tempFilePaths) {
try {
// 调用接口上传每一个文件
const [err, {
data
}] = await uni.uploadFile({
url: `${baseUrls}/common/upload`, // 自己的后台地址
filePath: path,
name: 'file',
// header: { // 添加请求头
// 'Authorization': `Bearer ${token}`, // 假设你使用的是 Bearer 认证
// // 可以添加其他需要的请求头
// },
});
// 处理错误情况
if (err) {
console.error('上传错误:', err);
continue; // 如果上传出错,继续上传下一张图片
}
// 把返回的数据转为对象格式
const res = JSON.parse(data);
console.log(res)
// 将每个上传成功的文件路径添加到数组中
successfulUploads.push(res); // 假设 res.data 是上传后的文件路径
} catch (error) {
console.error('处理上传时发生错误:', error);
continue; // 捕获上传错误并继续
}
}
// 返回所有成功上传的文件路径
return successfulUploads;
};
// 导出 uploadImg 函数
export default uploadImg;
\ No newline at end of file
import request from '@/network/request.js'
// 热点留言列表
export function getHotList(data) {
return request({
url: "/cms/leavemessage/list",
method: "post",
data: data,
});
}
// 最新回复列表
export function getRecoverList(data) {
return request({
url: "/cms/leavemessage/replyList",
method: "post",
data: data,
});
}
// 留言详情
export function getMessageDetail(id) {
return request({
url: "/cms/leavemessage/get?id="+id,
method: "get",
});
}
// 我要留言
export function getWantMessage(data) {
return request({
url: "/cms/leavemessage/saveEntity",
method: "post",
data: data,
});
}
// let baseUrl = "http://192.168.19.142:9002";
// let baseUrl = "http://192.168.19.217:9002";
let baseUrl = "http://yjzh.sxyztech.cn";
let baseImageUrl = (path) => baseUrl + path;
export {
baseUrl,
baseImageUrl
};
// 栏目属性
export const tyshlwyjzhhjbzx = "tyshlwyjzhhjbzx"; //太原市互联网应急指挥和举报中心
export const sxhlwlhpypt = "sxhlwlhpypt"; //山西互联网联合辟谣平台
export const wmyhs = "wmyhs"; //网民有话说
//文章类型
export const yjjblbt = "yjjblbt"; // 轮播图
export const rdxw = "rdxw"; // 热点新闻
export const tyxw = "tyxw"; // 太原新闻
export const sxxw = "sxxw"; // 山西新闻
export const tzgg = "tzgg"; // 通知公告
export const lhpylbt = "lhpylbt"; // 轮播图
export const qwfb = "qwfb"; // 权威发布
export const pyzq = "pyzq"; // 辟谣专区
export const zjjd = "zjjd"; // 专家解读
export const flfg = "flfg"; // 法律法规
export const dtsy = "dtsy"; // 读图识谣
\ No newline at end of file
import {
baseUrl
} from "./config.js";
// 全局请求封装
// const base_url = 'http://192.168.19.142:9002'
// 请求超出时间
const timeout = 5000
// 需要修改token,和根据实际修改请求头
export default (params) => {
let url = params.url;
let method = params.method || "get";
let data = params.data || {};
let header = {
// 'Blade-Auth': uni.getStorageSync('token') || '',
'Content-Type': 'application/json;charset=UTF-8',
// 'Authorization': 'Basic c2FiZXI6c2FiZXJfc2VjcmV0',
// 'Tenant-Id': uni.getStorageSync('tenantId') || 'xxx', // avue配置相关
// ...params.header
}
if (method == "post") {
header = {
'Content-Type': 'application/json;charset=UTF-8 ' // 自定义,跟后台约定好传什么格式的
};
}
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + url,
method: method,
header: header,
data: data,
timeout,
success(response) {
const res = response
// 根据返回的状态码做出对应的操作
//获取成功
// console.log(res.statusCode);
if (res.statusCode == 200) {
resolve(res.data);
} else {
uni.clearStorageSync()
switch (res.statusCode) {
case 401:
uni.showToast({
title: '401...',
duration: 2000,
})
// uni.showModal({
// title: "提示",
// content: "请登录",
// showCancel: false,
// success() {
// setTimeout(() => {
// uni.navigateTo({
// url: "/pages/login/index",
// })
// }, 1000);
// },
// });
break;
case 404:
uni.showToast({
title: '请求地址不存在...',
duration: 2000,
})
break;
default:
uni.showToast({
title: '请重试...',
duration: 2000,
})
break;
}
}
},
fail(err) {
console.log(err)
if (err.errMsg.indexOf('request:fail') !== -1) {
uni.showToast({
title: '网络异常',
icon: "error",
duration: 2000
})
} else {
uni.showToast({
title: '未知异常',
duration: 2000
})
}
reject(err);
},
complete() {
// 不管成功还是失败都会执行
uni.hideLoading();
uni.hideToast();
}
});
}).catch(() => {});
};
\ No newline at end of file
{
"name": "uni-app-vue3-tailwind-hbuilder-template",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"postinstall": "weapp-tw patch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sonofmagic/uni-app-vue3-tailwind-hbuilder-template.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/sonofmagic/uni-app-vue3-tailwind-hbuilder-template/issues"
},
"homepage": "https://github.com/sonofmagic/uni-app-vue3-tailwind-hbuilder-template#readme",
"devDependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15",
"weapp-tailwindcss": "^3.7.0"
}
}
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9",
"navigationStyle": "custom",
"onReachBottomDistance": 50
}
},
{
"path": "pages/zx/index",
"style": {
"navigationBarTitleText": "资讯",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9",
"enablePullDownRefresh": false
// "navigationStyle": "custom",
// "onReachBottomDistance": 50
}
},
{
"path": "pages/py/index",
"style": {
"navigationBarTitleText": "辟谣",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9",
"enablePullDownRefresh": false
// "navigationStyle": "custom",
// "onReachBottomDistance": 50
}
},
{
"path": "pages/wm/index",
"style": {
"navigationBarTitleText": "",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9",
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
},
{
"path": "pages/web/index",
"style": {
"navigationBarTitleText": "网页",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9",
"enablePullDownRefresh": false
}
}
],
"subPackages": [{
"root": "pages_wm",
"pages": [{
"needLogin": false,
"path": "modules/message/index",
"style": {
"navigationBarTitleText": "留言列表",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}, {
"needLogin": false,
"path": "modules/recover/index",
"style": {
"navigationBarTitleText": "回复列表",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}, {
"needLogin": false,
"path": "modules/detail/index",
"style": {
"navigationBarTitleText": "详情",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}, {
"needLogin": false,
"path": "modules/submit/index",
"style": {
"navigationBarTitleText": "我要留言",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}]
}, {
"root": "pages_zx",
"pages": [{
"needLogin": false,
"path": "modules/detail/index",
"style": {
"navigationBarTitleText": "详情",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}]
}, {
"root": "pages_py",
"pages": [{
"needLogin": false,
"path": "modules/detail/index",
"style": {
"navigationBarTitleText": "详情",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#4975E9"
}
}]
}],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#4975E9",
"borderStyle": "black",
"backgroundColor": "#fff",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/tabIcons/shouye.png",
"selectedIconPath": "static/tabIcons/shouyeHL.png",
"text": "首页"
},
{
"pagePath": "pages/zx/index",
"iconPath": "static/tabIcons/zixun.png",
"selectedIconPath": "static/tabIcons/zixunHL.png",
"text": "资讯"
}, {
"pagePath": "pages/py/index",
"iconPath": "static/tabIcons/piyao.png",
"selectedIconPath": "static/tabIcons/piyaoHL.png",
"text": "辟谣"
}, {
"pagePath": "pages/wm/index",
"iconPath": "static/tabIcons/wm.png",
"selectedIconPath": "static/tabIcons/wmHL.png",
"text": "有话说"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}
\ No newline at end of file
<template>
<view class="app-container">
<!-- 顶部轮播图 -->
<view class="banner-container">
<view class="banner-title">
<image :src="logoUrl" mode="aspectFit" class="logo-image" />
<text class="title-text">太原市互联网应急指挥和举报中心</text>
</view>
<swiper class="swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500">
<swiper-item v-for="(banner, index) in slides" :key="index" @click="goToDetails(banner)">
<image :src="banner.images" :alt="banner.contentTitle" mode="aspectFill" class="banner-image" />
</swiper-item>
</swiper>
</view>
<!-- 功能区域 -->
<view class="features-grid">
<view v-for="(item, index) in features" :key="index" class="feature-card" @click="goToMore(index)">
<image :src=" item.imageUrl" :alt="item.title" mode="aspectFill" class="feature-image" />
<view class="feature-overlay">
<text class="feature-title">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 热点新闻 -->
<view class="news-section">
<view class="section-header">
<text class="section-title">热点新闻</text>
<text class="more-text" @click="goToMoreList(0)">更多</text>
</view>
<view class="news-list">
<view v-for="(news, index) in rdNews" :key="index" class="news-card" @click="goToDetails(news)">
<image :src="news.images" :alt="news.contentTitle" mode="aspectFill" class="news-image" />
<view class="news-content">
<text class="news-title">{{ news.contentTitle }}</text>
<text class="news-date">{{ news.contentDatetime }}</text>
</view>
</view>
</view>
</view>
<!-- 山西新闻 -->
<view class="news-section shanxi-news">
<view class="section-header">
<text class="section-title">山西新闻</text>
<text class="more-text" @click="goToMoreList(1)">更多</text>
</view>
<view class="news-list">
<view v-for="(news, index) in sxNews" :key="index" class="news-card" @click="goToDetails(news)">
<image :src="news.images" :alt="news.contentTitle" mode="aspectFill" class="news-image" />
<view class="news-content">
<text class="news-title">{{ news.contentTitle }}</text>
<text class="news-date">{{ news.contentDatetime }}</text>
</view>
</view>
</view>
</view>
<!-- 太原新闻 -->
<view class="news-section ty-news">
<view class="section-header">
<text class="section-title">太原新闻</text>
<text class="more-text" @click="goToMoreList(2)">更多</text>
</view>
<view class="news-list">
<view v-for="(news, index) in tyNews" :key="index" class="news-card" @click="goToDetails(news)">
<image :src="news.images" :alt="news.contentTitle" mode="aspectFill" class="news-image" />
<view class="news-content">
<text class="news-title">{{ news.contentTitle }}</text>
<text class="news-date">{{ news.contentDatetime }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList } from "@/network/api/news.js";
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, baseImageUrl } from "@/network/config.js";
const slides = ref([]);
const rdNews = ref([]);
const sxNews = ref([]);
const tyNews = ref([]);
getLbtList(5, 1);
//获取首页轮播图
function getLbtList(pageSize : number, pageNo : number) {
const datas = {
contentType: yjjblbt,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
const data = response.data;
const rowsList = data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.images = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
slides.value = rowsList;
});
}
getRdList(5, 1);
//获取首页轮播图
function getRdList(pageSize : number, pageNo : number) {
const datas = {
contentType: rdxw,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
const data = response.data;
const rowsList = data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.images = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
rdNews.value = rowsList;
});
}
getSxList(5, 1);
//获取首页轮播图
function getSxList(pageSize : number, pageNo : number) {
const datas = {
contentType: sxxw,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
const data = response.data;
const rowsList = data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.images = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
sxNews.value = rowsList;
});
}
getTyList(5, 1);
//获取首页轮播图
function getTyList(pageSize : number, pageNo : number) {
const datas = {
contentType: tyxw,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
const data = response.data;
const rowsList = data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.images = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
tyNews.value = rowsList;
});
}
const logoUrl = 'https://ai-public.mastergo.com/ai/img_res/6a4441592a02fd2dd968cdfc1a656d74.jpg';
const features = [
{
title: '太原市互联网联合辟谣平台',
imageUrl: 'https://ai-public.mastergo.com/ai/img_res/fcf10d894f163ac3ae02390c4d541ba7.jpg'
},
// {
// title: '太原市互联网违法和不良信息举报平台',
// imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6dcc96210a253d72af67308fb98f43dc.jpg'
// },
{
title: '网民有话说',
imageUrl: 'https://ai-public.mastergo.com/ai/img_res/fe75574e074021d6e33931c815f9d2de.jpg'
}
];
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_zx/modules/detail/index?id=' + item.id,
});
}
}
function goToMore(index : number) {
switch (index) {
case 0:
uni.switchTab({
url: '/pages/py/index'
})
break;
case 1:
uni.switchTab({
url: '/pages/wm/index'
})
break;
}
}
function goToMoreList(index : number) {
// getApp().globalData.tabIndex = index;
uni.switchTab({
url: '/pages/zx/index',
})
}
</script>
<style>
page {
height: 100%;
}
.app-container {
width: 750rpx;
min-height: 100%;
background-color: #f5f5f5;
}
.banner-container {
height: 440rpx;
position: relative;
}
.banner-title {
position: absolute;
left: 12rpx;
top: 80rpx;
z-index: 10;
display: flex;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
padding: 16rpx 32rpx;
border-radius: 30rpx;
}
.logo-image {
width: 40rpx;
height: 40rpx;
margin-right: 16rpx;
}
.title-text {
color: #ffffff;
font-size: 14px;
}
.swiper {
width: 100%;
height: 440rpx;
}
.banner-image {
width: 100%;
height: 440rpx;
}
.features-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 32rpx;
}
.feature-card {
position: relative;
border-radius: 16rpx;
overflow: hidden;
background-color: #ffffff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
height: 200rpx;
}
.feature-image {
width: 100%;
height: 100%;
}
.feature-overlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx;
}
.feature-title {
color: #ffffff;
font-size: 14px;
text-align: center;
}
.news-section {
padding: 0 32rpx;
margin-top: 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.section-title {
font-size: 18px;
font-weight: 500;
}
.more-text {
color: #999999;
font-size: 14px;
}
.news-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.news-card {
display: flex;
gap: 24rpx;
background-color: #ffffff;
padding: 24rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.news-image {
width: 192rpx;
height: 128rpx;
border-radius: 8rpx;
}
.news-content {
flex: 1;
display: flex;
flex-direction: column;
}
.news-title {
font-size: 14px;
margin-bottom: 16rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.news-date {
font-size: 12px;
color: #999999;
}
.shanxi-news {
padding-bottom: 0rpx;
}
.ty-news {
padding-bottom: 20rpx;
}
</style>
\ No newline at end of file
<template>
<mescroll-body @init="mescrollInit" top="85" @down="downCallback" :up="upOption" @up="upCallback"
@emptyclick="emptyClick" :down="downOption">
<view class="news-count">{{ total }} 条新闻</view>
<view class="news-list">
<view v-for="(news, index) in dataList" :key="index" class="news-card" @click="goToDetails(news)">
<view class="news-content">
<image class="news-image" :src="news.image" mode="aspectFill" />
<view class="news-info">
<text class="news-title">{{ news.contentTitle }}</text>
<text class="news-date">{{ news.contentDatetime }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList } from "@/network/api/news.js";
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, qwfb, pyzq, zjjd, flfg, baseImageUrl } from "@/network/config.js";
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
const { mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: false //是否在初始化后,自动执行downCallback; 默认true
}
const upOption = {
noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
empty: {
tip: '~ 暂无相关数据 ~', // 提示
}
}
const props = defineProps({
dataValue: String,
});
//总条数
const total = ref(0);
const dataList = ref([]);
//下拉刷新
const downCallback = (mescroll : any) => {
dataList.value = [];
mescroll.resetUpScroll(); // 重置列表为第一页 (自动执行 page.num=1, 再触发upCallback方法 )
}
//上拉加载
const upCallback = (mescroll : any) => {
getList(false, mescroll, mescroll.size, mescroll.num);
}
//点击空布局按钮的回调
const emptyClick = () => {
dataList.value = []// 先置空列表,显示加载进度
getMescroll().resetUpScroll() // 再刷新列表数据
}
//获取新闻热点列表
function getList(isDown : boolean, mescroll : any, pageSize : number, pageNo : number) {
const datas = {
contentType: props.dataValue,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
total.value = response.data.total;
const rowsList = response.data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.image = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
if (isDown) {//下拉刷新
dataList.value = rowsList;
mescroll.endSuccess();
} else {//上拉加载
dataList.value = dataList.value.concat(rowsList);
mescroll.endSuccess(rowsList.length);
}
}).catch(() => {
mescroll.endErr(); // 请求失败, 结束加载
})
}
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_py/modules/detail/index?id=' + item.id + '&dataValue=' + props.dataValue,
});
}
}
</script>
<style scoped>
page {
height: 100%;
}
.page {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
}
.header {
flex-shrink: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
border-bottom: 1px solid #f0f0f0;
}
.tab-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.tab-btn {
flex: 1;
margin: 0 10rpx !important;
font-size: 14px !important;
}
.active-tab {
color: #2979ff !important;
border-color: #2979ff !important;
font-weight: bold !important;
}
.content {
flex: 1;
overflow: auto;
}
.news-count {
text-align: center;
font-size: 12px;
color: #999999;
padding: 20rpx 0;
}
.news-list {
padding: 0 30rpx;
}
.news-card {
background-color: #ffffff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.news-content {
display: flex;
padding: 20rpx;
}
.news-image {
width: 200rpx;
height: 160rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.news-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.news-title {
font-size: 14px;
color: #333333;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.news-date {
font-size: 12px;
color: #999999;
margin-top: 10rpx;
}
</style>
\ No newline at end of file
<template>
<mescroll-body @init="mescrollInit" top="85" @down="downCallback" :up="upOption" @up="upCallback"
@emptyclick="emptyClick" :down="downOption">
<!-- 新闻总数 -->
<view class="news-count">{{total}} 条新闻</view>
<!-- 新闻列表 -->
<view class="news-grid">
<view v-for="(news, index) in dataList" :key="index" class="news-item" @click="goToDetails(news)">
<view class="image-wrapper">
<image :src="news.image" :alt="news.contentTitle" mode="aspectFill" class="news-image" />
</view>
<view class="news-info">
<text class="news-title">{{news.contentTitle}}</text>
<text class="news-date">{{news.contentDatetime}}</text>
</view>
</view>
</view>
</mescroll-body>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList } from "@/network/api/news.js";
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, qwfb, pyzq, zjjd, flfg, baseImageUrl } from "@/network/config.js";
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
const { mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: false //是否在初始化后,自动执行downCallback; 默认true
}
const upOption = {
noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
empty: {
tip: '~ 暂无相关数据 ~', // 提示
}
}
const props = defineProps({
dataValue: String,
});
//总条数
const total = ref(0);
const dataList = ref([]);
//下拉刷新
const downCallback = (mescroll : any) => {
dataList.value = [];
mescroll.resetUpScroll(); // 重置列表为第一页 (自动执行 page.num=1, 再触发upCallback方法 )
}
//上拉加载
const upCallback = (mescroll : any) => {
getList(false, mescroll, mescroll.size, mescroll.num);
}
//点击空布局按钮的回调
const emptyClick = () => {
dataList.value = []// 先置空列表,显示加载进度
getMescroll().resetUpScroll() // 再刷新列表数据
}
//获取新闻热点列表
function getList(isDown : boolean, mescroll : any, pageSize : number, pageNo : number) {
const datas = {
contentType: props.dataValue,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
total.value = response.data.total;
const rowsList = response.data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.image = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
if (isDown) {//下拉刷新
dataList.value = rowsList;
mescroll.endSuccess();
} else {//上拉加载
dataList.value = dataList.value.concat(rowsList);
mescroll.endSuccess(rowsList.length);
}
}).catch(() => {
mescroll.endErr(); // 请求失败, 结束加载
})
}
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_py/modules/detail/index?id=' + item.id + '&dataValue=' + props.dataValue,
});
}
}
</script>
<style>
page {
height: 100%;
}
.page {
position: relative;
height: 100%;
background-color: #f5f5f5;
}
.content-area {
height: 100%;
padding: 10rpx 32rpx 0;
box-sizing: border-box;
}
.news-count {
text-align: center;
font-size: 12px;
color: #999999;
margin-top: 20rpx;
margin-bottom: 15rpx;
}
.news-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
padding: 0rpx 32rpx 0rpx 32rpx;
}
.news-item {
background-color: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.image-wrapper {
width: 100%;
padding-top: 75%;
position: relative;
}
.news-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.news-info {
padding: 16rpx;
}
.news-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
color: #333333;
font-size: 28rpx;
font-weight: 500;
margin-bottom: 8rpx;
line-height: 1.4;
}
.news-date {
display: block;
color: #999999;
font-size: 26rpx;
}
</style>
\ No newline at end of file
<template>
<view class="app-container flex flex-col">
<view class="nav-bar fixed w-full top-0 z-50">
<view class="tab-container">
<view v-for="(tab, index) in tabs" :key="index" class="tab-item"
:class="{'tab-active': activeTab === index}" @click="changeTabs(index)">
{{ tab.categoryTitle }}
</view>
</view>
</view>
<view class="flex-1 h-0">
<all-list class="h-full" v-if="activeTab == 0" :dataValue="qwfb"></all-list>
<all-list class="h-full" v-if="activeTab == 1" :dataValue="pyzq"></all-list>
<all-list class="h-full" v-if="activeTab == 2" :dataValue="zjjd"></all-list>
<all-list class="h-full" v-if="activeTab == 3" :dataValue="flfg"></all-list>
<dt-list class="h-full" v-if="activeTab == 4" :dataValue="dtsy"></dt-list>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import allList from './all-list/all-list.vue';
import dtList from './dt-list/dt-list.vue';
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, qwfb, pyzq, zjjd, flfg, dtsy, sxhlwlhpypt, baseImageUrl } from "@/network/config.js";
import { getTab } from '@/network/api/news';
const activeTab = ref(0);
// const tabs = [
// '权威发布',
// '辟谣专区',
// '专家解读',
// '法律法规',
// '读图识谣'
// ];
const tabs = ref([]);
getTab(sxhlwlhpypt).then((res) => {
console.log(res);
const data = res.data.rows;
// //用data内的categorySort排序
data.sort((a, b) => a.categorySort - b.categorySort);
//将data内categoryTitle等于“权威发布”和“辟谣专区”和“轮播图”的数据过滤不在navItems.value中显示
const filterData = data.filter(
(item) =>
item.categoryTitle !== "轮播图" &&
item.categoryTitle !== "山西互联网联合辟谣平台"&&
item.categoryTitle !== "首页"
);
tabs.value = filterData;
});
function changeTabs(index : number) {
switch (index) {
case 0: {
activeTab.value = 0;
break
}
case 1: {
activeTab.value = 1;
break
}
case 2: {
activeTab.value = 2;
break
}
case 3: {
activeTab.value = 3;
break
}
case 4: {
activeTab.value = 4;
break
}
}
}
</script>
<style>
page {
height: 100%;
}
.app-container {
width: 750rpx;
min-height: 100%;
background-color: #f5f5f5;
}
.nav-bar {
flex-shrink: 0;
height: 88rpx;
background-color: #4975E9;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tab-container {
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
}
.tab-item {
height: 88rpx;
line-height: 88rpx;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
transition: all 0.3s;
}
.tab-active {
color: #ffffff;
font-size: 16px;
font-weight: bold;
}
</style>
\ No newline at end of file
<template>
<view>
<web-view :src="webUrl"></web-view>
</view>
</template>
<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app';
import { ref } from 'vue';
const webUrl = ref();
onLoad((options) => {
webUrl.value = options.webUrl;
console.log(options.webUrl)
});
</script>
<style>
</style>
\ No newline at end of file
<template>
<view class="container">
<!-- Banner区域 -->
<view class="banner">
<view class="banner-bg">
<view class="banner-content">
<view>
<view class="tag">网民有话说</view>
<text class="slogan">用真诚的态度和专业的服务,构建政民互动的新平台</text>
</view>
<!-- 搜索框 -->
<view class="search-area">
<view class="search-input-wrap">
<uni-icons type="search" size="16" color="#999"></uni-icons>
<input v-model="searchKeyword" type="text" placeholder="搜索您关注的问题..."
placeholder-class="placeholder" class="search-input" />
</view>
<button class="search-btn" @click="seachBt()">搜索</button>
<button class="post-btn" @click="goToMessage()">我要留言</button>
</view>
</view>
</view>
</view>
<!-- 主体内容区域 -->
<view class="main-content">
<!-- 热点留言 -->
<view class="card">
<view class="card-header">
<text class="card-title">热点留言</text>
<text class="more-link" @click="moreMessage()">更多留言 ></text>
</view>
<view class="message-list">
<view v-for="item in hotList" :key="item.id" class="message-item" @click="goToDetails(item.id)">
<text class="title">{{ item.title }}</text>
<view class="message-info">
<text class="date">{{ item.contentDatetime }}</text>
<text
:class="['status-tag',
item.replyStatus === '1' ? 'status-replied' : 'status-processing']">{{ item.replyStatus === "1" ? "已回复" : "未回复" }}</text>
</view>
</view>
</view>
</view>
<!-- 最新回复 -->
<view class="card">
<view class="card-header">
<text class="card-title">最新回复</text>
<text class="more-link" @click="moreRecover()">更多回复 ></text>
</view>
<view class="reply-list">
<view v-for="item in recoverList" :key="item.id" class="reply-item" @click="goToDetails(item.id)">
<view class="reply-header">
<text class="reply-title">{{ item.title }}</text>
</view>
<text class="reply-content">{{ item.content }}</text>
<view class="reply-footer">
<text class="reply-date">{{ item.contentDatetime }}</text>
<text
:class="['status-tag',
item.replyStatus === '1' ? 'status-replied' : 'status-processing']">{{ item.replyStatus === "1" ? "已回复" : "未回复" }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getHotList, getRecoverList } from "@/network/api/wm.js";
import { onPullDownRefresh } from "@dcloudio/uni-app"
const hotList = ref([]);
const recoverList = ref([]);
const searchKeyword = ref('');
//下拉刷新
onPullDownRefresh(() => {
getList1(6, 1);
getList2(6, 1);
uni.stopPullDownRefresh();
})
getList1(6, 1);
getList2(6, 1);
//热点留言
function getList1(pageSize : number, pageNo : number) {
const data = {
pageSize: pageSize,
pageNo: pageNo,
};
getHotList(data).then((response) => {
const data = response.data;
const rowsList = data.rows;
hotList.value = rowsList;
});
}
//最新回复
function getList2(pageSize : number, pageNo : number) {
const data = {
pageSize: pageSize,
pageNo: pageNo,
};
getRecoverList(data).then((response) => {
const data = response.data;
const rowsList = data.rows;
recoverList.value = rowsList;
});
}
//更多留言
function moreMessage() {
uni.navigateTo({
url: '/pages_wm/modules/message/index',
});
}
//搜索
function seachBt() {
uni.navigateTo({
url: '/pages_wm/modules/message/index?keyword=' + searchKeyword.value,
});
}
//更多回复
function moreRecover() {
uni.navigateTo({
url: '/pages_wm/modules/recover/index',
});
}
//详情页面
function goToDetails(id : number) {
uni.navigateTo({
url: '/pages_wm/modules/detail/index?id=' + id,
});
}
//我要留言
function goToMessage() {
uni.navigateTo({
url: '/pages_wm/modules/submit/index',
});
}
</script>
<style>
page {
height: 100%;
background-color: #f5f5f5;
}
.container {
min-height: 100%;
}
.banner {
position: relative;
height: 400rpx;
}
.banner-bg {
height: 400rpx;
background-image: url('https://ai-public.mastergo.com/ai/img_res/954e9782407fe5f072dfb3c6680518d5.jpg');
background-size: cover;
background-position: center;
position: relative;
}
.banner-bg::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: linear-gradient(to right, rgba(30, 58, 138, 0.8), rgba(30, 64, 175, 0.8));
}
.banner-content {
position: relative;
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 32rpx;
}
.tag {
display: inline-block;
background: rgba(0, 0, 0, 0.6);
border-radius: 12rpx;
padding: 8rpx 24rpx;
color: #ffffff;
font-size: 14px;
margin-bottom: 16rpx;
margin-top: 70rpx;
}
.logo {
color: #ffffff;
font-size: 24px;
font-weight: 600;
margin-bottom: 16rpx;
display: block;
}
.slogan {
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
display: block;
}
.search-area {
display: flex;
gap: 16rpx;
}
.search-input-wrap {
flex: 1;
background: rgba(255, 255, 255, 0.9);
border-radius: 12rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
}
.search-input {
flex: 1;
height: 60rpx;
padding: 0 16rpx;
font-size: 14px;
color: #333;
}
.placeholder {
color: #999;
}
.search-btn,
.post-btn {
height: 60rpx;
line-height: 60rpx;
padding: 0 20rpx;
font-size: 14px;
border-radius: 20rpx;
}
.search-btn {
background-color: #2563eb;
color: #ffffff;
}
.post-btn {
background-color: #ffffff;
color: #1e40af;
}
.main-content {
padding: 32rpx;
}
.card {
background: #ffffff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 32rpx;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.card-title {
font-size: 18px;
font-weight: 500;
}
.more-link {
color: #2563eb;
font-size: 14px;
}
.message-list,
.reply-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.message-item,
.reply-item {
padding-bottom: 24rpx;
border-bottom: 1px solid #f3f4f6;
}
.message-info {
display: flex;
align-items: center;
justify-content: space-between;
}
.message-item:last-child,
.reply-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.date {
color: #9ca3af;
font-size: 12px;
}
.title {
font-size: 14px;
margin-bottom: 16rpx;
display: block;
}
.reply-header {
display: flex;
justify-content: space-between;
margin-bottom: 16rpx;
}
.reply-title {
font-size: 14px;
font-weight: 500;
flex: 1;
}
.department {
font-size: 12px;
color: #6b7280;
}
.reply-content {
font-size: 14px;
color: #4b5563;
margin-bottom: 16rpx;
display: block;
}
.status-tag {
font-size: 12px;
padding: 4rpx 16rpx;
border-radius: 32rpx;
display: inline-block;
}
.status-replied {
background-color: #dcfce7;
color: #16a34a;
}
.status-processing {
background-color: #fef3c7;
color: #d97706;
}
.reply-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.reply-date {
font-size: 12px;
color: #9ca3af;
}
</style>
\ No newline at end of file
<template>
<view class="app-container flex flex-col">
<view class="nav-bar fixed w-full top-0 z-50">
<view class="tab-container">
<view v-for="(tab, index) in tabs" :key="index" class="tab-item"
:class="{'tab-active': activeTab === index}" @click="changeTabs(index)">
{{ tab.categoryTitle }}
</view>
</view>
</view>
<view class="flex-1 h-0">
<news-list class="h-full" v-if="activeTab == 0" :dataValue="rdxw"></news-list>
<news-list class="h-full" v-if="activeTab == 1" :dataValue="sxxw"></news-list>
<news-list class="h-full" v-if="activeTab == 2" :dataValue="tyxw"></news-list>
<tz-list class="h-full" v-if="activeTab == 3" :dataValue="tzgg"></tz-list>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import newsList from './news-list/news-list.vue';
import tzList from './tz-list/tz-list.vue';
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, tyshlwyjzhhjbzx, baseImageUrl } from "@/network/config.js";
import { getTab } from '@/network/api/news';
const activeTab = ref(0);
// const tabs = [
// '新闻热点',
// '山西新闻',
// '太原新闻',
// '通知公告'
// ];
const tabs = ref([]);
getTab(tyshlwyjzhhjbzx).then((res) => {
console.log(res);
const data = res.data.rows;
// //用data内的categorySort排序
data.sort((a, b) => a.categorySort - b.categorySort);
//将data内categoryTitle等于“权威发布”和“辟谣专区”和“轮播图”的数据过滤不在navItems.value中显示
const filterData = data.filter((item) => item.categoryTitle !== "轮播图" && item.categoryTitle !== "首页");
tabs.value = filterData;
});
function changeTabs(index : number) {
switch (index) {
case 0: {
activeTab.value = 0;
break
}
case 1: {
activeTab.value = 1;
break
}
case 2: {
activeTab.value = 2;
break
}
case 3: {
activeTab.value = 3;
break
}
}
}
</script>
<style scoped>
page {
height: 100%;
}
.app-container {
height: 100vh;
width: 750rpx;
background-color: #f5f5f5;
}
.nav-bar {
flex-shrink: 0;
height: 88rpx;
background-color: #4975E9;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tab-container {
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
}
.tab-item {
height: 88rpx;
line-height: 88rpx;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
transition: all 0.3s;
}
.tab-active {
color: #ffffff;
font-size: 16px;
font-weight: bold;
}
</style>
\ No newline at end of file
<template>
<!-- top="xxx"下拉布局往下偏移,防止被悬浮菜单遮住 -->
<mescroll-body @init="mescrollInit" top="85" @down="downCallback" :up="upOption" @up="upCallback"
@emptyclick="emptyClick" :down="downOption">
<view class="container h-full">
<view class="news-count">{{ total }} 条新闻</view>
<view class="news-list">
<view v-for="(item, index) in dataList" :key="index" class="news-item" @click="goToDetails(item)">
<view class="news-image">
<image :src="item.image" :alt="item.contentTitle" mode="aspectFill" />
</view>
<view class="news-content">
<text class="news-title">{{ item.contentTitle }}</text>
<text class="news-date">{{ item.contentDatetime }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList } from "@/network/api/news.js";
import { yjjblbt, rdxw, tyxw, sxxw, tzgg, baseImageUrl } from "@/network/config.js";
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
const { mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: false //是否在初始化后,自动执行downCallback; 默认true
}
const upOption = {
noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
empty: {
tip: '~ 暂无相关数据 ~', // 提示
}
}
const props = defineProps({
dataValue: String,
});
//总条数
const total = ref(0);
const dataList = ref([]);
//下拉刷新
const downCallback = (mescroll : any) => {
dataList.value = [];
mescroll.resetUpScroll(); // 重置列表为第一页 (自动执行 page.num=1, 再触发upCallback方法 )
}
//上拉加载
const upCallback = (mescroll : any) => {
getList(false, mescroll, mescroll.size, mescroll.num);
}
//点击空布局按钮的回调
const emptyClick = () => {
dataList.value = []// 先置空列表,显示加载进度
getMescroll().resetUpScroll() // 再刷新列表数据
}
//获取新闻热点列表
function getList(isDown : boolean, mescroll : any, pageSize : number, pageNo : number) {
const datas = {
contentType: props.dataValue,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
total.value = response.data.total;
const rowsList = response.data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.image = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
if (isDown) {//下拉刷新
dataList.value = rowsList;
mescroll.endSuccess();
} else {//上拉加载
dataList.value = dataList.value.concat(rowsList);
mescroll.endSuccess(rowsList.length);
}
}).catch(() => {
mescroll.endErr(); // 请求失败, 结束加载
})
}
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_zx/modules/detail/index?id=' + item.id + '&dataValue=' + props.dataValue,
});
}
}
</script>
<style>
page {
height: 100%;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
padding: 15rpx 32rpx 10rpx 32rpx;
/* flex: 1; */
/* overflow: auto; */
}
.content-area {
flex: 1;
overflow: auto;
/* padding: 32rpx; */
}
.news-count {
text-align: center;
font-size: 13px;
color: #999999;
margin-bottom: 10rpx;
}
.news-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.news-item {
display: flex;
gap: 24rpx;
padding: 24rpx;
background-color: #ffffff;
border-radius: 16rpx;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.news-image {
width: 200rpx;
height: 160rpx;
flex-shrink: 0;
border-radius: 12rpx;
overflow: hidden;
}
.news-image image {
width: 100%;
height: 100%;
}
.news-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.news-title {
font-size: 16px;
color: #333333;
font-weight: 500;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.news-date {
font-size: 12px;
color: #999999;
}
</style>
\ No newline at end of file
<template>
<!-- top="xxx"下拉布局往下偏移,防止被悬浮菜单遮住 -->
<mescroll-body @init="mescrollInit" top="85" @down="downCallback" :up="upOption" @up="upCallback"
@emptyclick="emptyClick" :down="downOption">
<view class="container h-full">
<view class="news-count">{{ total }} 条通知公告</view>
<view class="news-list">
<view v-for="(item, index) in dataList" :key="index" class="news-item" @click="goToDetails(item.id)">
<!-- <view class="news-image">
<image :src="item.image" :alt="item.contentTitle" mode="aspectFill" />
</view> -->
<view class="news-content">
<text class="news-title">{{ item.contentTitle }}</text>
<text class="news-date mt-3">{{ item.contentDatetime }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList } from "@/network/api/news.js";
import { baseImageUrl } from "@/network/config.js";
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
const { mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: false //是否在初始化后,自动执行downCallback; 默认true
}
const upOption = {
noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
empty: {
tip: '~ 暂无相关数据 ~', // 提示
}
}
const props = defineProps({
dataValue: String,
});
//总条数
const total = ref(0);
const dataList = ref([]);
//下拉刷新
const downCallback = (mescroll : any) => {
dataList.value = [];
mescroll.resetUpScroll(); // 重置列表为第一页 (自动执行 page.num=1, 再触发upCallback方法 )
}
//上拉加载
const upCallback = (mescroll : any) => {
getList(false, mescroll, mescroll.size, mescroll.num);
}
//点击空布局按钮的回调
const emptyClick = () => {
dataList.value = []// 先置空列表,显示加载进度
getMescroll().resetUpScroll() // 再刷新列表数据
}
//获取新闻热点列表
function getList(isDown : boolean, mescroll : any, pageSize : number, pageNo : number) {
const datas = {
contentType: props.dataValue,
pageSize: pageSize,
pageNo: pageNo,
};
getNewsList(datas).then((response) => {
total.value = response.data.total;
const rowsList = response.data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.image = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
if (isDown) {//下拉刷新
dataList.value = rowsList;
mescroll.endSuccess();
} else {//上拉加载
dataList.value = dataList.value.concat(rowsList);
mescroll.endSuccess(rowsList.length);
}
}).catch(() => {
mescroll.endErr(); // 请求失败, 结束加载
})
}
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_zx/modules/detail/index?id=' + item.id + '&dataValue=' + props.dataValue,
});
}
}
</script>
<style>
page {
height: 100%;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
padding: 15rpx 32rpx 10rpx 32rpx;
/* flex: 1; */
/* overflow: auto; */
}
.content-area {
flex: 1;
overflow: auto;
/* padding: 32rpx; */
}
.news-count {
text-align: center;
font-size: 13px;
color: #999999;
margin-bottom: 10rpx;
}
.news-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.news-item {
display: flex;
gap: 24rpx;
padding: 24rpx;
background-color: #ffffff;
border-radius: 16rpx;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.news-image {
width: 200rpx;
height: 160rpx;
flex-shrink: 0;
border-radius: 12rpx;
overflow: hidden;
}
.news-image image {
width: 100%;
height: 100%;
}
.news-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.news-title {
font-size: 16px;
color: #333333;
font-weight: 500;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.news-date {
font-size: 12px;
color: #999999;
}
</style>
\ No newline at end of file
<template>
<view class="container">
<!-- 主要内容区域 -->
<view class="content">
<!-- 标题区域 -->
<text class="title">{{details.title}}</text>
<!-- 作者信息区域 -->
<view class="author-info">
<view class="author-detail">
<view class="publish-info">
<text class="author-name">{{details.author}}</text>
<text class="separator">·</text>
<text class="info-text">{{details.source}}</text>
<text class="separator">·</text>
<text class="info-text">{{details.date}}</text>
<text class="separator">·</text>
<text class="info-text">阅读 {{details.hit}}</text>
</view>
</view>
</view>
<rich-text :nodes="formatRichText(details.content)"></rich-text>
<!-- 相关推荐 -->
<view class="recommendations">
<text class="recommendations-title">相关推荐</text>
<view class="recommendation-list">
<view v-for="(item, index) in dataList" :key="index" class="recommendation-item"
@click="goToDetails(item)">
<image :src="item.image" class="recommendation-image" mode="aspectFill" />
<view class="recommendation-content">
<text class="recommendation-title">{{ item.contentTitle }}</text>
<text class="recommendation-reads">{{ item.contentHit}}阅读</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getNewsList, getNewsDetail, getHit } from "@/network/api/news.js";
import { onLoad } from '@dcloudio/uni-app';
import { qwfb, pyzq, zjjd, flfg, baseImageUrl, dtsy } from "@/network/config.js";
import { formatDate } from "@/utils/dateUtils.js";
const details = ref({});
const dataList = ref([]);
const dataValue = ref('');
function formatRichText(html) { // 富文本内容格式化
return html && html.replace(/<img[^>]*>/gi, function (match, capture) { // 查找所有的 img 元素
return match.replace(/style=".*"/gi, '').replace(/style='.*'/gi, '') // 删除找到的所有 img 元素中的 style 属性
}).replace(/\<img/gi, '<img style="width:100%;"') // 对 img 元素增加 style 属性,并设置宽度为 100%
}
onLoad((options) => {
const data = {
id: options.id,
};
dataValue.value = options.dataValue;
getNewsLists();
//统计点击数量
getHit(options.id);
getNewsDetail(data).then((res) => {
// 将2025-01-08 09:20:55 格式化YYYY-MM-DD格式
res.data.date = formatDate(res.data.date);
details.value = res.data;
});
});
function getContentType() {
const type = dataValue.value;
switch (type) {
case qwfb:
return pyzq;
case pyzq:
return zjjd;
case zjjd:
return flfg;
case flfg:
return dtsy;
case dtsy:
return qwfb;
}
}
//获取相关列表
function getNewsLists() {
const datas = {
contentType: getContentType(),
pageSize: 5,
pageNo: 1,
};
getNewsList(datas).then((response) => {
const rowsList = response.data.rows;
rowsList.forEach((item : any) => {
if (item.contentImg) {
item.image = baseImageUrl(JSON.parse(item.contentImg)[0].path);
}
});
dataList.value = rowsList;
});
}
//详情页面
function goToDetails(item : any) {
if (item.contentOutLink) {
uni.navigateTo({
url: '/pages/web/index?webUrl=' + item.contentOutLink,
});
} else {
uni.navigateTo({
url: '/pages_py/modules/detail/index?id=' + item.id + '&dataValue=' + qwfb,
});
}
}
</script>
<style>
page {
height: 100%;
}
.container {
display: flex;
flex-direction: column;
min-height: 100%;
background-color: #f5f5f5;
}
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 88rpx;
background-color: #2979ff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
z-index: 100;
}
.nav-title {
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
}
.placeholder {
width: 48rpx;
}
.content {
padding: 45rpx 30rpx 30rpx;
}
.title {
font-size: 40rpx;
font-weight: bold;
color: #333333;
line-height: 56rpx;
margin-top: 10rpx;
}
.author-info {
display: flex;
align-items: center;
margin-top: 30rpx;
margin-bottom: 20rpx;
}
.author-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
}
.author-detail {
margin-left: 0rpx;
}
.author-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.publish-info {
display: flex;
align-items: center;
margin-top: 8rpx;
}
.info-text {
font-size: 24rpx;
color: #999999;
}
.separator {
font-size: 24rpx;
color: #999999;
margin: 0 10rpx;
}
.cover-container {
margin-top: 30rpx;
width: 100%;
height: 400rpx;
border-radius: 16rpx;
overflow: hidden;
}
.cover-image {
width: 100%;
height: 100%;
}
.article-content {
margin-top: 40rpx;
}
.paragraph {
font-size: 30rpx;
color: #666666;
line-height: 48rpx;
margin-bottom: 30rpx;
display: block;
}
.content-image-container {
margin: 30rpx 0;
width: 100%;
height: 400rpx;
border-radius: 16rpx;
overflow: hidden;
}
.content-image {
width: 100%;
height: 100%;
}
.recommendations {
margin-top: 60rpx;
}
.recommendations-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
margin-bottom: 30rpx;
display: block;
}
.recommendation-list {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.recommendation-item {
display: flex;
gap: 20rpx;
}
.recommendation-image {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
flex-shrink: 0;
}
.recommendation-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.recommendation-title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.recommendation-reads {
font-size: 24rpx;
color: #999999;
margin-top: 8rpx;
}
</style>
\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment