feat(商品sku)
This commit is contained in:
parent
b999d59bfc
commit
3f51efcebe
|
@ -1,28 +1,31 @@
|
|||
{
|
||||
"name" : "mall-app-t",
|
||||
"appid" : "__UNI__B201544",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
"name": "mall-app-t",
|
||||
"appid": "__UNI__B201544",
|
||||
"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
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
"compatible": {
|
||||
"ignoreVersion": true
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
"modules": {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
"distribute": {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"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\"/>",
|
||||
|
@ -41,32 +44,32 @@
|
|||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
"ios": {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
"quickapp": {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx64387dc8bba916ec",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
"mp-weixin": {
|
||||
"appid": "wx64387dc8bba916ec",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents" : true
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
"vueVersion": "3"
|
||||
}
|
||||
|
|
|
@ -1,20 +1,42 @@
|
|||
<template>
|
||||
<pb-layout class="product-list" title="产品" navbar="normal" tabbar="/pages/index/product" :bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white" color="black">
|
||||
<pb-layout
|
||||
class="product-list"
|
||||
title="产品"
|
||||
navbar="normal"
|
||||
tabbar="/pages/index/product"
|
||||
:bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white"
|
||||
color="black"
|
||||
>
|
||||
<view v-if="state.pagination.total > 0" class="goods-list ss-m-t-20">
|
||||
<view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
|
||||
<p-goods-column size="lg" :data="item" :topRadius="10" :bottomRadius="10"
|
||||
@click="peach.$router.go('/pages/product/manageGoods', { id: item.id, mark: 'detail' })" />
|
||||
<p-goods-column
|
||||
size="lg"
|
||||
:data="item"
|
||||
:topRadius="10"
|
||||
:bottomRadius="10"
|
||||
@click="peach.$router.go('/pages/product/manageGoods', { id: item.id, mark: 'detail' })"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
|
||||
<uni-load-more
|
||||
v-if="state.pagination.total > 0"
|
||||
:status="state.loadStatus"
|
||||
:content-text="{
|
||||
contentdown: '上拉加载更多',
|
||||
}" @click="loadMore" />
|
||||
}"
|
||||
@click="loadMore"
|
||||
/>
|
||||
|
||||
<view class="_icon-add-round add-product" @click="addGoods"></view>
|
||||
|
||||
<p-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无产品" bgColor="transparent" />
|
||||
<p-empty
|
||||
v-if="state.pagination.total === 0"
|
||||
icon="/static/soldout-empty.png"
|
||||
text="暂无产品"
|
||||
bgColor="transparent"
|
||||
/>
|
||||
</pb-layout>
|
||||
</template>
|
||||
|
||||
|
@ -67,8 +89,13 @@ async function getList() {
|
|||
}
|
||||
|
||||
function addGoods() {
|
||||
peach.$store('trade').$patch({
|
||||
selectedProperty: null,
|
||||
goodsInfo: null,
|
||||
skus: null,
|
||||
})
|
||||
peach.$router.go('/pages/product/manageGoods', {
|
||||
title: '添加商品'
|
||||
title: '添加商品',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<view></view>
|
||||
<view>页面重定向</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import $store from '@/peach/store'
|
||||
import peach from '@/peach'
|
||||
|
||||
const userStore = $store('user')
|
||||
|
||||
async function redirectFn() {
|
||||
const userStore = peach.$store('user')
|
||||
|
||||
// 判断是否登录
|
||||
if (!userStore.isLogin) {
|
||||
userStore.logOut()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div class="sku-item">
|
||||
<uni-forms label-width="176rpx" label-position="left">
|
||||
|
||||
<template v-if="specType">
|
||||
<template v-for="item in formData.properties">
|
||||
<uni-forms-item :label="item.propertyName">
|
||||
|
@ -11,31 +10,77 @@
|
|||
</template>
|
||||
|
||||
<uni-forms-item label="商品封面图" name="picUrl" label-position="top">
|
||||
<p-uploader v-model:url="formData.picUrl" :readonly="!canEdit" fileMediatype="image" limit="1" mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }" />
|
||||
<p-uploader
|
||||
v-model:url="formData.picUrl"
|
||||
:readonly="!canEdit"
|
||||
fileMediatype="image"
|
||||
limit="1"
|
||||
mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品条码" name="barCode">
|
||||
<uni-easyinput type="text" trim="all" v-model="formData.barCode" :disabled="!canEdit" placeholder="请输入商品条码" />
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
trim="all"
|
||||
v-model="formData.barCode"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品条码"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="销售价" name="price">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.price" :disabled="!canEdit" placeholder="请输入商品销售价" />
|
||||
<uni-easyinput
|
||||
type="digit"
|
||||
trim="all"
|
||||
v-model="formData.price"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品销售价"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="市场价" name="marketPrice">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.marketPrice" :disabled="!canEdit"
|
||||
placeholder="请输入商品销售价" />
|
||||
<uni-easyinput
|
||||
type="digit"
|
||||
trim="all"
|
||||
v-model="formData.marketPrice"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品销售价"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="成本价" name="costPrice">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.costPrice" :disabled="!canEdit"
|
||||
placeholder="请输入商品销售价" />
|
||||
<uni-easyinput
|
||||
type="digit"
|
||||
trim="all"
|
||||
v-model="formData.costPrice"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品销售价"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="库存" name="stock">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.stock" :disabled="!canEdit" placeholder="请输入商品库存" />
|
||||
<uni-easyinput
|
||||
type="number"
|
||||
trim="all"
|
||||
v-model="formData.stock"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品库存"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="重量(kg)" name="weight">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.weight" :disabled="!canEdit" placeholder="请输入商品重量" />
|
||||
<uni-easyinput
|
||||
type="digit"
|
||||
trim="all"
|
||||
v-model="formData.weight"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品重量"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="体积(m³)" name="volume">
|
||||
<uni-easyinput type="number" trim="all" v-model="formData.volume" :disabled="!canEdit" placeholder="请输入商品体积" />
|
||||
<uni-easyinput
|
||||
type="digit"
|
||||
trim="all"
|
||||
v-model="formData.volume"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品体积"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</div>
|
||||
|
@ -44,27 +89,29 @@
|
|||
<script setup>
|
||||
import { ref, watch, computed, defineProps } from 'vue'
|
||||
import peach from '@/peach'
|
||||
import { canEdit } from '../js/sku';
|
||||
import { canEdit } from '../js/sku'
|
||||
const props = defineProps({
|
||||
skus: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const formData = ref({})
|
||||
const specType = computed(() => peach.$store("trade").goodsInfo.specType);
|
||||
const specType = computed(() => peach.$store('trade').goodsInfo?.specType || false)
|
||||
|
||||
watch(() => props.skus, (newVal) => {
|
||||
watch(
|
||||
() => props.skus,
|
||||
(newVal) => {
|
||||
console.log(newVal)
|
||||
// 如果是单规格,取 sku 第一条数据
|
||||
if (!specType.value) {
|
||||
formData.value = newVal[0] ?? {}
|
||||
if (newVal) formData.value = newVal[0] ?? {}
|
||||
return
|
||||
}
|
||||
formData.value = newVal ?? {}
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -75,6 +122,5 @@ watch(() => props.skus, (newVal) => {
|
|||
|
||||
.sku-item:first-child {
|
||||
border-top: 1px solid var(--ui-BG-Main);
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,3 +8,27 @@ export const SPEC_TYPE = [
|
|||
value: true,
|
||||
},
|
||||
]
|
||||
|
||||
// sku 相关属性校验
|
||||
export const SKU_RULE_CONFIG = [
|
||||
{
|
||||
name: 'stock',
|
||||
rule: (arg) => arg >= 0,
|
||||
message: '商品库存必须大于等于 1 !!!',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
rule: (arg) => arg >= 0.01,
|
||||
message: '商品销售价格必须大于等于 0.01 元!!!',
|
||||
},
|
||||
{
|
||||
name: 'marketPrice',
|
||||
rule: (arg) => arg >= 0.01,
|
||||
message: '商品市场价格必须大于等于 0.01 元!!!',
|
||||
},
|
||||
{
|
||||
name: 'costPrice',
|
||||
rule: (arg) => arg >= 0.01,
|
||||
message: '商品成本价格必须大于等于 0.00 元!!!',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,96 +1,97 @@
|
|||
import { ref, computed } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import peach from "@/peach";
|
||||
import GoodsApi from "@/peach/api/trade/goods";
|
||||
import { SPEC_TYPE } from "./config";
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import peach from '@/peach'
|
||||
import GoodsApi from '@/peach/api/trade/goods'
|
||||
import { SPEC_TYPE, SKU_RULE_CONFIG } from './config'
|
||||
|
||||
const pickerRef = ref(null);
|
||||
const pickerRef = ref(null)
|
||||
|
||||
// 多属性商品 sku 列表
|
||||
const skus = ref([]);
|
||||
const skus = ref([])
|
||||
|
||||
const propertyList = ref([]);
|
||||
const propertyList = ref([])
|
||||
|
||||
const goodsPropertyList = ref([]);
|
||||
const goodsPropertyList = ref([])
|
||||
|
||||
const propertyListRef = ref(null);
|
||||
const propertyListRef = ref(null)
|
||||
|
||||
const canEdit = computed(() => peach.$store("trade").canEdit);
|
||||
const canEdit = computed(() => peach.$store('trade').canEdit)
|
||||
|
||||
const formData = ref({
|
||||
specType: true,
|
||||
specText: SPEC_TYPE[0].label,
|
||||
});
|
||||
})
|
||||
|
||||
async function showPropertyList() {
|
||||
await getGoodsProperty();
|
||||
propertyListRef.value.onOpen();
|
||||
await getGoodsProperty()
|
||||
propertyListRef.value.onOpen()
|
||||
}
|
||||
|
||||
function onRDPickerConfirm(e) {
|
||||
peach.$store("trade").specType = SPEC_TYPE[e.value[0]].value;
|
||||
formData.value.specText = SPEC_TYPE[e.value[0]].label;
|
||||
formData.value.specType = SPEC_TYPE[e.value[0]].value;
|
||||
formData.value.specType = SPEC_TYPE[e.value[0]].value
|
||||
formData.value.specText = SPEC_TYPE[e.value[0]].label
|
||||
|
||||
// 如果商品规格不一致,则需要重新初始化 sku 列表
|
||||
initSku()
|
||||
|
||||
peach.$store('trade').specType = SPEC_TYPE[e.value[0]].value
|
||||
}
|
||||
|
||||
function pickerProperty() {
|
||||
if (canEdit.value) {
|
||||
let index = specType.value ? 1 : 0;
|
||||
pickerRef.value.onOpen([index]);
|
||||
let index = formData.value.specType ? 1 : 0
|
||||
|
||||
console.log(index)
|
||||
|
||||
pickerRef.value.onOpen([index])
|
||||
}
|
||||
}
|
||||
|
||||
async function onPropertyConfirm(e) {
|
||||
await getGoodsProperty();
|
||||
console.log(e);
|
||||
await getGoodsProperty()
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
console.log(skus.value);
|
||||
console.log(skus.value)
|
||||
}
|
||||
|
||||
async function getGoodsProperty() {
|
||||
let { data } = await GoodsApi.getHistoryProperty();
|
||||
let { data } = await GoodsApi.getHistoryProperty()
|
||||
|
||||
// 把 propertyList 中 id 相同的属性合并,并去重
|
||||
propertyList.value = peach.$store("trade").selectedProperty;
|
||||
propertyList.value = peach.$store('trade').selectedProperty
|
||||
|
||||
console.log(propertyList.value);
|
||||
console.log(propertyList.value)
|
||||
|
||||
// 根据已经选择数据,设置默认选中
|
||||
data.forEach((item) => {
|
||||
// 判断属性是否已经选中
|
||||
let propertyParent = propertyList.value.find(
|
||||
(sitem) => sitem?.id === item.id
|
||||
);
|
||||
let propertyParent = propertyList.value.find((sitem) => sitem?.id === item.id)
|
||||
|
||||
item.checked = propertyParent ? true : false;
|
||||
item.checked = propertyParent ? true : false
|
||||
|
||||
// 如果属性已经选中,查询子类中是否有选中
|
||||
if (item.checked) {
|
||||
item.propertyValues.forEach((child) => {
|
||||
let childResult = propertyParent?.children.some(
|
||||
(schild) => schild === child.id
|
||||
);
|
||||
child.checked = childResult ? true : false;
|
||||
});
|
||||
let childResult = propertyParent?.children.some((schild) => schild === child.id)
|
||||
child.checked = childResult ? true : false
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
goodsPropertyList.value = data;
|
||||
console.log(goodsPropertyList.value);
|
||||
goodsPropertyList.value = data
|
||||
console.log(goodsPropertyList.value)
|
||||
}
|
||||
|
||||
function changeSubProperty() {
|
||||
// 修改子属性状态,需要同步更新 skus 的显示
|
||||
console.log(goodsPropertyList.value);
|
||||
console.log(goodsPropertyList.value)
|
||||
// 过滤父属性 checked 选项,深拷贝避免后面循环改变元数据内容
|
||||
let temp = JSON.parse(
|
||||
JSON.stringify(goodsPropertyList.value.filter((item) => item.checked))
|
||||
);
|
||||
let temp = JSON.parse(JSON.stringify(goodsPropertyList.value.filter((item) => item.checked)))
|
||||
temp.forEach((item) => {
|
||||
item.propertyValues = item.propertyValues.filter((child) => child.checked);
|
||||
});
|
||||
item.propertyValues = item.propertyValues.filter((child) => child.checked)
|
||||
})
|
||||
|
||||
let result = temp.map((item) => {
|
||||
return item.propertyValues.map((child) => ({
|
||||
|
@ -98,15 +99,15 @@ function changeSubProperty() {
|
|||
propertyName: item.name,
|
||||
valueId: child.id,
|
||||
valueName: child.name,
|
||||
}));
|
||||
});
|
||||
}))
|
||||
})
|
||||
|
||||
let tempSkus = [];
|
||||
let tempSkus = []
|
||||
|
||||
for (let item of reduceArr(result)) {
|
||||
let obj = {
|
||||
picUrl: "",
|
||||
barCode: "",
|
||||
picUrl: '',
|
||||
barCode: '',
|
||||
price: 0,
|
||||
marketPrice: 0,
|
||||
costPrice: 0,
|
||||
|
@ -114,51 +115,146 @@ function changeSubProperty() {
|
|||
weight: 0,
|
||||
volume: 0,
|
||||
properties: item,
|
||||
};
|
||||
tempSkus.push(obj);
|
||||
}
|
||||
tempSkus.push(obj)
|
||||
}
|
||||
|
||||
skus.value = tempSkus;
|
||||
skus.value = tempSkus
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Ankkaya
|
||||
* @description 新增商品初始化商品 sku
|
||||
* @param {Type} -
|
||||
* @returns {Type}
|
||||
*/
|
||||
function initSku() {
|
||||
// 单规格
|
||||
if (!formData.value.specType) {
|
||||
let obj = {
|
||||
picUrl: '',
|
||||
barCode: '',
|
||||
price: 0,
|
||||
marketPrice: 0,
|
||||
costPrice: 0,
|
||||
stock: 0,
|
||||
weight: null,
|
||||
volume: null,
|
||||
properties: [
|
||||
{
|
||||
propertyId: 0,
|
||||
propertyName: '默认',
|
||||
valueId: 0,
|
||||
valueName: '默认',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
skus.value = [obj]
|
||||
} else {
|
||||
// 多规格
|
||||
skus.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Ankkaya
|
||||
* @description 确认属性
|
||||
* @param {Type} -
|
||||
* @returns {Type}
|
||||
*/
|
||||
function submitProperty() {
|
||||
try {
|
||||
validateSku(skus.value)
|
||||
peach.$store('trade').skus = skus.value
|
||||
peach.$router.back()
|
||||
} catch (e) {
|
||||
console.log(skus.value)
|
||||
console.log(e, '校验失败')
|
||||
}
|
||||
}
|
||||
|
||||
function validateSku(skus) {
|
||||
let warningInfo = '请检查商品各行相关属性配置,'
|
||||
let validateStatue = true
|
||||
let skusValue = skus ?? peach.$store('trade').skus
|
||||
for (const sku of skusValue) {
|
||||
for (const rule of SKU_RULE_CONFIG) {
|
||||
const arg = getValue(sku, rule.name)
|
||||
if (!rule.rule(arg)) {
|
||||
validateStatue = false
|
||||
warningInfo += rule.message
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!validateStatue) {
|
||||
uni.showToast({
|
||||
title: warningInfo,
|
||||
icon: 'none',
|
||||
duration: 4000,
|
||||
})
|
||||
throw new Error(warningInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(obj, arg) {
|
||||
const keys = arg.split('.')
|
||||
let value = obj
|
||||
for (const key of keys) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = value[key]
|
||||
} else {
|
||||
value = undefined
|
||||
break
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function reduceArr(arr) {
|
||||
return arr.reduce((acc, cur) => {
|
||||
let tempAcc = [];
|
||||
let tempAcc = []
|
||||
|
||||
if (acc.length < 1) {
|
||||
cur.forEach((item, index) => {
|
||||
if (tempAcc[index]) {
|
||||
tempAcc[index].push(item);
|
||||
tempAcc[index].push(item)
|
||||
} else {
|
||||
tempAcc[index] = [item];
|
||||
tempAcc[index] = [item]
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
acc.forEach((item, index) => {
|
||||
cur.forEach((sitem, sindex) => {
|
||||
tempAcc.push([...item, sitem]);
|
||||
});
|
||||
});
|
||||
tempAcc.push([...item, sitem])
|
||||
})
|
||||
})
|
||||
|
||||
if (cur.length < 1) {
|
||||
tempAcc = acc;
|
||||
tempAcc = acc
|
||||
}
|
||||
}
|
||||
return tempAcc;
|
||||
}, []);
|
||||
return tempAcc
|
||||
}, [])
|
||||
}
|
||||
|
||||
const specType = computed(() => peach.$store("trade").goodsInfo.specType);
|
||||
const specType = computed(() => peach.$store('trade').goodsInfo?.specType || false)
|
||||
|
||||
function initial() {
|
||||
onLoad(() => {
|
||||
formData.value.specType = specType.value ? true : false;
|
||||
formData.value.specText = SPEC_TYPE[specType.value ? 1 : 0].label;
|
||||
skus.value = peach.$store("trade").skus;
|
||||
if (specType.value) {
|
||||
getGoodsProperty();
|
||||
formData.value.specType = specType.value ? true : false
|
||||
formData.value.specText = SPEC_TYPE[specType.value ? 1 : 0].label
|
||||
skus.value = JSON.parse(JSON.stringify(peach.$store('trade').skus))
|
||||
// 如果新增商品 sku,并且是单规格,初始化 sku
|
||||
|
||||
if (!skus.value) {
|
||||
initSku()
|
||||
}
|
||||
});
|
||||
if (specType.value) {
|
||||
getGoodsProperty()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -167,7 +263,9 @@ export {
|
|||
skus,
|
||||
pickerRef,
|
||||
pickerProperty,
|
||||
validateSku,
|
||||
onConfirm,
|
||||
submitProperty,
|
||||
propertyListRef,
|
||||
formData,
|
||||
onRDPickerConfirm,
|
||||
|
@ -176,4 +274,4 @@ export {
|
|||
showPropertyList,
|
||||
goodsPropertyList,
|
||||
changeSubProperty,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,31 +1,83 @@
|
|||
<template>
|
||||
<pb-layout class="manage-goods" :title="goodsTitle" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white" color="black">
|
||||
<pb-layout
|
||||
class="manage-goods"
|
||||
:title="goodsTitle"
|
||||
leftIcon="leftIcon"
|
||||
navbar="normal"
|
||||
:bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white"
|
||||
color="black"
|
||||
>
|
||||
<view class="goods-form">
|
||||
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
|
||||
<uni-forms-item label="商品封面图" name="picUrl" required>
|
||||
<p-uploader v-model:url="formData.picUrl" :readonly="!canEdit" fileMediatype="image" limit="1" mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }" />
|
||||
<p-uploader
|
||||
v-model:url="formData.picUrl"
|
||||
:readonly="!canEdit"
|
||||
fileMediatype="image"
|
||||
limit="1"
|
||||
mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品轮播图" name="sliderPicUrls" required>
|
||||
<p-uploader v-model:url="formData.sliderPicUrls" :readonly="!canEdit" fileMediatype="image" limit="6"
|
||||
mode="grid" :imageStyles="{ width: '168rpx', height: '168rpx' }" />
|
||||
<p-uploader
|
||||
v-model:url="formData.sliderPicUrls"
|
||||
:readonly="!canEdit"
|
||||
fileMediatype="image"
|
||||
limit="6"
|
||||
mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品名称" name="name" required>
|
||||
<uni-easyinput type="text" trim="all" v-model="formData.name" :disabled="!canEdit" placeholder="请输入商品名称" />
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
trim="all"
|
||||
v-model="formData.name"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品名称"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品分类" @tap="openPicker('category', 'multiple')" name="categoryId" label-position="left"
|
||||
required>
|
||||
<uni-easyinput type="text" v-model="formData.categoryText" :disabled="!canEdit" :styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a" :clearable="false" :inputBorder="false" placeholder="请选择商品分类" disabled>
|
||||
<uni-forms-item
|
||||
label="商品分类"
|
||||
@tap="openPicker('category', 'multiple')"
|
||||
name="categoryId"
|
||||
label-position="left"
|
||||
required
|
||||
>
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
v-model="formData.categoryText"
|
||||
:styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a"
|
||||
:clearable="false"
|
||||
:inputBorder="false"
|
||||
placeholder="请选择商品分类"
|
||||
disabled
|
||||
>
|
||||
<template v-slot:right>
|
||||
<uni-icons type="right" />
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品品牌" name="brandId" label-position="left" required @tap="openPicker('brand', 'single')">
|
||||
<uni-easyinput type="text" v-model="formData.brandText" :disabled="!canEdit" :styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a" :clearable="false" :inputBorder="false" placeholder="请选择商品品牌" disabled>
|
||||
<uni-forms-item
|
||||
label="商品品牌"
|
||||
name="brandId"
|
||||
label-position="left"
|
||||
required
|
||||
@tap="openPicker('brand', 'single')"
|
||||
>
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
v-model="formData.brandText"
|
||||
:styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a"
|
||||
:clearable="false"
|
||||
:inputBorder="false"
|
||||
placeholder="请选择商品品牌"
|
||||
disabled
|
||||
>
|
||||
<template v-slot:right>
|
||||
<uni-icons type="right" />
|
||||
</template>
|
||||
|
@ -37,38 +89,80 @@
|
|||
</view>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品关键词" name="keyword" required>
|
||||
<uni-easyinput type="text" v-model="formData.keyword" :disabled="!canEdit" placeholder="请输入商品关键词" />
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
v-model="formData.keyword"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请输入商品关键词"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品简介" name="introduction" required>
|
||||
<uni-easyinput type="textarea" :disabled="!canEdit" trim="all" autoHeight v-model="formData.introduction"
|
||||
placeholder="请输入商品简介" />
|
||||
<uni-easyinput
|
||||
type="textarea"
|
||||
:disabled="!canEdit"
|
||||
trim="all"
|
||||
autoHeight
|
||||
v-model="formData.introduction"
|
||||
placeholder="请输入商品简介"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="物流设置" @tap="openPicker('delivery', 'single')" name="deliveryTypes" label-position="left"
|
||||
required>
|
||||
<uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a"
|
||||
:inputBorder="false" v-model="formData.deliveryText" :disabled="!canEdit" placeholder="请选择配送方式" disabled>
|
||||
<uni-forms-item
|
||||
label="物流设置"
|
||||
@tap="openPicker('delivery', 'single')"
|
||||
name="deliveryTypes"
|
||||
label-position="left"
|
||||
required
|
||||
>
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
:clearable="false"
|
||||
:styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a"
|
||||
:inputBorder="false"
|
||||
v-model="formData.deliveryText"
|
||||
:disabled="!canEdit"
|
||||
placeholder="请选择配送方式"
|
||||
disabled
|
||||
>
|
||||
<template v-slot:right>
|
||||
<uni-icons type="right" />
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="商品详情">
|
||||
<piaoyiEditor
|
||||
:values="richValues"
|
||||
:maxlength="3000"
|
||||
:readOnly="richReadOnly"
|
||||
:photoUrl="photoUrl"
|
||||
:api="richApi"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<view @tap="onSubmit" v-if="canEdit">
|
||||
<button class="ss-reset-button submit-button ui-Shadow-Main">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<p-picker ref="pickerRef" :mode="pickerMode" :options-cols="optionsCols" @confirm="onRDPickerConfirm"></p-picker>
|
||||
<p-picker
|
||||
ref="pickerRef"
|
||||
:mode="pickerMode"
|
||||
:options-cols="optionsCols"
|
||||
@confirm="onRDPickerConfirm"
|
||||
></p-picker>
|
||||
</pb-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import peach from '@/peach'
|
||||
import { handleTree } from '@/peach/utils'
|
||||
import GoodsApi from '@/peach/api/trade/goods'
|
||||
import _ from 'lodash'
|
||||
import GoodsApi from '@/peach/api/trade/goods'
|
||||
import piaoyiEditor from '@/uni_modules/piaoyi-editor/components/piaoyi-editor/piaoyi-editor.vue'
|
||||
import peach from '@/peach'
|
||||
import { baseUrl, apiPath } from '@/peach/config'
|
||||
import { handleTree } from '@/peach/utils'
|
||||
import { validateSku } from './js/sku'
|
||||
|
||||
const bgStyle = {
|
||||
backgroundImage: '',
|
||||
|
@ -92,6 +186,10 @@ const selfStyles = {
|
|||
}
|
||||
|
||||
const pickerRef = ref()
|
||||
const richValues = ref('')
|
||||
const photoUrl = baseUrl + apiPath
|
||||
const richApi = '/infra/file/upload'
|
||||
const richReadOnly = ref(false)
|
||||
const formData = ref({
|
||||
picUrl: 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png',
|
||||
sliderPicUrls: [
|
||||
|
@ -224,36 +322,39 @@ function onRDPickerConfirm(e) {
|
|||
|
||||
if (popMark.value === 'brand') {
|
||||
formData.value.brandId = brandList.value[e.value[0]].id
|
||||
formData.value.brandText = brandList.value[e.value[0]].name
|
||||
}
|
||||
}
|
||||
|
||||
function clickSetProperty() {
|
||||
if (formData.value.skus) {
|
||||
// 如果是多规格,处理格式问题,合并属性
|
||||
let temp = formData.value.skus.map((item) => {
|
||||
let temp = formData.value.skus
|
||||
.map((item) => {
|
||||
return item.properties.map((sitem) => ({
|
||||
id: sitem.propertyId,
|
||||
children: [sitem.valueId],
|
||||
}));
|
||||
}))
|
||||
})
|
||||
.flat(1);
|
||||
.flat(1)
|
||||
|
||||
// 去除重复数据
|
||||
let result = temp.reduce((pre, cur) => {
|
||||
let index = pre.findIndex((item) => item.id === cur.id);
|
||||
let index = pre.findIndex((item) => item.id === cur.id)
|
||||
if (index !== -1) {
|
||||
pre[index].children.push(...new Set(cur.children));
|
||||
pre[index].children.push(...new Set(cur.children))
|
||||
} else {
|
||||
pre.push(cur);
|
||||
pre.push(cur)
|
||||
}
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
return pre
|
||||
}, [])
|
||||
|
||||
peach.$store('trade').$patch({
|
||||
selectedProperty: result,
|
||||
goodsInfo: formData.value,
|
||||
skus: formData.value.skus
|
||||
})
|
||||
}
|
||||
|
||||
peach.$router.go('/pages/product/sku')
|
||||
}
|
||||
|
||||
|
@ -261,6 +362,8 @@ function getProduct(id) {
|
|||
GoodsApi.getProduct({ id }).then((res) => {
|
||||
formData.value = res.data
|
||||
|
||||
richValues.value = res.data.description
|
||||
|
||||
// 循环遍历 categoryList,从二级分类找出和 formData.value.categoryId 相等的
|
||||
let tempCategory = categoryList.value.find((item) => {
|
||||
return item.children.find((child) => {
|
||||
|
@ -286,25 +389,46 @@ function getProduct(id) {
|
|||
formData.value.deliveryText = DELIVERY_TYPES.find((item) => {
|
||||
return item.value === formData.value.deliveryTypes[0]
|
||||
}).label
|
||||
|
||||
peach.$store('trade').$patch({
|
||||
goodsInfo: formData.value,
|
||||
skus: formData.value.skus,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
console.log('res', formData.value)
|
||||
console.log('richtext', richValues.value)
|
||||
|
||||
formData.value.description = richValues.value
|
||||
formData.value.skus = peach.$store('trade').skus
|
||||
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (res) => {
|
||||
let tempObj = { ...res }
|
||||
// 校验 skus 是否正确填写完成
|
||||
validateSku()
|
||||
|
||||
// if (formData.value.id) {
|
||||
// tempObj.id = formData.value.id
|
||||
// await GoodsApi.editProduct(tempObj)
|
||||
// } else {
|
||||
// await GoodsApi.addProduct(tempObj)
|
||||
// }
|
||||
let tempObj = _.cloneDeep(formData.value)
|
||||
|
||||
tempObj.skus.forEach((item) => {
|
||||
item.name = formData.value.name
|
||||
})
|
||||
|
||||
if (formData.value.id) {
|
||||
tempObj.id = formData.value.id
|
||||
await GoodsApi.editProduct(tempObj)
|
||||
} else {
|
||||
await GoodsApi.addProduct(tempObj)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.showToast({
|
||||
title: err[0].errorMessage,
|
||||
icon: 'none',
|
||||
duration: 4000,
|
||||
})
|
||||
console.log('err', err)
|
||||
})
|
||||
}
|
||||
|
@ -327,6 +451,13 @@ onLoad(async (options) => {
|
|||
|
||||
goodsTitle.value = options.title
|
||||
|
||||
// 避免页面自动滚动到富文本框,先设置为只读
|
||||
richReadOnly.value = true
|
||||
|
||||
/**
|
||||
* todo 滚动一定距离后,修改富文本状态和 canEdit 一致
|
||||
*/
|
||||
|
||||
if (options.id) {
|
||||
getProduct(options.id)
|
||||
peach.$store('trade').detailTag = options.mark
|
||||
|
@ -378,7 +509,6 @@ onLoad(async (options) => {
|
|||
}
|
||||
|
||||
.is-direction-left {
|
||||
|
||||
.is-disabled {
|
||||
color: #333333;
|
||||
text-align: right;
|
||||
|
@ -388,7 +518,6 @@ onLoad(async (options) => {
|
|||
left: -160rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
|
@ -400,7 +529,7 @@ onLoad(async (options) => {
|
|||
border-radius: 0 10px 10px 0;
|
||||
|
||||
.ss-set-property {
|
||||
@include ss-set-property
|
||||
@include ss-set-property;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
<template>
|
||||
<pb-layout class="goods-property" title="商品属性" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white" color="black">
|
||||
<pb-layout
|
||||
class="goods-property"
|
||||
title="商品属性"
|
||||
leftIcon="leftIcon"
|
||||
navbar="normal"
|
||||
:bgStyle="bgStyle"
|
||||
opacityBgUi="bg-white"
|
||||
color="black"
|
||||
>
|
||||
<view class="property">
|
||||
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
|
||||
<uni-forms-item label="商品规格" @tap="pickerProperty" name="specType" label-position="left" required>
|
||||
<uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a"
|
||||
:inputBorder="false" v-model="formData.specText" placeholder="请选择商品规格" disabled>
|
||||
<uni-easyinput
|
||||
type="text"
|
||||
:clearable="false"
|
||||
:styles="selfStyles"
|
||||
placeholderStyle="color:#8a8a8a"
|
||||
:inputBorder="false"
|
||||
v-model="formData.specText"
|
||||
placeholder="请选择商品规格"
|
||||
disabled
|
||||
>
|
||||
<template v-slot:right>
|
||||
<uni-icons type="right" />
|
||||
</template>
|
||||
|
@ -17,8 +32,12 @@
|
|||
<!-- 添加商品 -->
|
||||
<button v-if="canEdit" class="ss-reset-button add-property" @tap="showPropertyList">+添加规格</button>
|
||||
<!-- 商品属性展示 -->
|
||||
<property-detail v-if="propertyList.length > 0" v-model="propertyList" :goodsPropertyList="goodsPropertyList"
|
||||
@changeSubProperty="changeSubProperty"></property-detail>
|
||||
<property-detail
|
||||
v-if="propertyList.length > 0"
|
||||
v-model="propertyList"
|
||||
:goodsPropertyList="goodsPropertyList"
|
||||
@changeSubProperty="changeSubProperty"
|
||||
></property-detail>
|
||||
<!-- 多规格商品 -->
|
||||
<mutiple-sku :skus="skus"></mutiple-sku>
|
||||
</template>
|
||||
|
@ -29,16 +48,20 @@
|
|||
</template>
|
||||
|
||||
<!-- 确认选择 -->
|
||||
<view style="padding: 0 40rpx 40rpx;" @tap="onConfirm" v-if="canEdit">
|
||||
<button class="ss-reset-button submit-button ui-Shadow-Main">提交</button>
|
||||
<view style="padding: 0 40rpx 40rpx" @tap="submitProperty" v-if="canEdit">
|
||||
<button class="ss-reset-button submit-button ui-Shadow-Main">保存</button>
|
||||
</view>
|
||||
|
||||
<!-- 商品规格 -->
|
||||
<p-picker ref="pickerRef" mode="single" :options-cols="SPEC_TYPE" @confirm="onRDPickerConfirm"></p-picker>
|
||||
|
||||
<!-- 商品属性列表 -->
|
||||
<PropertyList ref="propertyListRef" v-model="propertyList" :goodsPropertyList="goodsPropertyList"
|
||||
@confirm="onPropertyConfirm" />
|
||||
<PropertyList
|
||||
ref="propertyListRef"
|
||||
v-model="propertyList"
|
||||
:goodsPropertyList="goodsPropertyList"
|
||||
@confirm="onPropertyConfirm"
|
||||
/>
|
||||
</pb-layout>
|
||||
</template>
|
||||
|
||||
|
@ -62,6 +85,7 @@ import {
|
|||
pickerProperty,
|
||||
skus,
|
||||
changeSubProperty,
|
||||
submitProperty,
|
||||
} from './js/sku'
|
||||
|
||||
const bgStyle = {
|
||||
|
|
|
@ -20,7 +20,7 @@ const GoodsApi = {
|
|||
// 添加商品
|
||||
addProduct: (data) => {
|
||||
return request({
|
||||
url: '/trade/order/page',
|
||||
url: '/product/spu/create',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
|
@ -28,8 +28,8 @@ const GoodsApi = {
|
|||
// 修改商品
|
||||
editProduct: (data) => {
|
||||
return request({
|
||||
url: '/trade/order/page',
|
||||
method: 'POST',
|
||||
url: '/product/spu/update',
|
||||
method: 'PUT',
|
||||
data,
|
||||
})
|
||||
},
|
||||
|
|
|
@ -4,27 +4,39 @@
|
|||
<image class="lg-img-box" :src="peach.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
|
||||
<view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
|
||||
<view>
|
||||
<view v-if="goodsFields.title?.show || goodsFields.name?.show" class="lg-goods-title ss-line-2"
|
||||
:style="[{ color: titleColor }]">
|
||||
<view
|
||||
v-if="goodsFields.title?.show || goodsFields.name?.show"
|
||||
class="lg-goods-title ss-line-2"
|
||||
:style="[{ color: titleColor }]"
|
||||
>
|
||||
{{ data.title || data.name }}
|
||||
</view>
|
||||
<view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
|
||||
<view
|
||||
v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
|
||||
class="lg-goods-subtitle ss-m-t-10 ss-line-1"
|
||||
:style="[{ color: subTitleColor, background: subTitleBackground }]">
|
||||
:style="[{ color: subTitleColor, background: subTitleBackground }]"
|
||||
>
|
||||
{{ data.subtitle || data.introduction }}
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="ss-flex ss-col-bottom ss-m-t-10">
|
||||
<view v-if="goodsFields.price?.show" class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
|
||||
:style="[{ color: goodsFields.price.color }]">
|
||||
<view
|
||||
v-if="goodsFields.price?.show"
|
||||
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
|
||||
:style="[{ color: goodsFields.price.color }]"
|
||||
>
|
||||
<text class="ss-font-24">{{ priceUnit }}</text>
|
||||
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
|
||||
</view>
|
||||
<view v-if="
|
||||
<view
|
||||
v-if="
|
||||
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
|
||||
(data.original_price > 0 || data.marketPrice > 0)
|
||||
" class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS" :style="[{ color: originPriceColor }]">
|
||||
"
|
||||
class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
|
||||
:style="[{ color: originPriceColor }]"
|
||||
>
|
||||
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
|
||||
<view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
|
||||
</view>
|
||||
|
@ -44,9 +56,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import peach from '@/peach'
|
||||
import { ref, computed } from 'vue'
|
||||
import { isArray } from 'lodash'
|
||||
import peach from '@/peach'
|
||||
import { fen2yuan, formatSales, formatStock } from '@/peach/hooks/useGoods'
|
||||
import { unix } from 'dayjs'
|
||||
|
||||
|
@ -138,12 +150,16 @@ const salesAndStock = computed(() => {
|
|||
})
|
||||
|
||||
function clickGoods(mark) {
|
||||
|
||||
if (mark === 'detail' || mark === 'edit') {
|
||||
peach.$store('trade').$patch({
|
||||
selectedProperty: null,
|
||||
goodsInfo: null,
|
||||
skus: null,
|
||||
})
|
||||
peach.$router.go('/pages/product/manageGoods', {
|
||||
id: props.data.id,
|
||||
mark: mark,
|
||||
title: mark === 'detail' ? '商品详情' : '编辑商品'
|
||||
title: mark === 'detail' ? '商品详情' : '编辑商品',
|
||||
})
|
||||
} else if (mark === 'del') {
|
||||
uni.showModal({
|
||||
|
@ -205,8 +221,9 @@ function clickGoods(mark) {
|
|||
}
|
||||
|
||||
.btn-group {
|
||||
width: 120rpx;
|
||||
height: 50rpx;
|
||||
width: 140rpx;
|
||||
height: 55rpx;
|
||||
line-height: 55rpx;
|
||||
background: var(--ui-BG-1);
|
||||
border-radius: 25rpx;
|
||||
font-size: 24rpx;
|
||||
|
|
|
@ -7,18 +7,30 @@
|
|||
<view class="button-link" @click="onConfirmPopup">确定</view>
|
||||
</view>
|
||||
<view class="popup-content">
|
||||
<picker-view :indicator-style="indicatorStyle" :value="pickerViewValue" @change="bindChange"
|
||||
class="picker-view">
|
||||
<picker-view
|
||||
:indicator-style="indicatorStyle"
|
||||
:value="pickerViewValue"
|
||||
@change="bindChange"
|
||||
class="picker-view"
|
||||
>
|
||||
<template v-if="mode === 'single'">
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item, index) in props.optionsCols" :key="`${item.value}-${index}`">{{
|
||||
item.label ?? item.name }}</view>
|
||||
<view
|
||||
class="item"
|
||||
v-for="(item, index) in props.optionsCols"
|
||||
:key="`${item.value}-${index}`"
|
||||
>{{ item.label ?? item.name }}</view
|
||||
>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
<template v-else>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item, index) in props.optionsCols" :key="`${item.value}-${index}`">{{ item.name
|
||||
}}</view>
|
||||
<view
|
||||
class="item"
|
||||
v-for="(item, index) in props.optionsCols"
|
||||
:key="`${item.value}-${index}`"
|
||||
>{{ item.name }}</view
|
||||
>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item, index) in childrenList" :key="`${item.value}-${index}`">{{
|
||||
|
@ -130,7 +142,7 @@ defineExpose({
|
|||
|
||||
.item {
|
||||
text-align: center;
|
||||
line-height: 34px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
## 1.1.0(2024-04-19)
|
||||
解决不存在问题
|
||||
## 1.0.9(2024-02-04)
|
||||
更新使用文档说明
|
||||
## 1.0.8(2023-12-28)
|
||||
修改文档使用注意事项
|
||||
## 1.0.7(2023-12-05)
|
||||
优化
|
||||
## 1.0.6(2023-12-05)
|
||||
优化
|
|
@ -0,0 +1,784 @@
|
|||
<template>
|
||||
<view v-if="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
|
||||
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
|
||||
<view class="t-box" :class="{active:active}">
|
||||
<view class="t-header">
|
||||
<view class="t-header-button" @click="close">取消</view>
|
||||
<view class="t-header-button" @click="confirm">确认</view>
|
||||
</view>
|
||||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
|
||||
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
|
||||
<view class="t-color-mask"></view>
|
||||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-control__box">
|
||||
<view class="t-control__color">
|
||||
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
|
||||
</view>
|
||||
<view class="t-control-box__item">
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
|
||||
<view class="t-hue">
|
||||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
|
||||
<view class="t-transparency">
|
||||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-result__box">
|
||||
<view v-if="mode" class="t-result__item">
|
||||
<view class="t-result__box-input">{{hex}}</view>
|
||||
<view class="t-result__box-text">HEX</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.r}}</view>
|
||||
<view class="t-result__box-text">R</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.g}}</view>
|
||||
<view class="t-result__box-text">G</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.b}}</view>
|
||||
<view class="t-result__box-text">B</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.a}}</view>
|
||||
<view class="t-result__box-text">A</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="t-result__item t-select" @click="select">
|
||||
<view class="t-result__box-input">
|
||||
<view>切换</view>
|
||||
<view>模式</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-alternative">
|
||||
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
|
||||
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
|
||||
@click="selectColor(item)">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
spareColor: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
active: false,
|
||||
// rgba 颜色
|
||||
rgba: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
// hsb 颜色
|
||||
hsb: {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
},
|
||||
site: [{
|
||||
top: 0,
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}],
|
||||
index: 0,
|
||||
bgcolor: {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
hex: '#000000',
|
||||
mode: true,
|
||||
colorList: [{
|
||||
r: 244,
|
||||
g: 67,
|
||||
b: 54,
|
||||
a: 1
|
||||
}, {
|
||||
r: 233,
|
||||
g: 30,
|
||||
b: 99,
|
||||
a: 1
|
||||
}, {
|
||||
r: 156,
|
||||
g: 39,
|
||||
b: 176,
|
||||
a: 1
|
||||
}, {
|
||||
r: 103,
|
||||
g: 58,
|
||||
b: 183,
|
||||
a: 1
|
||||
}, {
|
||||
r: 63,
|
||||
g: 81,
|
||||
b: 181,
|
||||
a: 1
|
||||
}, {
|
||||
r: 33,
|
||||
g: 150,
|
||||
b: 243,
|
||||
a: 1
|
||||
}, {
|
||||
r: 3,
|
||||
g: 169,
|
||||
b: 244,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 188,
|
||||
b: 212,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 150,
|
||||
b: 136,
|
||||
a: 1
|
||||
}, {
|
||||
r: 76,
|
||||
g: 175,
|
||||
b: 80,
|
||||
a: 1
|
||||
}, {
|
||||
r: 139,
|
||||
g: 195,
|
||||
b: 74,
|
||||
a: 1
|
||||
}, {
|
||||
r: 205,
|
||||
g: 220,
|
||||
b: 57,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 235,
|
||||
b: 59,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 193,
|
||||
b: 7,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 152,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 87,
|
||||
b: 34,
|
||||
a: 1
|
||||
}, {
|
||||
r: 121,
|
||||
g: 85,
|
||||
b: 72,
|
||||
a: 1
|
||||
}, {
|
||||
r: 158,
|
||||
g: 158,
|
||||
b: 158,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0.5
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}, ]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rgba = this.color;
|
||||
if (this.spareColor.length !== 0) {
|
||||
this.colorList = this.spareColor;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
// hsb 颜色
|
||||
this.hsb = this.rgbToHex(this.rgba);
|
||||
// this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
moveHandle() {},
|
||||
open() {
|
||||
this.show = true;
|
||||
this.$nextTick(() => {
|
||||
this.init();
|
||||
setTimeout(() => {
|
||||
this.active = true;
|
||||
setTimeout(() => {
|
||||
this.getSelectorQuery();
|
||||
}, 350)
|
||||
}, 50)
|
||||
})
|
||||
|
||||
},
|
||||
close() {
|
||||
this.active = false;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false;
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
confirm() {
|
||||
this.close();
|
||||
this.$emit('confirm', {
|
||||
rgba: this.rgba,
|
||||
hex: this.hex
|
||||
})
|
||||
},
|
||||
// 选择模式
|
||||
select() {
|
||||
this.mode = !this.mode
|
||||
},
|
||||
// 常用颜色选择
|
||||
selectColor(item) {
|
||||
this.setColorBySelect(item)
|
||||
},
|
||||
touchstart(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.pageX = pageX;
|
||||
this.pageY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchmove(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.moveX = pageX;
|
||||
this.moveY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchend(e, index) {},
|
||||
/**
|
||||
* 设置位置
|
||||
*/
|
||||
setPosition(x, y, index) {
|
||||
this.index = index;
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
// 设置最大最小值
|
||||
|
||||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
if (index === 0) {
|
||||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
|
||||
// 设置颜色
|
||||
this.hsb.s = parseInt((100 * this.site[index].left) / width);
|
||||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
|
||||
this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
} else {
|
||||
this.setControl(index, this.site[index].left);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 设置 rgb 颜色
|
||||
*/
|
||||
setColor() {
|
||||
const rgb = this.HSBToRGB(this.hsb);
|
||||
this.rgba.r = rgb.r;
|
||||
this.rgba.g = rgb.g;
|
||||
this.rgba.b = rgb.b;
|
||||
},
|
||||
/**
|
||||
* 设置二进制颜色
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
setValue(rgb) {
|
||||
this.hex = '#' + this.rgbToHex(rgb);
|
||||
},
|
||||
setControl(index, x) {
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
|
||||
if (index === 1) {
|
||||
this.hsb.h = parseInt((360 * x) / width);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
this.setColor()
|
||||
} else {
|
||||
this.rgba.a = (x / width).toFixed(1);
|
||||
}
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
/**
|
||||
* rgb 转 二进制 hex
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
rgbToHex(rgb) {
|
||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.map(function(str, i) {
|
||||
if (str.length == 1) {
|
||||
hex[i] = '0' + str;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
},
|
||||
setColorBySelect(getrgb) {
|
||||
const {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
} = getrgb;
|
||||
let rgb = {}
|
||||
rgb = {
|
||||
r: r ? parseInt(r) : 0,
|
||||
g: g ? parseInt(g) : 0,
|
||||
b: b ? parseInt(b) : 0,
|
||||
a: a ? a : 0,
|
||||
};
|
||||
this.rgba = rgb;
|
||||
this.hsb = this.rgbToHsb(rgb);
|
||||
this.changeViewByHsb();
|
||||
},
|
||||
changeViewByHsb() {
|
||||
const [a, b, c] = this.position;
|
||||
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
|
||||
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
|
||||
this.setColor(this.hsb.h);
|
||||
this.setValue(this.rgba);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
|
||||
this.site[1].left = this.hsb.h / 360 * b.width;
|
||||
this.site[2].left = this.rgba.a * c.width;
|
||||
|
||||
},
|
||||
/**
|
||||
* hsb 转 rgb
|
||||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
||||
*/
|
||||
HSBToRGB(hsb) {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round((hsb.s * 255) / 100);
|
||||
let v = Math.round((hsb.b * 255) / 100);
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
let t1 = v;
|
||||
let t2 = ((255 - s) * v) / 255;
|
||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
},
|
||||
rgbToHsb(rgb) {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
} else hsb.h = -1;
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) hsb.h = 0;
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
},
|
||||
getSelectorQuery() {
|
||||
const views = uni.createSelectorQuery().in(this);
|
||||
views
|
||||
.selectAll('.boxs')
|
||||
.boundingClientRect(data => {
|
||||
if (!data || data.length === 0) {
|
||||
setTimeout(() => this.getSelectorQuery(), 20)
|
||||
return
|
||||
}
|
||||
this.position = data;
|
||||
// this.site[0].top = data[0].height;
|
||||
// this.site[0].left = 0;
|
||||
// this.site[1].left = data[1].width;
|
||||
// this.site[2].left = data[2].width;
|
||||
this.setColorBySelect(this.rgba);
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
spareColor(newVal) {
|
||||
this.colorList = newVal;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.t-box {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 30upx 0;
|
||||
padding-top: 0;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.t-box.active {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.t-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100upx;
|
||||
border-bottom: 1px #eee solid;
|
||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 150upx;
|
||||
height: 100upx;
|
||||
font-size: 30upx;
|
||||
color: #666;
|
||||
padding-left: 20upx;
|
||||
}
|
||||
|
||||
.t-header-button:last-child {
|
||||
justify-content: flex-end;
|
||||
padding-right: 20upx;
|
||||
}
|
||||
|
||||
.t-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.t-mask.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.t-color__box {
|
||||
position: relative;
|
||||
height: 400upx;
|
||||
background: rgb(255, 0, 0);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin: 0 20upx;
|
||||
margin-top: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.t-color-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 400upx;
|
||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.t-pointer {
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: -8px;
|
||||
z-index: 2;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 1px #fff solid;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.t-show-color {
|
||||
width: 100upx;
|
||||
height: 50upx;
|
||||
}
|
||||
|
||||
.t-control__box {
|
||||
margin-top: 50upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-left: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-control__color {
|
||||
flex-shrink: 0;
|
||||
width: 100upx;
|
||||
height: 100upx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-control__color-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.t-control-box__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-controller {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 32upx 32upx;
|
||||
background-position: 0 0, 16upx 16upx;
|
||||
}
|
||||
|
||||
.t-hue {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
}
|
||||
|
||||
.t-transparency {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
.t-circle {
|
||||
position: absolute;
|
||||
/* right: -10px; */
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-result__box {
|
||||
margin-top: 20upx;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__box-input {
|
||||
padding: 10upx 0;
|
||||
width: 100%;
|
||||
font-size: 28upx;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
color: #999;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-result__box-text {
|
||||
margin-top: 10upx;
|
||||
font-size: 28upx;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.t-select {
|
||||
flex-shrink: 0;
|
||||
width: 150upx;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input {
|
||||
border-radius: 10upx;
|
||||
border: none;
|
||||
color: #999;
|
||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input:active {
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-alternative {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* justify-content: space-between; */
|
||||
width: 100%;
|
||||
padding-right: 10upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-alternative__item {
|
||||
margin-left: 12upx;
|
||||
margin-top: 10upx;
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
border-radius: 10upx;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-alternative__item-content {
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.t-alternative__item:active {
|
||||
transition: all 0.3s;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4040150 */
|
||||
src: url('iconfont.woff2?t=1682491617906') format('woff2'),
|
||||
url('iconfont.woff?t=1682491617906') format('woff'),
|
||||
url('iconfont.ttf?t=1682491617906') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-checklist:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.icon-zitiyanse:before {
|
||||
content: "\e646";
|
||||
}
|
||||
|
||||
.icon-formatheader1:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.icon-formatheader2:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.icon-undo:before {
|
||||
content: "\e787";
|
||||
}
|
||||
|
||||
.icon-redo:before {
|
||||
content: "\e788";
|
||||
}
|
||||
|
||||
.icon-indent:before {
|
||||
content: "\e7f3";
|
||||
}
|
||||
|
||||
.icon-outdent:before {
|
||||
content: "\e7f4";
|
||||
}
|
||||
|
||||
.icon-zitijiacu:before {
|
||||
content: "\ec83";
|
||||
}
|
||||
|
||||
.icon-zuoyouduiqi:before {
|
||||
content: "\ec87";
|
||||
}
|
||||
|
||||
.icon-Character-Spacing:before {
|
||||
content: "\ed91";
|
||||
}
|
||||
|
||||
.icon-format:before {
|
||||
content: "\e6da";
|
||||
}
|
||||
|
||||
.icon-font-size:before {
|
||||
content: "\e7b9";
|
||||
}
|
||||
|
||||
.icon-duanhouju:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.icon-duanqianju:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.icon-shanchuxian:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.icon-charutupian:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.icon-fengexian:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.icon-juzhongduiqi:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-wuxupailie:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.icon-youduiqi:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
.icon-youxupailie:before {
|
||||
content: "\e64c";
|
||||
}
|
||||
|
||||
.icon-zitixiahuaxian:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.icon-zitixieti:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.icon-zuoduiqi:before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.icon-LineHeight:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-editor-background-color:before {
|
||||
content: "\e829";
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,516 @@
|
|||
<template>
|
||||
<view class="container-editor">
|
||||
<view class="textarea">
|
||||
<view class="page-body">
|
||||
<view class="wrapper">
|
||||
<PickerColor
|
||||
ref="colorPicker"
|
||||
:color="{ r: 255, g: 0, b: 0, a: 0.6 }"
|
||||
@confirm="confirm"
|
||||
></PickerColor>
|
||||
<view class="toolbar" @tap="format">
|
||||
<!-- <view
|
||||
:class="formats.fontSize === '24px' ? 'ql-active' : ''"
|
||||
class="iconfont icon-font-size"
|
||||
data-name="fontSize"
|
||||
data-value="24px"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.color ? 'ql-active' : ''"
|
||||
class="iconfont icon-zitiyanse"
|
||||
data-name="color"
|
||||
:data-value="formats.color"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
:class="formats.header === 1 ? 'ql-active' : ''"
|
||||
class="iconfont icon-formatheader1"
|
||||
data-name="header"
|
||||
:data-value="1"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.header === 2 ? 'ql-active' : ''"
|
||||
class="iconfont icon-formatheader2"
|
||||
data-name="header"
|
||||
:data-value="2"
|
||||
></view> -->
|
||||
<view :class="formats.bold ? 'ql-active' : ''" class="iconfont icon-zitijiacu" data-name="bold">
|
||||
</view>
|
||||
<view
|
||||
:class="formats.italic ? 'ql-active' : ''"
|
||||
class="iconfont icon-zitixieti"
|
||||
data-name="italic"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.underline ? 'ql-active' : ''"
|
||||
class="iconfont icon-zitixiahuaxian"
|
||||
data-name="underline"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.strike ? 'ql-active' : ''"
|
||||
class="iconfont icon-shanchuxian"
|
||||
data-name="strike"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.align === 'left' ? 'ql-active' : ''"
|
||||
class="iconfont icon-zuoduiqi"
|
||||
data-name="align"
|
||||
data-value="left"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.align === 'center' ? 'ql-active' : ''"
|
||||
class="iconfont icon-juzhongduiqi"
|
||||
data-name="align"
|
||||
data-value="center"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.align === 'right' ? 'ql-active' : ''"
|
||||
class="iconfont icon-youduiqi"
|
||||
data-name="align"
|
||||
data-value="right"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.align === 'justify' ? 'ql-active' : ''"
|
||||
class="iconfont icon-zuoyouduiqi"
|
||||
data-name="align"
|
||||
data-value="justify"
|
||||
></view>
|
||||
<!-- <view
|
||||
:class="formats.lineHeight ? 'ql-active' : ''"
|
||||
class="iconfont icon-LineHeight"
|
||||
data-name="lineHeight"
|
||||
data-value="2"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.letterSpacing ? 'ql-active' : ''"
|
||||
class="iconfont icon-Character-Spacing"
|
||||
data-name="letterSpacing"
|
||||
data-value="2em"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
:class="formats.marginTop ? 'ql-active' : ''"
|
||||
class="iconfont icon-duanqianju"
|
||||
data-name="marginTop"
|
||||
data-value="10px"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.previewarginBottom ? 'ql-active' : ''"
|
||||
class="iconfont icon-duanhouju"
|
||||
data-name="marginBottom"
|
||||
data-value="10px"
|
||||
></view> -->
|
||||
<!-- <view class="iconfont icon-rili4" @tap="insertDate"></view> -->
|
||||
<!-- <view class="iconfont icon-checklist" data-name="list" data-value="check"></view> -->
|
||||
<view
|
||||
:class="formats.list === 'ordered' ? 'ql-active' : ''"
|
||||
class="iconfont icon-youxupailie"
|
||||
data-name="list"
|
||||
data-value="ordered"
|
||||
></view>
|
||||
<view
|
||||
:class="formats.list === 'bullet' ? 'ql-active' : ''"
|
||||
class="iconfont icon-wuxupailie"
|
||||
data-name="list"
|
||||
data-value="bullet"
|
||||
></view>
|
||||
<!-- <view class="iconfont icon-outdent" data-name="indent" data-value="-1"></view>
|
||||
<view class="iconfont icon-indent" data-name="indent" data-value="+1"></view> -->
|
||||
<view class="iconfont icon-fengexian" @tap="insertDivider"></view>
|
||||
<view class="iconfont icon-charutupian" @tap="insertImage"></view>
|
||||
<view class="iconfont icon-undo" @tap="undo"></view>
|
||||
<view class="iconfont icon-redo" @tap="redo"></view>
|
||||
<view class="iconfont icon-format" @tap="clear"></view>
|
||||
</view>
|
||||
<editor
|
||||
id="editor"
|
||||
class="editor"
|
||||
placeholder="开始输入..."
|
||||
showImgSize
|
||||
showImgToolbar
|
||||
showImgResize
|
||||
@statuschange="onStatusChange"
|
||||
:read-only="readOnly"
|
||||
@ready="onEditorReady"
|
||||
@input="saveContens"
|
||||
>
|
||||
</editor>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PickerColor from './color-picker.vue'
|
||||
export default {
|
||||
components: {
|
||||
PickerColor,
|
||||
},
|
||||
props: {
|
||||
api: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
photoUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
values: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTab: 0,
|
||||
curColor: '#000000',
|
||||
show: true,
|
||||
hdid: '',
|
||||
myHtml: '',
|
||||
formats: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showPicker() {
|
||||
this.$refs.colorPicker.open()
|
||||
},
|
||||
confirm(e) {
|
||||
this.editorCtx.format('color', e.hex)
|
||||
},
|
||||
saveContens() {
|
||||
let that = this
|
||||
let maxlength = parseInt(that.maxlength)
|
||||
that.editorCtx.getContents({
|
||||
success: function (res) {
|
||||
let html_text = res.html
|
||||
let html_length = html_text.length
|
||||
if (html_length > maxlength) {
|
||||
uni.showModal({
|
||||
title: '最多只能输入' + maxlength + '字',
|
||||
confirmText: '确定',
|
||||
showCancel: false,
|
||||
success(res) {
|
||||
that.$emit('changes', {
|
||||
html: res.html,
|
||||
length: html_length,
|
||||
})
|
||||
},
|
||||
})
|
||||
} else {
|
||||
that.$emit('changes', {
|
||||
html: res.html,
|
||||
length: html_length,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
update() {
|
||||
//获取一下是否有数据
|
||||
let that = this
|
||||
setTimeout(() => {
|
||||
that.editorCtx.setContents({
|
||||
html: that.values,
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
onEditorReady() {
|
||||
let that = this
|
||||
console.log(uni.createSelectorQuery().in(this).select('#editor'))
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('#editor')
|
||||
.context((res) => {
|
||||
that.editorCtx = res.context
|
||||
that.update()
|
||||
})
|
||||
.exec((result) => {})
|
||||
},
|
||||
undo() {
|
||||
this.editorCtx.undo()
|
||||
},
|
||||
redo() {
|
||||
this.editorCtx.redo()
|
||||
},
|
||||
format(e) {
|
||||
let { name, value } = e.target.dataset
|
||||
if (!name) return
|
||||
if (name == 'color') {
|
||||
this.showPicker()
|
||||
} else {
|
||||
this.editorCtx.format(name, value)
|
||||
}
|
||||
},
|
||||
onStatusChange(e) {
|
||||
const formats = e.detail
|
||||
this.formats = formats
|
||||
},
|
||||
insertDivider() {
|
||||
this.editorCtx.insertDivider()
|
||||
},
|
||||
clear() {
|
||||
this.editorCtx.clear()
|
||||
this.$emit()
|
||||
},
|
||||
insertDate() {
|
||||
const date = new Date()
|
||||
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
|
||||
this.editorCtx.insertText({
|
||||
text: formatDate,
|
||||
})
|
||||
},
|
||||
insertImage() {
|
||||
let that = this
|
||||
// #ifdef APP-PLUS || H5
|
||||
uni.chooseImage({
|
||||
count: 1, //默认9
|
||||
sizeType: ['original'], //可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album'], //从相册或者相机选择
|
||||
success: (res) => {
|
||||
const tempFilePaths = res.tempFilePaths[0]
|
||||
if (!this.api || !this.photoUrl) {
|
||||
that.editorCtx.insertImage({
|
||||
src: tempFilePaths,
|
||||
alt: '图像',
|
||||
success: function () {},
|
||||
})
|
||||
uni.showToast({
|
||||
title: '未传入api字段或者photoUrl字段,此为临时图片路径',
|
||||
duration: 3000,
|
||||
icon: 'none',
|
||||
})
|
||||
} else {
|
||||
uni.uploadFile({
|
||||
url: this.photoUrl + this.api,
|
||||
filePath: tempFilePaths,
|
||||
name: this.name,
|
||||
header: {
|
||||
// Accept: 'text/json',
|
||||
Accept: '*/*',
|
||||
'tenant-id': '1',
|
||||
// Authorization: 'Bearer test247',
|
||||
},
|
||||
formData: {},
|
||||
success: (uploadFileRes) => {
|
||||
let result = JSON.parse(uploadFileRes.data)
|
||||
|
||||
if (result.error === 1) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: result.msg,
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '上传成功',
|
||||
})
|
||||
|
||||
this.editorCtx.insertImage({
|
||||
src: result.data,
|
||||
alt: '图像',
|
||||
success: function () {},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
uni.showToast({
|
||||
title: '未授权访问相册权限,请授权后使用',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album'],
|
||||
sizeType: 'compressed',
|
||||
success: (chooseImageRes) => {
|
||||
const tempFilePaths = chooseImageRes.tempFiles[0].tempFilePath
|
||||
console.log(tempFilePaths)
|
||||
if (!this.api || !this.photoUrl) {
|
||||
that.editorCtx.insertImage({
|
||||
src: tempFilePaths,
|
||||
alt: '图像',
|
||||
success: function () {},
|
||||
})
|
||||
uni.showToast({
|
||||
title: '未传入api字段或者photoUrl字段,此为临时图片路径',
|
||||
duration: 3000,
|
||||
icon: 'none',
|
||||
})
|
||||
} else {
|
||||
uni.uploadFile({
|
||||
url: this.photoUrl + this.api,
|
||||
filePath: tempFilePaths,
|
||||
name: this.name,
|
||||
header: {
|
||||
// Accept: 'text/json',
|
||||
Accept: '*/*',
|
||||
'tenant-id': '1',
|
||||
// Authorization: 'Bearer test247',
|
||||
},
|
||||
formData: {},
|
||||
success: (uploadFileRes) => {
|
||||
let result = JSON.parse(uploadFileRes.data)
|
||||
|
||||
if (result.error === 1) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: result.msg,
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '上传成功',
|
||||
})
|
||||
|
||||
this.editorCtx.insertImage({
|
||||
src: result.data,
|
||||
alt: '图像',
|
||||
success: function () {},
|
||||
})
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.log(err)
|
||||
uni.showToast({
|
||||
title: err.errMsg,
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('iconfont.css');
|
||||
|
||||
.container-editor {
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.tabs .current {
|
||||
border-bottom: 2px solid #0369d6;
|
||||
}
|
||||
|
||||
.tabs .tab {
|
||||
font-size: 32upx;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 20upx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.main .item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 80upx;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.main .item .left {
|
||||
min-width: 200upx;
|
||||
}
|
||||
|
||||
.main .title {
|
||||
padding-bottom: 20upx;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.main .textarea {
|
||||
border: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 10upx;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
display: inline-block;
|
||||
width: 9%;
|
||||
cursor: pointer;
|
||||
font-size: 40upx !important;
|
||||
text-align: center;
|
||||
padding: 10upx 0;
|
||||
}
|
||||
|
||||
.icon-rili4 {
|
||||
font-size: 48upx !important;
|
||||
}
|
||||
|
||||
.icon-duanqianju,
|
||||
.icon-duanhouju,
|
||||
.icon-zitijiacu,
|
||||
.icon-zitixieti,
|
||||
.icon-zitixiahuaxian,
|
||||
.icon-shanchuxian {
|
||||
font-size: 36upx !important;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
box-sizing: border-box;
|
||||
border-bottom: 0;
|
||||
margin-bottom: 10upx;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 300upx;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
font-size: 32upx;
|
||||
line-height: 1;
|
||||
padding-bottom: 60upx;
|
||||
}
|
||||
|
||||
/deep/ .ql-editor.ql-blank:before {
|
||||
font-size: 28upx;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
.ql-active {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 150upx;
|
||||
font-size: 30upx;
|
||||
}
|
||||
|
||||
.editor {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "piaoyi-editor",
|
||||
"name": "多功能富文本编辑器",
|
||||
"displayName": "多功能富文本编辑器",
|
||||
"version": "1.1.0",
|
||||
"description": "富文本编辑器,内置上传图片以及更改颜色, 多样化等功能",
|
||||
"keywords": [
|
||||
"富文本编辑器",
|
||||
"上传图片",
|
||||
"字体颜色"
|
||||
],
|
||||
"dcloudext": {
|
||||
"type": "component-vue"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
### piaoyiEditor 富文本编辑器
|
||||
|
||||
**使用方法:**
|
||||
|
||||
```
|
||||
<template>
|
||||
<view class="richtext">
|
||||
<piaoyiEditor :values="values" :maxlength="3000" @changes="saveContens" :readOnly="readOnly" :photoUrl="photoUrl" :api="api" :name="name"/>
|
||||
<view class="">
|
||||
{{txt}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import piaoyiEditor from '@/uni_modules/piaoyi-editor/components/piaoyi-editor/piaoyi-editor.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
readOnly: false, //是否只读
|
||||
photoUrl: 'http://test.com', //服务器图片域名或者ip
|
||||
api: '/upload', //上传图片接口地址
|
||||
txt: '',
|
||||
name: 'file',
|
||||
values: '<div>11111222</div>'
|
||||
};
|
||||
},
|
||||
components: {
|
||||
piaoyiEditor
|
||||
},
|
||||
methods: {
|
||||
saveContens(e) {
|
||||
this.txt = e.html
|
||||
}
|
||||
},
|
||||
onShareAppMessage(res) {
|
||||
if (res.from === 'button') { // 来自页面内分享按钮
|
||||
console.log(res.target)
|
||||
}
|
||||
return {
|
||||
title: '多功能富文本编辑器!',
|
||||
path: '/pages/editor/editor'
|
||||
}
|
||||
},
|
||||
onShareTimeline(res) {
|
||||
if (res.from === 'button') { // 来自页面内分享按钮
|
||||
console.log(res.target)
|
||||
}
|
||||
return {
|
||||
title: '多功能富文本编辑器!'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
```
|
||||
|
||||
#### 事件说明
|
||||
|
||||
1、**使用上传图片功能时需要注意查看代码里面的uni.uploadFile方法,因为它的返回值取决于自己后端接口的值,所以可以根据实际情况就行更改即可**
|
||||
|
||||
| 事件名 | 返回值 | 描述 |
|
||||
| :---------: | :----: | :------------: |
|
||||
| @saveContens | {html: html片段, length: html长度} | 文本框内容回调 |
|
||||
|
||||
#### Prop
|
||||
|
||||
| 参数名称 | 默认值 | 描述 |
|
||||
| -------- | ------------------------------ |
|
||||
| maxlength| 300 | 输入最大长度 |
|
||||
| readOnly | false | 是否只读 |
|
||||
| api | 空 | 上传图片接口地址 |
|
||||
| photoUrl | 空 | 服务器图片域名或者ip |
|
||||
| name | 'file' | 上传图片接口的key |
|
||||
| values | '' | 富文本编辑器默认值 |
|
||||
|
||||
### 注:近期收到使用用户反馈,存在以下四个问题(如有好的建议,期待私信,谢谢)
|
||||
|
||||
1、当组件在页面中部或者底部的时候,进入页面,页面会自动滚动到富文本编辑器的区域
|
||||
属于正常现象;
|
||||
官网文档有这么一句话:编辑器聚焦时页面会被上推,系统行为以保证编辑区可见;
|
||||
作者建议这种情况,进入页面初始设置富文本编辑器为只读,然后页面滚动到一定距离的时候取消这个只读;
|
||||
|
||||
2、组件粘贴文字出现软键盘闪烁,导致文字粘贴不了
|
||||
目前暂未发现解决方法,本插件是在官方的editor基础上开发的,这个组件存在这个问题;
|
||||
经测试长按出现粘贴后,手不松开滑动到粘贴字样上就不会出现闪烁,然后松开手,点击粘贴就可以;
|
||||
|
||||
3、有些上传图片接口是需要token的,接口需要token的话可以在组件内搜索uni.uploadFile,加上headers头部参数
|
||||
|
||||
4、H5有时候会出现插件异常情况,不要慌,查看[editor组件官网](https://uniapp.dcloud.net.cn/component/editor.html)官网下的注意事项(原话是:H5 端需要动态引入 quill.min.js、image-resize.min.js 依赖,默认情况下浏览器会从 unpkg.com 加载。如果依赖加载较慢或失败,uni-app 建议使用通过测试的 js 依赖保证效果一致,访问 github.com 或者 gitee.com 选择下载。可以放入 static 目录进行托管,或者使用 CDN 服务商。为了保证服务的稳定性,推荐开发者将所有前端资源使用 uniCloud 前端网页托管 服务进行托管,然后在 自定义模板 的 head 标签内引入相关 js 依赖。)
|
||||
|
||||
(1)上面H5这种情况我推荐一种方法进行测试:把所需要的静态资源下载到本地,在APP.vue里面的onLunch下进行dom操作插入这些静态资源
|
||||
(2)使用官方提供的自定义模板(hx新建项目的Hello uni-app模板里面有示例):使用官方的自定义模板引入静态资源,亲测引入静态资源成功了(即引入js和css都是本地的静态资源,不再请求远程CDN文件),但是原来页面样式全丢失了,目前还没有找到问题所在没有成功的朋友欢迎加群交流一下,谢谢
|
||||
|
||||
### 可接定制化组件开发
|
||||
### 右侧有本人代表作小程序二维码,可以扫码体验
|
||||
### 如使用过程中有问题或有一些好的建议,欢迎加QQ群互相学习交流:120594820
|
Loading…
Reference in New Issue