feat(ai-house): 支持 Markdown 并优化找房交互
This commit is contained in:
@@ -10,8 +10,8 @@ module.exports = {
|
|||||||
|
|
||||||
// 生产环境
|
// 生产环境
|
||||||
serverUrl: 'https://server.websoft.top/api',
|
serverUrl: 'https://server.websoft.top/api',
|
||||||
// apiUrl: 'http://127.0.0.1:9200/api',
|
apiUrl: 'http://127.0.0.1:9200/api',
|
||||||
apiUrl: 'https://cms-api.websoft.top/api',
|
// apiUrl: 'https://cms-api.websoft.top/api',
|
||||||
socketUrl: 'wss://server.websoft.top',
|
socketUrl: 'wss://server.websoft.top',
|
||||||
fileUrl: 'https://file.wsdns.cn',
|
fileUrl: 'https://file.wsdns.cn',
|
||||||
|
|
||||||
|
|||||||
+22
-197
@@ -2,14 +2,6 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-toolbar">
|
<view class="page-toolbar">
|
||||||
<view class="page-toolbar-title">AI找房助手</view>
|
<view class="page-toolbar-title">AI找房助手</view>
|
||||||
<view
|
|
||||||
class="clear-session-btn"
|
|
||||||
:class="{ 'clear-session-btn-disabled': loading }"
|
|
||||||
@click="confirmClearSession"
|
|
||||||
>
|
|
||||||
<u-icon name="trash" size="18" color="#667085"></u-icon>
|
|
||||||
<text class="clear-session-text">清空</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="message-scroll">
|
<view class="message-scroll">
|
||||||
<view class="message-content">
|
<view class="message-content">
|
||||||
@@ -41,7 +33,7 @@
|
|||||||
></image>
|
></image>
|
||||||
<view :class="['message-bubble', message.role === 'user' ? 'bubble-user' : 'bubble-ai']">
|
<view :class="['message-bubble', message.role === 'user' ? 'bubble-user' : 'bubble-ai']">
|
||||||
<view v-if="message.type === 'form'" class="form-message">
|
<view v-if="message.type === 'form'" class="form-message">
|
||||||
<view class="form-title">{{ message.content || '您好,请填写您的找房需求' }}</view>
|
<!-- <view class="form-title">{{ message.content || '您好,请填写您的找房需求' }}</view> -->
|
||||||
<view class="form-desc">填写楼盘、面积和预算,我来帮您筛选合适的房源。</view>
|
<view class="form-desc">填写楼盘、面积和预算,我来帮您筛选合适的房源。</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">楼盘</text>
|
<text class="form-label">楼盘</text>
|
||||||
@@ -118,7 +110,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="message.type === 'text'" class="text-message">
|
<view v-else-if="message.type === 'text'" class="text-message">
|
||||||
<view user-select>{{ message.content }}</view>
|
<mp-html
|
||||||
|
v-if="message.role === 'ai'"
|
||||||
|
:content="message.content"
|
||||||
|
markdown
|
||||||
|
/>
|
||||||
|
<view v-else user-select>{{ message.content }}</view>
|
||||||
<!-- <view-->
|
<!-- <view-->
|
||||||
<!-- v-if="message.content"-->
|
<!-- v-if="message.content"-->
|
||||||
<!-- class="copy-btn"-->
|
<!-- class="copy-btn"-->
|
||||||
@@ -132,25 +129,6 @@
|
|||||||
<!-- <text class="copy-text">复制</text>-->
|
<!-- <text class="copy-text">复制</text>-->
|
||||||
<!-- </view>-->
|
<!-- </view>-->
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="message.type === 'location'" class="location-message">
|
|
||||||
<view class="result-title">{{ message.content }}</view>
|
|
||||||
<view v-for="item in message.locationCards" :key="item.locationId" class="location-card">
|
|
||||||
<view class="location-card-title">{{ item.locationName }}</view>
|
|
||||||
<view v-if="item.parentLocationName || item.city" class="location-card-path">
|
|
||||||
{{ [item.city, item.parentLocationName].filter(Boolean).join(' / ') }}
|
|
||||||
</view>
|
|
||||||
<view v-if="item.tags && item.tags.length" class="location-tag-row">
|
|
||||||
<text v-for="tag in item.tags" :key="tag" class="location-tag">{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-for="knowledge in item.knowledgeItems" :key="knowledge.topic" class="location-knowledge-item">
|
|
||||||
<view class="location-knowledge-title">{{ knowledge.title }}</view>
|
|
||||||
<view v-if="locationKnowledgeFacts(knowledge)" class="location-knowledge-facts">
|
|
||||||
{{ locationKnowledgeFacts(knowledge) }}
|
|
||||||
</view>
|
|
||||||
<view v-if="knowledge.content" class="location-knowledge-content">{{ knowledge.content }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-else-if="message.type === 'contact'" class="contact-message">
|
<view v-else-if="message.type === 'contact'" class="contact-message">
|
||||||
<button class="contact-btn" open-type="contact">{{ message.content || '联系客服' }}</button>
|
<button class="contact-btn" open-type="contact">{{ message.content || '联系客服' }}</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -218,6 +196,14 @@
|
|||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</view>
|
</view>
|
||||||
|
<view
|
||||||
|
class="clear-session-btn clear-session-btn-inline"
|
||||||
|
:class="{ 'clear-session-btn-disabled': loading }"
|
||||||
|
@click="confirmClearSession"
|
||||||
|
>
|
||||||
|
<u-icon name="trash" size="18" color="#667085"></u-icon>
|
||||||
|
<text class="clear-session-text">清空</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="input-row">
|
<view class="input-row">
|
||||||
<input
|
<input
|
||||||
@@ -260,15 +246,15 @@
|
|||||||
quickQuestions: [
|
quickQuestions: [
|
||||||
{
|
{
|
||||||
label: '面积和价格',
|
label: '面积和价格',
|
||||||
question: '这套房的面积和价格是多少?'
|
question: '介绍一下所有楼盘的面积和价格。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '物业和水电',
|
label: '物业和水电',
|
||||||
question: '这套房的物业费和水电怎么收费?'
|
question: '介绍一下所有楼盘的物业费和水电收费情况。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '空调和停车',
|
label: '空调和停车',
|
||||||
question: '这套房有空调和停车位吗?'
|
question: '介绍一下所有楼盘是否配备空调和停车位。'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
inputText: '',
|
inputText: '',
|
||||||
@@ -654,84 +640,6 @@
|
|||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
normalizeLocationCards(value) {
|
|
||||||
return this.normalizeStructuredArray(value)
|
|
||||||
.map((item, index) => {
|
|
||||||
if (!item || typeof item !== 'object') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
locationId: item.locationId ?? item.location_id ?? `location-${index}`,
|
|
||||||
locationName: item.locationName ?? item.location_name ?? item.name ?? '地点资料',
|
|
||||||
parentLocationName: item.parentLocationName ?? item.parent_location_name ?? '',
|
|
||||||
tags: this.normalizeLocationTags(item.tags),
|
|
||||||
knowledgeItems: this.normalizeLocationKnowledgeItems(
|
|
||||||
item.knowledgeItems !== undefined ? item.knowledgeItems : item.knowledge_items
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
},
|
|
||||||
normalizeStructuredArray(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(value)
|
|
||||||
return Array.isArray(parsed) ? parsed : (parsed && typeof parsed === 'object' ? [parsed] : [])
|
|
||||||
} catch (error) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value && typeof value === 'object' ? [value] : []
|
|
||||||
},
|
|
||||||
normalizeLocationTags(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.filter(Boolean).map((item) => String(item))
|
|
||||||
}
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const tags = this.normalizeStructuredArray(value)
|
|
||||||
if (tags.length) {
|
|
||||||
return tags.filter(Boolean).map((item) => String(item))
|
|
||||||
}
|
|
||||||
return value.split(/[,,]/).map((item) => item.trim()).filter(Boolean)
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
normalizeLocationKnowledgeItems(value) {
|
|
||||||
return this.normalizeStructuredArray(value)
|
|
||||||
.map((item, index) => {
|
|
||||||
if (!item || typeof item !== 'object') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
topic: item.topic || `knowledge-${index}`,
|
|
||||||
name: item.name || '',
|
|
||||||
title: item.title || '补充信息',
|
|
||||||
propertyCompany: item.propertyCompany ?? item.property_company,
|
|
||||||
propertyFees: item.propertyFees ?? item.property_fees,
|
|
||||||
waterBillingType: item.waterBillingType ?? item.water_billing_type,
|
|
||||||
waterUnitPrice: item.waterUnitPrice ?? item.water_unit_price,
|
|
||||||
electricityBillingType: item.electricityBillingType ?? item.electricity_billing_type,
|
|
||||||
electricityUnitPrice: item.electricityUnitPrice ?? item.electricity_unit_price,
|
|
||||||
parkingAvailable: this.normalizeBoolean(item.parkingAvailable ?? item.parking_available),
|
|
||||||
parkingFee: item.parkingFee ?? item.parking_fee
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
},
|
|
||||||
normalizeBoolean(value) {
|
|
||||||
if (value === 'true' || value === 1 || value === '1') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (value === 'false' || value === 0 || value === '0') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
connectWebSocket() {
|
connectWebSocket() {
|
||||||
if (!this.loginUserId) {
|
if (!this.loginUserId) {
|
||||||
return
|
return
|
||||||
@@ -847,17 +755,6 @@
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const locationCards = this.normalizeLocationCards(
|
|
||||||
data.locationCards !== undefined ? data.locationCards : data.location_cards
|
|
||||||
)
|
|
||||||
if (locationCards.length) {
|
|
||||||
this.messages.push(
|
|
||||||
this.createMessage('ai', 'location', {
|
|
||||||
content: '以下地点资料来自已维护的知识库:',
|
|
||||||
locationCards
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (data.showContactForm) {
|
if (data.showContactForm) {
|
||||||
this.messages.push(this.createMessage('ai', 'messageForm'))
|
this.messages.push(this.createMessage('ai', 'messageForm'))
|
||||||
}
|
}
|
||||||
@@ -872,19 +769,6 @@
|
|||||||
url: `/sub_pages/house/detail?houseId=${item.houseId}`
|
url: `/sub_pages/house/detail?houseId=${item.houseId}`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
locationKnowledgeFacts(item) {
|
|
||||||
const facts = []
|
|
||||||
if (item.propertyCompany) facts.push(`物业公司:${item.propertyCompany}`)
|
|
||||||
if (item.propertyFees !== null && item.propertyFees !== undefined) facts.push(`物业费:${item.propertyFees}`)
|
|
||||||
if (item.waterBillingType) facts.push(`水费:${item.waterBillingType}`)
|
|
||||||
if (item.waterUnitPrice !== null && item.waterUnitPrice !== undefined) facts.push(`水费单价:${item.waterUnitPrice}`)
|
|
||||||
if (item.electricityBillingType) facts.push(`电费:${item.electricityBillingType}`)
|
|
||||||
if (item.electricityUnitPrice !== null && item.electricityUnitPrice !== undefined) facts.push(`电费单价:${item.electricityUnitPrice}`)
|
|
||||||
if (item.parkingAvailable === true) facts.push('停车:可用')
|
|
||||||
if (item.parkingAvailable === false) facts.push('停车:不可用')
|
|
||||||
if (item.parkingFee) facts.push(`停车费用:${item.parkingFee}`)
|
|
||||||
return facts.join(';')
|
|
||||||
},
|
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -939,6 +823,10 @@
|
|||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clear-session-btn-inline {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.message-scroll {
|
.message-scroll {
|
||||||
padding: 24rpx 24rpx 0;
|
padding: 24rpx 24rpx 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -1055,69 +943,6 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-message {
|
|
||||||
padding: 20rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
background: #f8fbff;
|
|
||||||
border: 1rpx solid #e6eefc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card {
|
|
||||||
margin-top: 16rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
background: #ffffff;
|
|
||||||
border: 1rpx solid #e9edf5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card-title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card-path {
|
|
||||||
margin-top: 8rpx;
|
|
||||||
font-size: 23rpx;
|
|
||||||
color: #667085;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-tag-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8rpx;
|
|
||||||
margin-top: 14rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-tag {
|
|
||||||
padding: 4rpx 12rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
background: #eef6ff;
|
|
||||||
color: #2f5f9f;
|
|
||||||
font-size: 21rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-knowledge-item {
|
|
||||||
margin-top: 16rpx;
|
|
||||||
padding-top: 16rpx;
|
|
||||||
border-top: 1rpx solid #eef1f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-knowledge-title {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #344054;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-knowledge-facts,
|
|
||||||
.location-knowledge-content {
|
|
||||||
margin-top: 8rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 1.55;
|
|
||||||
color: #667085;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-message {
|
.faq-message {
|
||||||
padding: 8rpx 0 0;
|
padding: 8rpx 0 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,47 +29,29 @@ function createPageVm() {
|
|||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveLocationResponse(fieldName, locationCards) {
|
|
||||||
const vm = createPageVm()
|
const vm = createPageVm()
|
||||||
const response = {
|
vm.handleSocketMessage(JSON.stringify({
|
||||||
type: 'house_ai_result',
|
type: 'house_ai_result',
|
||||||
answer: '已按已维护的地点资料筛选,以下地点可供参考。'
|
answer: '航洋城附近通勤方便,停车以现场政策为准。',
|
||||||
}
|
locationCards: [{
|
||||||
response[fieldName] = locationCards
|
locationId: 101,
|
||||||
vm.handleSocketMessage(JSON.stringify(response))
|
locationName: '航洋城',
|
||||||
return vm.messages.find((message) => message.type === 'location')
|
knowledgeItems: [{ title: '停车' }]
|
||||||
}
|
}],
|
||||||
|
houses: [{
|
||||||
const locationCard = {
|
houseId: 201,
|
||||||
location_id: 101,
|
houseTitle: '航洋城附近两居室',
|
||||||
city: '南宁市',
|
files: []
|
||||||
location_name: '五象航洋城',
|
|
||||||
parent_location_name: '五象新区',
|
|
||||||
tags: ['停车方便'],
|
|
||||||
knowledge_items: [{
|
|
||||||
topic: 'parking',
|
|
||||||
title: '停车',
|
|
||||||
parking_available: true,
|
|
||||||
parking_fee: '月租 300 元'
|
|
||||||
}]
|
}]
|
||||||
}
|
}))
|
||||||
|
|
||||||
const locationMessage = receiveLocationResponse('location_cards', JSON.stringify([locationCard]))
|
assert.equal(vm.messages.filter((message) => message.type === 'location').length, 0,
|
||||||
|
'地点资料不应在小程序中创建卡片消息')
|
||||||
|
assert.equal(vm.messages.filter((message) => message.type === 'text').length, 1,
|
||||||
|
'地点资料应由 AI 直接组织为文本回答')
|
||||||
|
assert.equal(vm.messages.find((message) => message.type === 'text').content,
|
||||||
|
'航洋城附近通勤方便,停车以现场政策为准。')
|
||||||
|
assert.equal(vm.messages.filter((message) => message.type === 'list').length, 1,
|
||||||
|
'房源结果仍应创建房源卡片消息')
|
||||||
|
|
||||||
assert.ok(locationMessage, '地点咨询回包应创建地点卡片消息')
|
console.log('地点资料仅由 AI 组织为文本,页面不渲染地点卡片')
|
||||||
assert.ok(Array.isArray(locationMessage.locationCards), '地点卡片应转换为数组')
|
|
||||||
assert.equal(locationMessage.locationCards[0].locationId, 101)
|
|
||||||
assert.equal(locationMessage.locationCards[0].locationName, '五象航洋城')
|
|
||||||
assert.equal(locationMessage.locationCards[0].knowledgeItems[0].parkingAvailable, true)
|
|
||||||
|
|
||||||
const backendLocationMessage = receiveLocationResponse('locationCards', [{
|
|
||||||
locationId: 102,
|
|
||||||
city: '南宁市',
|
|
||||||
locationName: '会展航洋城',
|
|
||||||
knowledgeItems: []
|
|
||||||
}])
|
|
||||||
|
|
||||||
assert.ok(backendLocationMessage, '后端驼峰地点卡片回包应保持兼容')
|
|
||||||
assert.equal(backendLocationMessage.locationCards[0].locationName, '会展航洋城')
|
|
||||||
|
|
||||||
console.log('地点咨询回包能够创建可渲染的地点卡片消息')
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
const pagePath = path.join(root, 'pages/ai-house/index.vue')
|
||||||
|
const componentPath = path.join(root, 'uni_modules/mp-html/components/mp-html/mp-html.vue')
|
||||||
|
const markdownPluginPath = path.join(root, 'uni_modules/mp-html/components/mp-html/markdown/index.js')
|
||||||
|
|
||||||
|
const page = fs.readFileSync(pagePath, 'utf8')
|
||||||
|
const component = fs.readFileSync(componentPath, 'utf8')
|
||||||
|
|
||||||
|
assert.match(page, /v-if="message\.role === 'ai'"[\s\S]*:content="message\.content"[\s\S]*markdown/)
|
||||||
|
assert.match(component, /markdown:\s*Boolean/)
|
||||||
|
assert.match(component, /require\('\.\/markdown\/index\.js'\)/)
|
||||||
|
|
||||||
|
const Markdown = require(markdownPluginPath)
|
||||||
|
const html = new Markdown({ markdown: true }).onUpdate('# 标题\n\n**重点**\n\n- 房源 A')
|
||||||
|
|
||||||
|
assert.match(html, /<h1[^>]*>标题<\/h1>/)
|
||||||
|
assert.match(html, /<strong>重点<\/strong>/)
|
||||||
|
assert.match(html, /<li>房源 A<\/li>/)
|
||||||
|
|
||||||
|
console.log('AI 找房 Markdown 渲染配置检查通过')
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
|
||||||
|
const pagePath = path.resolve(__dirname, '../pages/ai-house/index.vue')
|
||||||
|
const page = fs.readFileSync(pagePath, 'utf8')
|
||||||
|
|
||||||
|
for (const label of ['面积和价格', '物业和水电', '空调和停车']) {
|
||||||
|
assert.match(page, new RegExp(`label: '${label}'`))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const question of [
|
||||||
|
'介绍一下所有楼盘的面积和价格。',
|
||||||
|
'介绍一下所有楼盘的物业费和水电收费情况。',
|
||||||
|
'介绍一下所有楼盘是否配备空调和停车位。'
|
||||||
|
]) {
|
||||||
|
assert.ok(page.includes(`question: '${question}'`))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(!page.includes('这套房的面积和价格是多少?'))
|
||||||
|
assert.ok(!page.includes('这套房的物业费和水电怎么收费?'))
|
||||||
|
assert.ok(!page.includes('这套房有空调和停车位吗?'))
|
||||||
|
|
||||||
|
console.log('AI 找房全量楼盘快捷提问检查通过')
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview markdown 插件
|
||||||
|
* Include marked (https://github.com/markedjs/marked)
|
||||||
|
* Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
|
||||||
|
*/
|
||||||
|
const marked = require('./marked.min')
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
function Markdown (vm) {
|
||||||
|
this.vm = vm
|
||||||
|
vm._ids = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Markdown.prototype.onUpdate = function (content) {
|
||||||
|
if (this.vm.markdown) {
|
||||||
|
return marked(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Markdown.prototype.onParse = function (node, vm) {
|
||||||
|
if (vm.options.markdown) {
|
||||||
|
// 中文 id 需要转换,否则无法跳转
|
||||||
|
if (vm.options.useAnchor && node.attrs && /[\u4e00-\u9fa5]/.test(node.attrs.id)) {
|
||||||
|
const id = 't' + index++
|
||||||
|
this.vm._ids[node.attrs.id] = id
|
||||||
|
node.attrs.id = id
|
||||||
|
}
|
||||||
|
if (node.name === 'p' || node.name === 'table' || node.name === 'tr' || node.name === 'th' || node.name === 'td' || node.name === 'blockquote' || node.name === 'pre' || node.name === 'code') {
|
||||||
|
node.attrs.class = `md-${node.name} ${node.attrs.class || ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Markdown
|
||||||
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@
|
|||||||
// #ifndef APP-PLUS-NVUE
|
// #ifndef APP-PLUS-NVUE
|
||||||
import node from './node/node'
|
import node from './node/node'
|
||||||
// #endif
|
// #endif
|
||||||
const plugins=[]
|
const plugins=[require('./markdown/index.js')]
|
||||||
const Parser = require('./parser')
|
const Parser = require('./parser')
|
||||||
// #ifdef APP-PLUS-NVUE
|
// #ifdef APP-PLUS-NVUE
|
||||||
const dom = weex.requireModule('dom')
|
const dom = weex.requireModule('dom')
|
||||||
@@ -55,6 +55,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
markdown: Boolean,
|
||||||
containerStyle: {
|
containerStyle: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -152,6 +153,7 @@ export default {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
navigateTo (id, offset) {
|
navigateTo (id, offset) {
|
||||||
|
id = this._ids[decodeURI(id)] || id
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.useAnchor) {
|
if (!this.useAnchor) {
|
||||||
reject(Error('Anchor is disabled'))
|
reject(Error('Anchor is disabled'))
|
||||||
|
|||||||
Reference in New Issue
Block a user