Commit a82437d6 by halweg

Merge branch 'haowei/reviewers' into developer

# Conflicts:
#	app/code/Rokanthemes/OnePageCheckout/view/frontend/web/js/action/update-item.js
#	app/code/Rokanthemes/OnePageCheckout/view/frontend/web/js/model/shipping-rate-processor/new-address.js
#	app/code/Sparsh/FreeShippingBar/view/frontend/templates/free_shipping_bar.phtml
parents bff42af5 c22d653e
...@@ -44,14 +44,14 @@ class InstallSchema implements InstallSchemaInterface ...@@ -44,14 +44,14 @@ class InstallSchema implements InstallSchemaInterface
->newTable($installer->getTable('amasty_advanced_review_images')) ->newTable($installer->getTable('amasty_advanced_review_images'))
->addColumn( ->addColumn(
'image_id', 'image_id',
Table::TYPE_INTEGER, Table::TYPE_BIGINT,
null, null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Review Id' 'Review Id'
) )
->addColumn( ->addColumn(
'review_id', 'review_id',
Table::TYPE_INTEGER, Table::TYPE_BIGINT,
null, null,
['default' => 0, 'nullable' => false], ['default' => 0, 'nullable' => false],
'Review table id' 'Review table id'
......
<?php
namespace Joshine\Review\Block\Components;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\View\Element\Template;
class Pager extends \Magento\Theme\Block\Html\Pager
{
const DEFAULT_PAGE_SIZE = 3;
/**
* @var string
*/
protected $_template = 'Joshine_Review::Components/pager.phtml';
/**
* @var Json
*/
private $json;
public function __construct(
Template\Context $context,
Json $json,
array $data = []
) {
parent::__construct($context, $data);
$this->json = $json;
}
/**
* @return string
*/
public function getJsonData()
{
return $this->json->serialize($this->getData());
}
}
<?php
namespace Joshine\Review\Block;
use Joshine\Review\Model\ResourceModel\Images\Collection;
use Joshine\Review\Helper\ImageHelper;
use Magento\Framework\View\Element\Template;
use Joshine\Review\Model\ResourceModel\Images\CollectionFactory;
/**
* Class Images
* @package Amasty\AdvancedReview\Block
*/
class Images extends \Magento\Framework\View\Element\Template
{
//评论缩略图宽度
const REVIEW_COVER_WIDTH = 200;
/**
* @var string
*/
protected $_template = 'Joshine_Review::images.phtml';
private $reviewId;
private $productId;
/**
* @var int
*/
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var CollectionFactory
*/
private $collectionFactory;
/**
* @var ImageHelper
*/
private $imageHelper;
public function __construct(
Template\Context $context,
CollectionFactory $collectionFactory,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
ImageHelper $imageHelper,
array $data = []
) {
parent::__construct($context, $data);
$this->jsonEncoder = $jsonEncoder;
$this->collectionFactory = $collectionFactory;
$this->imageHelper = $imageHelper;
}
/**
* @return int
*/
public function getReviewId(): int
{
return $this->reviewId;
}
/**
* @param $reviewId
*
* @return $this
*/
public function setReviewId($reviewId): Images
{
$this->reviewId = $reviewId;
return $this;
}
public function setProductId($productId): Images
{
$this->productId = $productId;
return $this;
}
public function getProductId()
{
return $this->productId;
}
public function getCollection() {
/** @var Collection $collection */
$collection = $this->collectionFactory->create()
->addFieldToSelect('*')
->addFieldToFilter('review_id', $this->getReviewId());
return $collection;
}
public function getFullImagePath($item): string
{
return $this->imageHelper->getFullPath($item->getPath());
}
/**
* @param $item
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getResizedImagePath($item): string
{
return $this->imageHelper->resize($item->getPath(), self::REVIEW_COVER_WIDTH * 2);
}
/**
* @param $item
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getResizedImagePathByLimit($item, $width): string
{
return $this->imageHelper->resize($item->getPath(), $width);
}
}
<?php
namespace Joshine\Review\Block;
class JsInit extends \Magento\Framework\View\Element\Template {
protected $_template = 'Joshine_Review::review-js.phtml';
}
\ No newline at end of file
<?php
namespace Joshine\Review\Block\Review\Product\View;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\DataObjectFactory;
use Magento\Review\Model\ResourceModel\Review\Collection as ReviewCollection;
use Magento\Review\Model\Review\SummaryFactory;
use Joshine\Review\Block\Components\Pager;
class ListView extends \Magento\Review\Block\Product\View\ListView
{
const VOTED_CLASS_NAME = 'active';
/**
* @var \Magento\Review\Model\Review\SummaryFactory
*/
private $summaryModFactory;
private $dataObjectFactory;
/**
* @var ReviewCollection
*/
private $reviewsCollection;
private $reviewsColFactory;
private $voteRepository;
private $vote;
private $formKey;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
public function __construct(
DataObjectFactory $dataObjectFactory,
SummaryFactory $summaryModFactory,
\Magento\Framework\Data\Form\FormKey $formKey,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
\Joshine\Review\Model\Repository\VoteRepository $voteRepository,
\Magento\Review\Model\ResourceModel\Review\CollectionFactory $reviewsColFactory,
\Magento\Catalog\Block\Product\Context $context, \Magento\Framework\Url\EncoderInterface $urlEncoder, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Framework\Stdlib\StringUtils $string, \Magento\Catalog\Helper\Product $productHelper, \Magento\Catalog\Model\ProductTypes\ConfigInterface $productTypeConfig, \Magento\Framework\Locale\FormatInterface $localeFormat, \Magento\Customer\Model\Session $customerSession, ProductRepositoryInterface $productRepository, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, \Magento\Review\Model\ResourceModel\Review\CollectionFactory $collectionFactory, array $data = [])
{
$this->summaryModFactory = $summaryModFactory;
$this->dataObjectFactory = $dataObjectFactory;
$this->reviewsColFactory = $reviewsColFactory;
$this->voteRepository = $voteRepository;
$this->formKey = $formKey;
$this->remoteAddress = $remoteAddress;
parent::__construct($context, $urlEncoder, $jsonEncoder, $string, $productHelper, $productTypeConfig, $localeFormat, $customerSession, $productRepository, $priceCurrency, $collectionFactory, $data);
}
protected function _prepareLayout()
{
parent::_prepareLayout();
if ($this->getReviewsCollection()) {
$pager = $this->getLayout()->createBlock(
Pager::class
)->setCollection(
$this->getLimitedReviewsCollection()
)->setData($this->getData())
->setProductId($this->getProductId());
$this->setChild('pager', $pager);
$this->getLimitedReviewsCollection()->load();
}
return $this;
}
public function setSummary($product, $displayedCollection)
{
$storeId = $this->_storeManager->getStore()->getId();
$summaryData = $this->summaryModFactory->create()
->setStoreId($storeId)
->load($product->getId());
$summary = $this->dataObjectFactory->create();
$summary->setData($summaryData->getData());
$product->setRatingSummary($summary);
}
public function getRatingSummary()
{
$result = null;
$summary = $this->getProduct()->getRatingSummary();
if ($summary) {
$result = $summary->getRatingSummary();
}
return $result;
}
function getRatingSummaryValue(): float
{
$value = $this->getRatingSummary();
$value = $value / 100 * 5;
return round($value, 1);
}
public function getDetailedSummary(): array
{
$result = [
'3' => 0,
'2' => 0,
'1' => 0
];
$reviews = $this->reviewsColFactory->create()->addStoreFilter(
$this->_storeManager->getStore()->getId()
)->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$this->getProduct()->getId()
);
foreach ($reviews as $item) {
if($item->getSizeFits() != 0 ){
$key = $item->getSizeFits();
$result[$key] = $result[$key] + 1;
}
}
return $result;
}
/**
* Get collection of reviews
*
* @return ReviewCollection
*/
public function getReviewsCollection()
{
if (null === $this->reviewsCollection) {
$this->reviewsCollection = $this->reviewsColFactory->create()->addStoreFilter(
$this->_storeManager->getStore()->getId()
)->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$this->getProduct()->getId()
)->setDateOrder();
}
return $this->reviewsCollection;
}
public function getReviewsCount() : int
{
return $this->getReviewsCollection()->getSize();
}
public function getLimitedReviewsCollection(): ReviewCollection
{
$page = $this->getRequest()->getParam('p') ?? 1;
return $this->getReviewsCollection()->clear()
->setPageSize(Pager::DEFAULT_PAGE_SIZE)
->setCurPage((int)$page)
->load()->addRateVotes();
}
public function getPagerHtml()
{
return $this->getChildHtml('pager');
}
private function getVote($reviewId)
{
return $this->voteRepository->getVotesCount($reviewId);
}
public function getPlusReview($reviewId)
{
$vote = $this->getVote($reviewId);
return $vote['plus'];
}
/**
* @return string
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getFormKey(): string
{
return $this->formKey->getFormKey();
}
/**
* @return string
*/
public function getPlusVotedClass($reviewId)
{
$result = '';
if ($this->isPlusVoted($reviewId)) {
$result = self::VOTED_CLASS_NAME;
}
return $result;
}
private function getVotedByIp($reviewId)
{
return $this->voteRepository->getVotesCount(
$reviewId,
$this->remoteAddress->getRemoteAddress()
);
}
public function isPlusVoted($reviewId)
{
$voted = $this->getVotedByIp($reviewId);
return $voted['plus'] > 0;
}
}
<?php
namespace Joshine\Review\Block\Review;
use Amasty\AdvancedReview\Model\Toolbar\Applier;
use Amasty\AdvancedReview\Model\Toolbar\UrlBuilder;
use Magento\Framework\View\Element\Template;
class ToolBar extends Template {
protected $request;
protected $_template = 'Joshine_Review::toolbar.phtml';
public function __construct(
Template\Context $context,
\Magento\Framework\App\Request\Http $request,
array $data = []
) {
parent::__construct($context, $data);
$this->request = $request;
}
}
<?php
namespace Joshine\Review\Contract;
interface ImagesInterface
{
const IMAGE_ID = 'image_id';
const REVIEW_ID = 'review_id';
const PATH = 'path';
/**
* Returns image id field
*
* @return int|null
*/
public function getImageId();
/**
* @param int $imageId
*
* @return $this
*/
public function setImageId($imageId);
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId();
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId);
/**
* Returns image path
*
* @return string|null
*/
public function getPath();
/**
* @param string $path
*
* @return $this
*/
public function setPath($path);
}
<?php
namespace Joshine\Review\Controller\Ajax;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Controller\Result\JsonFactory;
class Helpful extends \Magento\Framework\App\Action\Action
{
/**
* @var \Magento\Framework\Data\Form\FormKey\Validator
*/
private $formKeyValidator;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
private $voteRepository;
private $voteFactory;
/**
* @var JsonFactory
*/
private $resultJsonFactory;
public function __construct(
Context $context,
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
\Joshine\Review\Model\VoteFactory $voteFactory,
\Joshine\Review\Model\Repository\VoteRepository $voteRepository,
JsonFactory $resultJsonFactory
) {
parent::__construct($context);
$this->formKeyValidator = $formKeyValidator;
$this->logger = $logger;
$this->jsonEncoder = $jsonEncoder;
$this->remoteAddress = $remoteAddress;
$this->voteRepository = $voteRepository;
$this->voteFactory = $voteFactory;
$this->resultJsonFactory = $resultJsonFactory;
}
/**
* @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$message = [
'error' => __('Sorry. There is a problem with your Vote Request.')
];
if ($this->getRequest()->isAjax()) {
try {
if (!$this->formKeyValidator->validate($this->getRequest())) {
throw new LocalizedException(
__('Form key is not valid. Please try to reload the page.')
);
}
$type = $this->getRequest()->getParam('type');
$reviewId = (int)$this->getRequest()->getParam('review');
if ($reviewId > 0 && in_array($type, ['plus', 'minus', 'update'])) {
$ip = $this->remoteAddress->getRemoteAddress();
if ($type != 'update') {
$type = ($type == 'plus') ? '1' : '0';
$model = $this->voteRepository->getByIdAndIp($reviewId, $ip);
$modelType = $model->getType();
if ($model->getVoteId()) {
$this->voteRepository->delete($model);
}
if ($modelType === null || $modelType != $type) {
$model = $this->voteFactory->create();
$model->setIp($ip);
$model->setReviewId($reviewId);
$model->setType($type);
$this->voteRepository->save($model);
}
}
$votesForReview = $this->voteRepository->getVotesCount($reviewId);
$voted = $this->voteRepository->getVotesCount($reviewId, $ip);
$message = [
'success' => __('Success.'),
'data' => $votesForReview,
'voted' => $voted
];
}
} catch (LocalizedException $e) {
$message = ['error' => $e->getMessage()];
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
$resultPage = $this->resultJsonFactory->create();
$resultPage->setHttpResponseCode(200);
$resultPage->setData($message);
return $resultPage;
}
}
<?php
namespace Joshine\Review\Controller\Ajax;
use Amasty\AdvancedReview\Model\ResourceModel\Images as ImagesModel;
use Joshine\Review\Helper\BlockHelper;
use Joshine\Review\Helper\ImageHelper;
use Magento\Catalog\Model\ProductFactory;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Review\Model\ResourceModel\Review\Collection;
use Magento\Review\Model\Review;
class ReviewInfo extends \Magento\Framework\App\Action\Action {
/**
* @var \Magento\Framework\Data\Form\FormKey\Validator
*/
private $formKeyValidator;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var JsonFactory
*/
private $resultJsonFactory;
private $reviewFactory;
/**
* @var ProductFactory
*/
private $productFactory;
/**
* @var \Magento\Review\Model\ResourceModel\Review\CollectionFactory
*/
private $reviewsColFactory;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
public function __construct(
Context $context,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
JsonFactory $resultJsonFactory,
\Magento\Review\Model\ReviewFactory $reviewFactory,
ProductFactory $productFactory,
\Magento\Review\Model\ResourceModel\Review\CollectionFactory $reviewsColFactory,
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress
) {
parent::__construct($context);
$this->logger = $logger;
$this->jsonEncoder = $jsonEncoder;
$this->resultJsonFactory = $resultJsonFactory;
$this->reviewFactory = $reviewFactory;
$this->productFactory = $productFactory;
$this->reviewsColFactory = $reviewsColFactory;
$this->formKeyValidator = $formKeyValidator;
$this->remoteAddress = $remoteAddress;
}
/**
* @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$message = [
'error' => __('Sorry. There is a problem with your Request.')
];
if ($this->getRequest()->isAjax()) {
try {
if (!$this->formKeyValidator->validate($this->getRequest())) {
throw new LocalizedException(
__('Form key is not valid. Please try to reload the page.')
);
}
$reviewId = $this->getRequest()->getParam('review_id');
$productId = $this->getRequest()->getParam('product_id');
if (empty($reviewId) || empty($productId) ) {
throw new LocalizedException(
__('request is valid.')
);
}
$reviewInfo = $this->getReviewInfo($reviewId, $productId);
$reviews = $this->getAllReview($productId);
$message = [
'success' => __('Success.'),
'data' => $reviews,
'review' => $reviewInfo,
'total' => count($reviews),
'offset' => $this->getOffset($reviews, $reviewInfo)
];
} catch (LocalizedException $e) {
$message = ['error' => $e->getMessage()];
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
$resultPage = $this->resultJsonFactory->create();
$resultPage->setHttpResponseCode(200);
$resultPage->setData($message);
return $resultPage;
}
/**
* @param $productId
* @return \Magento\Review\Model\ResourceModel\Review\Collection
*/
public function getCollection($productId) : Collection
{
return $this->reviewsColFactory->create()->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$productId
);
}
private function getStoreId()
{
$storeManager = $this->_objectManager->get('\Magento\Store\Model\StoreManagerInterface');
return $storeManager->getStore()->getId();
}
public function getRatingPercent($reviewId)
{
$ratingCollection = $this->_objectManager->get('Magento\Review\Model\ResourceModel\Rating\Option\Vote\Collection')
->addFieldToFilter('review_id',$reviewId);
$data = $ratingCollection->getData();
return $data[0]['percent'] ?? 100;
}
public function getAllReview($productId)
{
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$db = $objectManager->get( 'Magento\Framework\App\ResourceConnection' )->getConnection();
$tblReview = $db->getTableName( 'review' );
$tblImages = $db->getTableName( 'joshine_review_images' );
$tblDetail = $db->getTableName( 'review_detail' );
$select = $db->select()
->distinct()
->from( [ 'review' => $tblReview ], ['*'] )
->join( [ 'images' => $tblImages ], 'review.review_id = images.review_id' )
->join( [ 'review_detail' => $tblDetail ], 'review.review_id = review_detail.review_id' )
->where( 'review.entity_pk_value = ?', $productId )
->where('review.status_id = ? ', Review::STATUS_APPROVED)
->group('review.review_id')->limit(50)->order('review.created_at DESC',);
$data = $db->fetchAll($select);
$res = [];
foreach ($data as $item) {
$_item['review_id'] = $item['review_id'];
$_item['size_fits'] = $item['size_fits'];
$_item['nickname'] = $item['nickname'];
$_item['title'] = $item['title'];
$_item['detail'] = $item['detail'];
$_item['created_at'] = $item['created_at'];
$_item['images'] = $this->getImages($item['review_id'], $productId);
$_item['rating'] = $this->getRatingPercent($item['review_id']);
$_item['helpful'] = $this->getVoteCount($item['review_id'])['plus'];
$_item['helpful_clicked'] = $this->getVoteCount($item['review_id'], $this->remoteAddress->getRemoteAddress())['plus'] > 0;
$res[] = $_item;
}
return $res;
}
public function getReviewInfo($reviewId, $productId)
{
$model = $this->reviewFactory->create()->load($reviewId);
$_item = [];
$_item['review_id'] = $reviewId;
$_item['size_fits'] = $model->getSize_fits();
$_item['nickname'] = $model->getNickname();
$_item['title'] = $model->getTitle();
$_item['detail'] = $model->getDetail();
$_item['created_at'] = $model->getCreatedAt();
$_item['images'] = $this->getImages($reviewId, $productId);
$_item['rating'] = $this->getRatingPercent($reviewId);
$_item['helpful'] = $this->getVoteCount($reviewId)['plus'];
$_item['helpful_clicked'] = $this->getVoteCount($reviewId, $this->remoteAddress->getRemoteAddress())['plus'] > 0;
return $_item;
}
public function getVoteCount($reviewId, $ip=null)
{
$repo = $this->_objectManager->get('Joshine\Review\Model\Repository\VoteRepository');
return $repo->getVotesCount($reviewId);
}
public function getImages($reviewId, $productId)
{
$collect = $this->_objectManager->create('Joshine\Review\Model\ResourceModel\Images\Collection')
->addFieldToSelect('*')
->addFieldToFilter('review_id', $reviewId);
/** @var $helper BlockHelper */
$helper = $this->_objectManager->get('Joshine\Review\Helper\BlockHelper');
$block = $helper->getReviewImagesBlock($reviewId, $productId);
$res = [];
foreach ($collect->getItems() as $item) {
$res['full'][] = ['url'=>$block->getFullImagePath($item), 'image_id'=>$item->getId()] ;
$res['thumb'][] = ['url'=>$block->getResizedImagePathByLimit($item, 100), 'image_id'=>$item->getId()] ; ;
}
return $res;
}
public function getOffset($allArr, $arr)
{
$offset = 0;
foreach ($allArr as $key => $value) {
$offset++;
if ($value['review_id'] == $arr['review_id']) {
break;
}
}
return $offset;
}
}
\ No newline at end of file
<?php
namespace Joshine\Review\Helper;
use Joshine\Review\Block\Images;
use Joshine\Review\Block\Review\ToolBar;
class BlockHelper implements \Magento\Framework\Data\CollectionDataSourceInterface
{
/**
* @var \Magento\Framework\View\Element\BlockFactory
*/
private $blockFactory;
/**
* @var \Magento\Framework\Stdlib\StringUtils
*/
private $stringUtils;
/**
* @var \Magento\Customer\Model\SessionFactory
*/
private $sessionFactory;
/**
* @var \Magento\Framework\Escaper
*/
private $escaper;
/**
* @var \Magento\Framework\UrlInterface
*/
private $urlBuilder;
/**
* @var \Magento\Framework\App\RequestInterface
*/
private $request;
public function __construct(
\Magento\Framework\View\Element\BlockFactory $blockFactory,
\Magento\Framework\Stdlib\StringUtils $stringUtils,
\Magento\Customer\Model\SessionFactory $sessionFactory,
\Magento\Framework\Escaper $escaper,
\Magento\Framework\UrlInterface $urlBuilder,
\Magento\Framework\App\RequestInterface $request
) {
$this->blockFactory = $blockFactory;
$this->stringUtils = $stringUtils;
$this->sessionFactory = $sessionFactory;
$this->escaper = $escaper;
$this->urlBuilder = $urlBuilder;
$this->request = $request;
}
public function getReviewImagesHtml($reviewId, $productId)
{
$html = '';
$block = $this->blockFactory
->createBlock(Images::class)
->setProductId($productId)
->setReviewId($reviewId);
if ($block) {
$html = $block->toHtml();
}
return $html;
}
public function getToolBarHtML()
{
return $this->blockFactory
->createBlock(ToolBar::class)
->toHtml();
}
public function getReviewImagesBlock($reviewId, $productId) : Images
{
return $this->blockFactory
->createBlock(Images::class)
->setProductId($productId)
->setReviewId($reviewId);
}
}
<?php
namespace Joshine\Review\Helper;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\UrlInterface;
class ImageHelper
{
const IMAGE_PATH = '/joshine/review/';
const IMAGE_TMP_PATH = '/joshine/review/tmp/';
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var \Magento\Framework\Filesystem
*/
private $filesystem;
/**
* @var \Magento\Framework\Image\AdapterFactory
*/
private $imageFactory;
/**
* @var \Magento\Framework\Filesystem\Io\File
*/
private $fileManager;
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\Image\AdapterFactory $imageFactory,
\Magento\Framework\Filesystem\Io\File $fileManager
) {
$this->storeManager = $storeManager;
$this->filesystem = $filesystem;
$this->imageFactory = $imageFactory;
$this->fileManager = $fileManager;
}
/**
* @param $name
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getFullPath($name)
{
$path = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
$path = trim($path, '/');
$name = trim($name, '/');
$path .= '/joshine/review/';
return $path . $name;
}
/**
* @param $image
* @param int $width
* @param int $height
* @return string
* @throws \Exception
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function resize($image, $width = null, $height = null)
{
$mediaUrl = $this->storeManager->getStore()->getBaseUrl(
UrlInterface::URL_TYPE_MEDIA
);
$mediaUrl = trim($mediaUrl, '/');
$resizedURL = $mediaUrl . self::IMAGE_PATH . 'resized/' . $width . '/' . $image;
$absolutePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
->getAbsolutePath(self::IMAGE_PATH)
. $image;
$imageResized = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
->getAbsolutePath(self::IMAGE_PATH . 'resized/' . $width . '/')
. $image;
if ($this->fileManager->fileExists($imageResized)) {
return $resizedURL;
}
if (!$this->fileManager->fileExists($absolutePath)) {
return '';
}
//create image factory...
$imageResize = $this->imageFactory->create();
$imageResize->open($absolutePath);
$imageResize->constrainOnly(true);
$imageResize->keepTransparency(true);
$imageResize->keepFrame(false);
$imageResize->backgroundColor([255, 255, 255]);
$imageResize->keepAspectRatio(true);
$imageResize->resize($width, $height);
$destination = $imageResized;
$imageResize->save($destination);
return $resizedURL;
}
}
<?php
namespace Joshine\Review\Model;
use Joshine\Review\Helper\ImageHelper;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\MediaStorage\Model\File\Uploader;
class ImageUploader
{
/**
* @var \Magento\Framework\Filesystem
*/
private $filesystem;
/**
* @var \Magento\MediaStorage\Model\File\UploaderFactory
*/
private $fileUploaderFactory;
/**
* @var \Magento\Framework\Image\AdapterFactory
*/
private $adapterFactory;
/**
* @var \Magento\Framework\Filesystem\Io\File
*/
private $ioFile;
public function __construct(
\Magento\Framework\Filesystem $filesystem,
\Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory,
\Magento\Framework\Image\AdapterFactory $adapterFactory,
\Magento\Framework\Filesystem\Io\File $ioFile
) {
$this->filesystem = $filesystem;
$this->fileUploaderFactory = $fileUploaderFactory;
$this->adapterFactory = $adapterFactory;
$this->ioFile = $ioFile;
}
public function execute(array $file, $isTmp = false)
{
$path = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
$isTmp ? ImageHelper::IMAGE_TMP_PATH : ImageHelper::IMAGE_PATH
);
$this->ioFile->checkAndCreateFolder($path);
/** @var $uploader Uploader */
$uploader = $this->fileUploaderFactory->create(
['fileId' => $file]
);
$imageAdapter = $this->adapterFactory->create();
$uploader->addValidateCallback('catalog_product_image', $imageAdapter, 'validateUploadFile');
$uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png']);
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(true);
$result = $uploader->save($path);
$this->trim($result);
return $result;
}
public function copy(string $imagePath)
{
$path = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
ImageHelper::IMAGE_TMP_PATH
);
$from = $path . $imagePath;
if ($this->ioFile->fileExists($from)) {
$realPath = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
ImageHelper::IMAGE_PATH
);
$counter = 0;
while ($this->ioFile->fileExists($realPath . $imagePath)) {
$imagePathArray = explode('.', $imagePath);
$imagePathArray[0] .= $counter++;
$imagePath = implode('.', $imagePathArray);
}
$this->ioFile->checkAndCreateFolder($this->ioFile->dirname($realPath . $imagePath));
if ($this->ioFile->mv($from, $realPath . $imagePath)) {
return $imagePath;
}
}
return false;
}
/**
* Fix for magento 2114 - setup upgrade
* @param $result
*/
private function trim($result)
{
if (isset($result['path']) && $result['file']) {
$path = rtrim($result['path'], '/') . $result['file'];
$this->ioFile->chmod($path, 0777);
}
}
}
<?php
namespace Joshine\Review\Model;
use Joshine\Review\Contract\ImagesInterface;
use Magento\Framework\Model\AbstractModel;
/**
* Class Images
* @package Amasty\AdvancedReview\Model
*/
class Images extends AbstractModel implements ImagesInterface
{
public function _construct()
{
$this->_init(\Joshine\Review\Model\ResourceModel\Images::class);
}
/**
* Returns image id field
*
* @return int|null
*/
public function getImageId()
{
return $this->getData(self::IMAGE_ID);
}
/**
* @param int $imageId
*
* @return $this
*/
public function setImageId($imageId)
{
$this->setData(self::IMAGE_ID, $imageId);
return $this;
}
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId()
{
return $this->getData(self::REVIEW_ID);
}
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId)
{
$this->setData(self::REVIEW_ID, $reviewId);
return $this;
}
/**
* Returns image path
*
* @return string|null
*/
public function getPath()
{
return $this->getData(self::PATH);
}
/**
* @param string $path
*
* @return $this
*/
public function setPath($path)
{
$this->setData(self::PATH, $path);
return $this;
}
}
<?php
namespace Joshine\Review\Model\Repository;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Joshine\Review\Model\ResourceModel;
use Joshine\Review\Model\VoteFactory;
class VoteRepository
{
/**
* @var array
*/
private $vote = [];
/**
* @var ResourceModel\Vote
*/
private $voteResource;
/**
* @var VoteFactory
*/
private $voteFactory;
/**
* @var ResourceModel\Vote\CollectionFactory
*/
private $collectionFactory;
public function __construct(
\Joshine\Review\Model\ResourceModel\Vote $voteResource,
\Joshine\Review\Model\VoteFactory $voteFactory,
\Joshine\Review\Model\ResourceModel\Vote\CollectionFactory $collectionFactory
) {
$this->voteResource = $voteResource;
$this->voteFactory = $voteFactory;
$this->collectionFactory = $collectionFactory;
}
/**
* {@inheritdoc}
*/
public function save($vote)
{
if ($vote->getVoteId()) {
$vote = $this->get($vote->getVoteId())->addData($vote->getData());
}
try {
$this->voteResource->save($vote);
$this->vote[$vote->getVoteId()] = $vote;
} catch (\Exception $e) {
if ($vote->getVoteId()) {
throw new CouldNotSaveException(
__('Unable to save vote with ID %1. Error: %2', [$vote->getVoteId(), $e->getMessage()])
);
}
throw new CouldNotSaveException(__('Unable to save new vote. Error: %1', $e->getMessage()));
}
return $vote;
}
/**
* {@inheritdoc}
*/
public function get($voteId)
{
if (!isset($this->vote[$voteId])) {
$vote = $this->voteFactory->create();
$this->voteResource->load($vote, $voteId);
if (!$vote->getVoteId()) {
throw new NoSuchEntityException(__('Vote with specified ID "%1" not found.', $voteId));
}
$this->vote[$voteId] = $vote;
}
return $this->vote[$voteId];
}
public function getByIdAndIp($reviewId, $ip)
{
$vote = $this->voteFactory->create();
$collection = $this->collectionFactory->create()
->addFieldToFilter('review_id', $reviewId)
->addFieldToFilter('ip', $ip);
$collection->getSelect()->limit(1);
if ($collection->getSize() > 0) {
$vote = $collection->getFirstItem();
}
return $vote;
}
/**
* {@inheritdoc}
*/
public function delete($vote)
{
try {
$this->voteResource->delete($vote);
unset($this->vote[$vote->getId()]);
} catch (\Exception $e) {
if ($vote->getVoteId()) {
throw new CouldNotDeleteException(
__('Unable to remove vote with ID %1. Error: %2', [$vote->getVoteId(), $e->getMessage()])
);
}
throw new CouldNotDeleteException(__('Unable to remove vote. Error: %1', $e->getMessage()));
}
return true;
}
/**
* {@inheritdoc}
*/
public function deleteById($voteId)
{
$model = $this->get($voteId);
$this->delete($model);
return true;
}
/**
* @param $reviewId
* @param null $ip
* @return array
*/
public function getVotesCount($reviewId, $ip = null)
{
$result = [
'plus' => 0,
'minus' => 0
];
$collection = $this->collectionFactory->create()
->addFieldToFilter('review_id', $reviewId);
if ($ip) {
$collection->addFieldToFilter('ip', $ip);
}
foreach ($collection as $vote) {
if ($vote->getType() == '1') {
$result['plus'] = ++$result['plus'];
} else {
$result['minus'] = ++$result['minus'];
}
}
return $result;
}
public function getVoteModel()
{
return $this->voteFactory->create();
}
/**
* @return array
*/
public function getVoteIpKeys()
{
return $this->collectionFactory->create()->getVoteIpKeys();
}
}
<?php
namespace Joshine\Review\Model\ResourceModel;
class Images extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
const TABLE_NAME = 'joshine_review_images';
/**
* Model Initialization
*
* @return void
*/
protected function _construct()
{
$this->_init(self::TABLE_NAME, 'image_id');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel\Images;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
/**
* Class Collection
* @package Amasty\AdvancedReview\Model\ResourceModel\Images
*/
class Collection extends AbstractCollection
{
public function _construct()
{
$this->_init(
\Joshine\Review\Model\Images::class,
\Joshine\Review\Model\ResourceModel\Images::class
);
}
/**
* @return array
*/
public function getImageKeys(): array
{
$this->getSelect()->columns('CONCAT(review_id,path) as image_key');
return $this->getColumnValues('image_key');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel;
class Vote extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
const TABLE_NAME = 'joshine_review_vote';
protected function _construct()
{
$this->_init(self::TABLE_NAME, 'vote_id');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel\Vote;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
public function _construct()
{
$this->_init(
\Joshine\Review\Model\Vote::class,
\Joshine\Review\Model\ResourceModel\Vote::class
);
}
/**
* @return array
*/
public function getVoteIpKeys(): array
{
$this->getSelect()->columns('CONCAT(review_id,ip) as vote_key');
return $this->getColumnValues('vote_key');
}
}
<?php
namespace Joshine\Review\Model;
use Magento\Framework\Model\AbstractModel;
class Vote extends AbstractModel
{
const VOTE_ID = 'vote_id';
const REVIEW_ID = 'review_id';
const TYPE = 'type';
const IP = 'ip';
public function _construct()
{
$this->_init(\Joshine\Review\Model\ResourceModel\Vote::class);
}
/**
* Returns vote id field
*
* @return int|null
*/
public function getVoteId()
{
return $this->getData(self::VOTE_ID);
}
/**
* @param int $voteId
*
* @return $this
*/
public function setVoteId($voteId)
{
$this->setData(self::VOTE_ID, $voteId);
return $this;
}
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId()
{
return $this->getData(self::REVIEW_ID);
}
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId)
{
$this->setData(self::REVIEW_ID, $reviewId);
return $this;
}
/**
* Returns vote type
*
* @return int|null
*/
public function getType()
{
return $this->getData(self::TYPE);
}
/**
* @param int $type
*
* @return $this
*/
public function setType($type)
{
$this->setData(self::TYPE, $type);
return $this;
}
/**
* Returns vote type
*
* @return string|null
*/
public function getIp()
{
return $this->getData(self::IP);
}
/**
* @param string $ip
*
* @return $this
*/
public function setIp($ip)
{
$this->setData(self::IP, $ip);
return $this;
}
}
<?php
namespace Joshine\Review\Plugin\Review\Block;
use Magento\Review\Block\Form as MagentoForm;
class Form
{
/**
* @var \Joshine\Review\Helper\BlockHelper
*/
private $blockHelper;
public function __construct(
\Joshine\Review\Helper\BlockHelper $blockHelper
) {
$this->blockHelper = $blockHelper;
}
public function afterToHtml(
MagentoForm $subject,
$result
) {
$search = '</fieldset>';
$replace = $this->getImageUploadHtml() . $search;
$result = substr_replace($result, $replace, strrpos($result, $search), strlen($search));
$searchForm = 'data-role="product-review-form"';
return str_replace($searchForm, $searchForm . ' enctype="multipart/form-data" ', $result);
}
protected function getImageUploadHtml(): string
{
return sprintf(
'<div class="field review-field-image %s">
<label class="label">%s</label><div class="control">
<input class="joshine-input" name="review_images[]" accept="image/*" multiple %s type="file" title="%s">
</div></div>',
'',
__('Add your photo'),
'',
__('Add your photo')
);
}
}
\ No newline at end of file
<?php
namespace Joshine\Review\Plugin\Review\Model;
use Joshine\Review\Model\ImageUploader;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Review\Block\Form as MagentoForm;
use Magento\Framework\Exception\LocalizedException;
use Magento\Review\Model\Review as MagentoReview;
class Review {
/**
* @var \Magento\Framework\App\RequestInterface
*/
private $request;
/**
* @var ImageUploader
*/
private $imageUploader;
/**
* @var \Joshine\Review\Model\ImagesFactory
*/
private $imagesFactory;
private $imagesResource;
private $messageManager;
public function __construct(
\Magento\Framework\App\RequestInterface $request,
ImageUploader $imageUploader,
\Joshine\Review\Model\ImagesFactory $imagesFactory,
\Magento\Framework\Message\ManagerInterface $messageManager,
\Joshine\Review\Model\ResourceModel\Images $imagesResource
)
{
$this->messageManager = $messageManager;
$this->imagesFactory = $imagesFactory;
$this->imageUploader = $imageUploader;
$this->request = $request;
$this->imagesResource = $imagesResource;
}
public function afterAggregate(
MagentoReview $subject,
$result
) {
$this->uploadReviewImages($subject);
return $result;
}
public function uploadReviewImages(MagentoReview $subject) {
$files = $this->request->getFiles('review_images');
$reviewId = $subject->getReviewId();
//todo:: 用helper方法做是否允许上传图片的判断
if ($files && $reviewId) {
foreach ($files as $fileId => $file) {
if (UPLOAD_ERR_OK == $file['error']) {
$this->uploadImage($file, $reviewId);
}
}
}
}
public function uploadImage($file, $reviewId) {
try {
$result = $this->imageUploader->execute($file);
$this->saveImage($result, $reviewId);
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__('An error occurred while uploading the image.'));
throw new LocalizedException(__($e->getMessage()));
}
return $this;
}
/**
* @throws NoSuchEntityException
* @throws CouldNotSaveException
*/
private function saveImage($result, $reviewId)
{
/** @var \Joshine\Review\Model\Images $model */
$model = $this->imagesFactory->create();
$model->setReviewId($reviewId);
$model->setPath($result['file']);
if ($model->getImageId()) {
$model = $this->getFullImage($model->getImageId())->addData($model->getData());
}
try {
$this->imagesResource->save($model);
} catch (\Exception $e) {
if ($model->getImageId()) {
throw new CouldNotSaveException(
__('Unable to save image with ID %1. Error: %2', [$model->getImageId(), $e->getMessage()])
);
}
throw new CouldNotSaveException(__('Unable to save new image. Error: %1', $e->getMessage()));
}
}
private function getFullImage($imageId) {
$image = $this->imagesFactory->create();
$this->imagesResource->load($image, $imageId);
if (!$image->getImageId()) {
throw new NoSuchEntityException(__('Rule with specified ID "%1" not found.', $imageId));
}
return $image;
}
}
<?php
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
namespace Joshine\Review\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;
/**
* Class InstallSchema
* @package Amasty\AdvancedReview\Setup
*/
class InstallSchema implements InstallSchemaInterface
{
/**
* @param SchemaSetupInterface $setup
* @param ModuleContextInterface $context
* @throws \Zend_Db_Exception
*/
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
/** 建新表 */
$this->createModuleTables($setup);
$this->addReviewColumns($setup);
$setup->endSetup();
}
/**
* @param SchemaSetupInterface $installer
* @throws \Zend_Db_Exception
*/
private function createModuleTables(SchemaSetupInterface $installer)
{
$table = $installer->getConnection()
->newTable($installer->getTable('joshine_review_images'))
->addColumn(
'image_id',
Table::TYPE_BIGINT,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Review Id'
)
->addColumn(
'review_id',
Table::TYPE_BIGINT,
null,
['default' => 0, 'nullable' => false],
'Review table id'
)
->addColumn(
'path',
Table::TYPE_TEXT,
'2M',
[],
'Image path'
)
->setComment('用户评价图片地址保存表');
$installer->getConnection()->createTable($table);
$tableVote = $installer->getConnection()
->newTable($installer->getTable('joshine_review_vote'))
->addColumn(
'vote_id',
Table::TYPE_INTEGER,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Vote Id'
)
->addColumn(
'review_id',
Table::TYPE_INTEGER,
null,
['default' => 0, 'nullable' => false],
'Review table id'
)
->addColumn(
'type',
Table::TYPE_SMALLINT,
null,
[],
'type'
)
->addColumn(
'ip',
Table::TYPE_TEXT,
256,
[],
'ip'
)
->setComment('用户评价点赞记录表');
$installer->getConnection()->createTable($tableVote);
}
/**
* review表添加字段
* @param SchemaSetupInterface $installer
*/
private function addReviewColumns(SchemaSetupInterface $installer)
{
/**
* 添加verified_buyer字段,用于后续搜索
*/
$installer->getConnection()->addColumn(
$installer->getTable('review'),
'verified_buyer',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
'nullable' => true,
'default' => 0,
'comment' => 'Verified Buyer'
]
);
}
}
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Review\Model\Review">
<plugin name="Joshine_Review::save-images" type="Joshine\Review\Plugin\Review\Model\Review" />
</type>
</config>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Review\Block\Form">
<plugin name="Joshine_Review::add-file-upload-control" type="Joshine\Review\Plugin\Review\Block\Form" />
</type>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="joshine_review" frontName="joshine_review">
<module name="Joshine_Review" />
</route>
</router>
</config>
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Joshine_Review" setup_version="0.0.4">
<sequence>
<module name="Magento_Review"/>
</sequence>
</module>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Joshine_Review',
__DIR__
);
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<block name="joshine.review.comments" class="Joshine\Review\Block\Comment\Container">
<block name="joshine.review.comments.messages" as="messages" class="Joshine\Review\Block\Comment\CommentsList">
<block name="joshine.review.comments.message" as="message" class="Joshine\Review\Block\Comment\Comment" />
</block>
<block name="joshine.review.comments.form" as="form" class="Amasty\AdvancedReview\Block\Comment\Form" />
</block>
</body>
</page>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template"
template="Joshine_Review::review-js.phtml"
name="joshine.review.toolbar.js"
after="-"/>
</referenceContainer>
</body>
</page>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<css src="Joshine_Review::css/_review.css"/>
</head>
</page>
\ No newline at end of file
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="product.info.product_additional_data" remove="true" />
<referenceBlock class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar" remove="true"/>
<referenceContainer name="root">
<block class="Joshine\Review\Block\Review\Product\View\ListView"
name="Joshine.product.info.product_additional_data"
template="Joshine_Review::product/view/list.phtml"
before="product_review_list.toolbar">
<arguments>
<argument name="joshine-review-helper" xsi:type="object">Joshine\Review\Helper\BlockHelper</argument>
</arguments>
</block>
<block name="joshine.review.js" class="Joshine\Review\Block\JsInit" ifconfig="joshine_review/comments/enabled" />
</referenceContainer>
</body>
</page>
var config = {
paths: {
"joshineReviewHandler": "Joshine_Review/js/review/handler",
},
};
\ No newline at end of file
<?php
/**
* @var $block \Joshine\Review\Block\Components\Pager
*/
?>
<?php if ($block->getCollection()->getSize() && $block->getLastPageNum() > 1): ?>
<div class="joshine-reviews-page">
<div class="joshine-pagination joshine-pagination-left" joshine-review-js="reviews-pages-<?= $block->escapeHtml($block->getProductId()); ?>" >
<?php if (!$block->isFirstPage()): ?>
<span class="joshine-pagination-btn joshine-pagination-prev"
data-href="<?= $block->escapeUrl($block->getPreviousPageUrl()); ?>"
>
<
</span>
<?php endif; ?>
<?php if ($block->canShowFirst()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getFirstPageUrl()); ?>"
>
1
</span>
<?php endif; ?>
<?php if ($block->canShowPreviousJump()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getPreviousJumpUrl()); ?>"
>
...
</span>
<?php endif; ?>
<?php foreach ($block->getFramePages() as $page): ?>
<?php if ($block->isPageCurrent($page)): ?>
<span class="joshine-pagination-btn joshine-pagination-hover active">
<?= $block->escapeHtml($page); ?>
</span>
<?php else: ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getPageUrl($page)); ?>"
>
<?= $block->escapeHtml($page) ?>
</span>
<?php endif; ?>
<?php endforeach; ?>
<?php if ($block->canShowNextJump()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getNextJumpUrl()) ?>"
>
...
</span>
<?php endif; ?>
<?php if ($block->canShowLast()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getLastPageUrl()) ?>"
>
<?= $block->escapeHtml($block->getLastPageNum()) ?>
</span>
<?php endif; ?>
<?php if (!$block->isLastPage()): ?>
<span class="joshine-pagination-btn joshine-pagination-next joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getNextPageUrl()) ?>">
>
</span>
<?php endif; ?>
</div>
</div>
<?php endif ?>
\ No newline at end of file
<?php
/** @var \Joshine\Review\Block\Images $block */
$collection = $block->getCollection();
?>
<?php if ($collection->getSize()): ?>
<?php foreach ($collection as $item): ?>
<div class="joshine-review-pic-item joshine-col-xs-4" data-img-src="<?= $block->getFullImagePath($item) ?>"
data-image-id="<?= $item->getId(); ?>"
data-product-id="<?= $block->getProductId() ?>"
data-reviews-id="<?= $block->getReviewId() ?>">
<img class="review-image rounded" src="<?= $block->getResizedImagePath($item) ?>"/>
</div>
<?php endforeach; ?>
<?php endif;?>
<?php
//rewrite file Magento_Review::product/view/list.phtml
// phpcs:ignoreFile
/** @var Joshine\Review\Block\Review\Product\View\ListView $block */
$displayedCollection = $block->getLimitedReviewsCollection();
$_items = $displayedCollection->getItems();
$format = $block->getDateFormat() ?: \IntlDateFormatter::MEDIUM;
$block->setSummary($block->getProduct(), $displayedCollection);
/** @var Joshine\Review\Helper\BlockHelper $helper */
$helper = $block->getData('joshine-review-helper');
$rating = $block->getRatingSummary();
$count = $block->getReviewsCount();
$t = [ 1 => __('Small') , 2 => __('True to Size') ,3 => __('Large')];
$imagesBlock = $helper->getReviewImagesBlock(15, $block->getProductId());
?>
<style>
.joshine-review-container-mobile .joshine-review .ave-rate {
display: flex;
align-items: center;
}
.joshine-review-container-mobile .joshine-review .fit-item {
display: flex;
align-items: center;
}
.joshine-review-container-mobile .joshine-review .fit-name {
width: 30%;
}
.joshine-review-container-mobile .joshine-review .joshine-process {
width: 60%;
}
.joshine-review-container-mobile .joshine-review .joshine-review-averate {
padding: .5em;
}
.joshine-review-container-mobile .joshine-review .add-new-box {
margin: .5em 0 .5em 0;
}
</style>
<div class="joshine-review-container joshine-clearfix joshine-hidden-md joshine-hidden-sm joshine-hidden-xs">
<h1 class="joshine-review-title">
Customer Reviews
</h1>
<div class="joshine-review">
<div class="joshine-review-averate joshine-bg-silver">
<div class="joshine-review-item joshine-review-rating-box">
<div class="name">
Average Rating
</div>
<div class="ave-rate">
<div class="joshine-rating-container">
<div class="joshine-rating-starts" style="width: <?= $rating ?>%;"></div>
</div>
<div class="ave-rate-total"><?= $block->getRatingSummaryValue() ?></div>
</div>
</div>
<div class="joshine-review-item">
<div class="name">
Did the item fit well?
</div>
<?php foreach ($block->getDetailedSummary() as $key => $detail) : ?>
<div class="fit-item">
<div class="fit-name joshine-font-mini">
<?= $t[$key] ?>
</div>
<div class="joshine-process">
<div class="joshine-process-active" style="width: <?= /* @noEscape */ $count ? round($detail / $count * 100) : 0 ?>%;"></div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="joshine-review-item add-new-box">
<a href="#review-form" title="Write a review" class="add-new-button joshine-btn joshine-btn-dark joshine-review-add-btn">
Write a review
</a>
</div>
</div>
<!-- 筛选框 -->
<?= $helper->getToolBarHtML() ?>
<div class="joshine-review-list joshine-clearfix">
<?php if (count($_items) > 0): ?>
<?php foreach ($_items as $review) : ?>
<div class="joshine-review-list-item ">
<div class="joshine-review-list-item-left">
<div class="joshine-review-nickname joshine-font-c-deepin joshine-font-size-default joshine-font-w-bolder ">
<?= $block->escapeHtml($review->getNickname()) ?>
</div>
<input type="hidden" id="modal-current-review-id">
<div class="joshine-review-date joshine-font-c-darkgray joshine-font-mini-plus">
<?= $block->escapeHtml($block->formatDate($review->getCreatedAt(), $format)) ?>
</div>
</div>
<div class="joshine-review-list-item-mid joshine-pull-left">
<div class="joshine-review-detail">
<div class="joshine-review-des-title joshine-font-size-default joshine-font-c-deepin">
<?= $block->escapeHtml($review->getTitle()) ?>
</div>
<div class="joshine-rating-container joshine-rating-small">
<?php if (count($review->getRatingVotes())) : ?>
<?php foreach ($review->getRatingVotes() as $_vote) : ?>
<div class="joshine-rating-starts" style="width: <?= $block->escapeHtml($_vote->getPercent()) ?>%;"></div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="joshine-review-des">
<?= $block->escapeHtml($review->getDetail()); ?>
</div>
<div class="joshine-foot-mark rate-fit">
<div class="rate-fit-item">
<?php $fitsValue = $review->getSizeFits(); ?>
<span><strong class="joshine-font-w-bolder">Size Fits:</strong> <?= $t[$fitsValue] ?></span>
</div>
</div>
</div>
<div class="joshine-review-pics">
<?= /* @noEscape */ $helper->getReviewImagesHtml($review->getId(), $block->getProductId()) ?>
</div>
</div>
<div class="joshine-review-list-item-right">
<div class="joshine-review-helpful joshine-font-c-darkgray joshine-font-text">
<span>helpful</span>
<span class="joshine-like <?= $block->getPlusVotedClass($review->getId()); ?>" data-review-id="<?= $review->getId(); ?>">
<input name="form_key" type="hidden" value="<?= /* @noEscape */ $block->getFormKey(); ?>" />
</span>
<span class="joshine-review-likes-count"><?= $block->getPlusReview($review->getId()); ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php if ($block->getPagerHtml()) : ?>
<div class=" toolbar toolbar-customized "><?= $block->getPagerHtml() ?></div>
<?php endif ?>
</div>
</div>
<div class="joshine-review-container-mobile joshine-hidden-lg">
<h4 class="joshine-review-title joshine-font-w-bolder">
Customer Reviews
</h4>
<div class="joshine-review">
<div class="joshine-review-averate joshine-bg-silver joshine-clearfix">
<div class="joshine-review-item joshine-review-rating-box">
<div class="name">
Average Rating
</div>
<div class="ave-rate row">
<div class="joshine-rating-container joshine-col-xs-8">
<div class="joshine-rating-starts" style="width: <?= $rating ?>%;"></div>
</div>
<h2 class="ave-rate-total" style="margin-top: 0; margin-bottom: .35em;"><?= $block->getRatingSummaryValue() ?></h2>
</div>
</div>
<div class="joshine-review-item">
<div class="name">
Did the item fit well?
</div>
<?php foreach ($block->getDetailedSummary() as $key => $detail) : ?>
<div class="fit-item">
<span class="fit-name joshine-font-mini">
<?= $t[$key] ?>
</span>
<div class="joshine-process">
<div class="joshine-process-active" style="width: <?= /* @noEscape */ $count ? round($detail / $count * 100) : 0 ?>%;"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="joshine-review-item add-new-box">
<a href="#review-form" title="Write a review" class="add-new-button joshine-btn joshine-btn-dark joshine-review-add-btn">
Write a review
</a>
</div>
<div class="joshine-review-list joshine-clearfix">
<?php if (count($_items) > 0): ?>
<?php foreach ($_items as $review) : ?>
<div class="joshine-review-list-item row joshine-clearfix">
<div class="row">
<span class="joshine-review-nickname" style="font-size: .9em;">
<?= $block->escapeHtml($review->getNickname()) ?>
</span>
&nbsp;
<span class="joshine-review-date joshine-font-c-darkgray joshine-font-mini-plus">
<?= $block->escapeHtml($block->formatDate($review->getCreatedAt(), $format)) ?>
</span>
</div>
<div class="row">
<div class="joshine-rating-container joshine-rating-small joshine-col-xs-8">
<?php if (count($review->getRatingVotes())) : ?>
<?php foreach ($review->getRatingVotes() as $_vote) : ?>
<div class="joshine-rating-starts" style="width: <?= $block->escapeHtml($_vote->getPercent()) ?>%;"></div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="joshine-pull-right">
<div class="joshine-review-helpful joshine-font-c-darkgray joshine-font-text">
<span class="joshine-like <?= $block->getPlusVotedClass($review->getId()); ?>" data-review-id="<?= $review->getId(); ?>">
<input name="form_key" type="hidden" value="<?= /* @noEscape */ $block->getFormKey(); ?>" />
</span>
<span class="joshine-review-likes-count" data-review-id="<?= $review->getId(); ?>"><?= $block->getPlusReview($review->getId()); ?></span>
</div>
</div>
</div>
<div class="row joshine-foot-mark">
<?php $fitsValue = $review->getSizeFits(); ?>
<span><strong class="joshine-font-w-bolder">Size Fits:</strong> <?= $t[$fitsValue] ?></span>
</div>
<div class="row joshine-font-w-bolder" style="font-size: .9em;">
<?= $block->escapeHtml($review->getTitle()); ?>
</div>
<div class="row joshine-font-c-darkgray" style="font-size: .8em;">
<?= $block->escapeHtml($review->getDetail()); ?>
</div>
<div class="row joshine-review-pics">
<?= /* @noEscape */ $helper->getReviewImagesHtml($review->getId(), $block->getProductId()) ?>
</div>
</div>
<hr>
<?php endforeach; ?>
<?= $block->getPagerHtml() ?>
<?php endif; ?>
</div>
</div>
</div>
<style>
.joshine-model-warp {
z-index: 1000;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.joshine-model-warp .joshine-mask {
z-index: 1000;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,.8);
}
.joshine-model-warp .j-modal {
overflow: hidden;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2000;
-webkit-overflow-scrolling: touch;
outline: 0;
}
.j-modal .j-modal-dialog {
position: relative;
width: 530px;
margin: 30px auto;
}
.j-modal .j-modal-content {
position: relative;
padding: 40px 50px 30px;
background: #fff;
background-clip: padding-box;
background-position: center;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.j-modal .j-modal-header {
text-align: center;
max-height: 60em;
overflow-y: auto;
}
.joshine-model-warp .j-modal .j-modal-dialog {
width: 1050px;
}
.joshine-close {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
line-height: 24px;
font-size: 14px;
cursor: pointer;
}
.j-modal .j-modal-body {
position: relative;
line-height: 1.2;
}
.joshine-review-des-head {
display: flex;
align-items: center;
justify-content:space-between;
}
.modal-reviews-details__des-wrap {
margin-left: 24px;
}
.modal-reviews-details__des-wrap div {
margin-bottom: 24px;
}
.modal-reviews-details__image-wrap .modal-reviews-details__image-swiper {
padding: 10px;
height: 779px;
}
.modal-reviews-details__image-thumbs {
margin-top: 14px;
overflow: hidden;
}
.image-thumbs-list {
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: -webkit-box;
display: flex;
-webkit-transition-property: -webkit-transform;
transition-property: -webkit-transform;
transition-property: transform;
transition-property: transform,-webkit-transform;
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
.swiper-item {
position: relative;
margin-right: 2px;
}
.swiper-item img {
left: 50%;
-webkit-transform: translate3d(-50%,-50%,0);
transform: translate3d(-50%,-50%,0);
display: block;
position: absolute;
top: 50%;
max-width: 100%;
max-height: 100%;
}
.image-thumbs-list-item {
margin-right: 4px;
height: 50px;
width: 45px;
overflow: hidden;
-webkit-flex-shrink: 0;
-ms-flex: 0 0 auto;
flex-shrink: 0;
position: relative;
cursor: pointer;
}
.image-thumbs-list-item.active {
border: 1px solid #000;
}
.image-thumbs-list-item .modal-reviews-details__image-thumbs-image img {
width: 100%;
display: block;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate3d(-50%,-50%,0);
transform: translate3d(-50%,-50%,0);
}
.swiper-wrapper {
display: flex;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: -webkit-box;
display: flex;
-webkit-transition-property: -webkit-transform;
transition-property: -webkit-transform;
-webkit-box-sizing: content-box;
box-sizing: content-box;
transform: translate3d(0,0,0);
}
.swiper-wrapper .swiper-item {
-webkit-flex-shrink: 0;
-ms-flex: 0 0 auto;
flex-shrink: 0;
width: 100%;
height: 100%;
position: relative;
}
.swiper-container {
margin-left: auto;
margin-right: auto;
position: relative;
overflow: hidden;
z-index: 1;
height: 100%;
width: 100%;
}
.modal-reviews-details__image-swiper .swiper-button-prev {
height: 100%;
width: 50%;
position: absolute;
top: 0;
margin-top: 0;
left: 0;
right: auto;
z-index: 12;
cursor: url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-pre.ico), auto;
}
.modal-reviews-details__image-swiper .swiper-button-next {
height: 100%;
width: 50%;
position: absolute;
top: 0;
margin-top: 0;
right: 0;
left: auto;
z-index: 12;
cursor: url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-next.ico), auto;
}
.modal-reviews-details__image-thumbs-image{
position: relative;
height: 100%;
overflow: hidden;
border: 1px solid #efefef;
}
</style>
<div class="joshine-model-warp joshine-hidden">
<div class="joshine-mask"></div>
<div class="j-modal">
<div class="j-modal-dialog">
<div class="j-modal-content">
<div class="j-modal-header">
<span class="joshine-close">x</span>
</div>
<div class="j-modal-body">
<div class="modal-reviews-details row">
<div class="modal-reviews-details__image-wrap joshine-col-xs-7 joshine-text-center">
<div class="modal-reviews-details__image-swiper joshine-bg-silver">
<div class="swiper-container">
<div class="swiper-button-prev"></div>
<div class="swiper-wrapper image-full-wrapper" data-review-js="image-full-wrapper">
</div>
<div class="swiper-button-next"></div>
</div>
</div>
<div class="modal-reviews-details__image-thumbs">
<div class="image-thumbs-list" data-review-js="image-thumbs-wrapper">
</div>
</div>
</div>
<div class="modal-reviews-details__des-wrap joshine-col-xs-4">
<div class="joshine-review-des-head">
<span class="joshine-review-nickname" data-review-js="nickname" style="font-size: .9em;">
</span>
<div class="joshine-rating-container joshine-rating-small" style="margin-bottom: 3px;">
<div class="joshine-rating-starts" style="width: 100%;" data-review-js="rating"></div>
</div>
<span class="joshine-review-date joshine-font-c-darkgray joshine-font-mini-plus joshine-pull-right" data-review-js="date">
</span>
</div>
<div class="joshine-review-des-title joshine-font-w-bolder joshine-font-text joshine-font-c-deepin" data-review-js="title">
</div>
<div class="joshine-review-des-text joshine-font-c-darkgray joshine-font-mini-plus" data-review-js="detail">
</div>
<div class="joshine-foot-mark rate-fit">
<div class="rate-fit-item">
<span><strong class="joshine-font-w-bolder">Size Fits:</strong> <span data-review-js="fits"></span></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
require([
'jquery','ko'
], function ($) {
$(".joshine-review-add-btn").on('click',function () {
$(".block.review-add").removeClass('joshine-hidden');
});
$(".joshine-like").on('click', function() {
var element = $(this);
var formKey = $.mage.cookies.get('form_key');
if (formKey) {
element.find('input[name="form_key"]').val(formKey);
}
var type = element.hasClass('active') ? 'minus' : 'plus';
var reviewId = element.data('review-id');
var data = 'type='+ type +'&form_key=' + formKey + '&review=' + reviewId;
function refreshReviewVote(response)
{
if (!response.hasOwnProperty('success')) {
return;
}
if (type === 'plus') {
element.addClass('active')
}
if (type === 'minus') {
element.removeClass('active')
}
element.siblings('.joshine-review-likes-count').html(response.data.plus);
}
$.ajax({
url: '/joshine_review/ajax/helpful',
data: data,
type: 'GET',
dataType: 'json',
success: function (response) {
refreshReviewVote(response);
}
});
});
var img_idx = 0;
// 节流锁
var lock = true;
var swiper_wrapper = $('.swiper-wrapper');
var swiper_thumbs = $('.image-thumbs-list');
var reviewList;
function processReview(review) {
$('[data-review-js="nickname"]').html(review.nickname);
$('[data-review-js="title"]').html(review.title);
$('[data-review-js="detail"]').html(review.detail);
$('[data-review-js="date"]').html(review.created_at);
let fits_text = fitsTranslate(review.size_fits);
$('[data-review-js="fits"]').html(fits_text);
$('[data-review-js="rating"]').width(review.rating + '%');
$('#modal-current-review-id').val(review.review_id);
}
function fitsTranslate(fits) {
var fits_words = 'default';
if (fits === '1') {
fits_words = 'Small';
}
if (fits === '2') {
fits_words = 'Ture Size';
}
if (fits === '3') {
fits_words = 'Large';
}
return fits_words;
}
function pullIdx(id)
{
img_idx = findIdxByImgId(id);
swiper_wrapper.css({"transform" : 'translateX(' + -536 * img_idx + 'px)'});
dealIndex(img_idx);
}
function findIdxByImgId(imgId)
{
let res = 0;
$(".image-thumbs-list-item").each(function (index, ele) {
if ($(ele).data('image-id') == imgId) {
res = $(ele).data('img-index');
}
});
return res;
}
$(".joshine-review-pic-item").on('click', function () {
var reviewId = $(this).data('reviews-id');
var productId = $(this).data('product-id');
var image_id = $(this).data('image-id');
var formKey = $.mage.cookies.get('form_key');
var data = 'review_id='+ reviewId +'&form_key=' + formKey + '&product_id=' + productId;
$.ajax({
url: '/joshine_review/ajax/reviewinfo',
data: data,
type: 'GET',
dataType: 'json',
success: function (response) {
reviewList = response.data;
processModal(response);
pullIdx(image_id);
}
});
function processModal(response)
{
processReview(response.review);
processThumbs(response);
processFullImage(response);
$(".joshine-model-warp").removeClass('joshine-hidden')
}
function processThumbs(response) {
var html = '';
var image_index = 0;
var data = response.data;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].images.thumb.length; j++) {
html += `
<div class="image-thumbs-list-item" data-review-id="${data[i].review_id}" data-img-index="${image_index}" data-image-id="${data[i].images.thumb[j].image_id}">
<div class="modal-reviews-details__image-thumbs-image">
<img src="${data[i].images.thumb[j].url}" alt="">
</div>
</div>
`;
image_index++;
}
}
$('.image-thumbs-list').html(html);
}
function processFullImage(response)
{
var html = '';
var image_index = 0;
var data = response.data;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].images.full.length; j++) {
html += `
<div class="swiper-item" data-review-id="${data[i].review_id}" data-img-index="${image_index}" data-image-id="${data[i].images.full[j].image_id}">
<img src="${data[i].images.full[j].url}" alt="">
</div>
`;
image_index++;
}
}
$('.image-full-wrapper').html(html);
}
});
$(".joshine-close").on('click', function () {
$(".joshine-model-warp").addClass('joshine-hidden')
});
function dealIndex(img_idx)
{
$('.image-thumbs-list-item').each(function (index, element) {
if ($(element).data('img-index') == img_idx && !$(this).hasClass('active')) {
$(element).addClass('active');
return;
}
$(element).removeClass('active');
});
let thumbPage = parseInt(img_idx / 11);
swiper_thumbs.css({"transition" : 'transform .2s ease 0s'});
swiper_thumbs.css({"transform" : 'translateX(' + -490 * thumbPage + 'px)'});
}
$(".swiper-button-next").on('click', function () {
if (!lock) return;
lock = false;
swiper_wrapper.css({"transition" : 'transform .5s ease 0s'});
img_idx++;
swiper_wrapper.css({"transform" : 'translateX(' + -536 * img_idx + 'px)'});
// 开锁,动画结束之后开锁
dealIndex(img_idx);
processReviewDetail(img_idx);
setTimeout(function () {
lock = true;
}, 500);
});
$(".swiper-button-prev").on('click', function () {
if (!lock) return;
lock = false;
if (img_idx == 0) {
setTimeout(function () {
lock = true;
}, 500);
return;
}
swiper_wrapper.css({"transition" : 'transform .5s ease 0s'});
img_idx--;
swiper_wrapper.css({"transform" : 'translateX(' + -536 * img_idx + 'px)'});
dealIndex(img_idx);
processReviewDetail(img_idx);
setTimeout(function () {
lock = true;
}, 500);
});
function processReviewDetail(img_idx)
{
var shouldReviewId;
$(".image-thumbs-list-item").each(function (index, ele) {
if ($(ele).data('img-index') == img_idx) {
shouldReviewId = $(ele).data('review-id');
}
});
let curReviewId = $('#modal-current-review-id').val();
if (shouldReviewId != curReviewId) {
let review = findReviewById(shouldReviewId);
processReview(review);
}
}
function findReviewById(reviewId)
{
let review = '';
for(let i = 0; i < reviewList.length; i++) {
if (reviewList[i].review_id == reviewId) {
review = reviewList[i];
break;
}
}
return review;
}
});
</script>
\ No newline at end of file
<?php
/** @var $block */
?>
<script type="text/x-magento-init">
{
"*": {
"Joshine_Review/js/review/js-loader": { }
}
}
</script>
<div class="joshine-review-filter joshine-clearfix">
<div class="joshine-review-tab joshine-review-tab-container">
<span class="tab active joshine-inline-block joshine-font-c-deepin review-js-with-image">All Reviews</span>
<span class="tab review-with-image joshine-inline-block review-js-with-image joshine-hidden">With Image</span>
</div>
<div class="joshine-review-select-wrap joshine-hidden">
<div class="joshine-review-select-item">
<label>Rating</label>
<select data-review-js="filter" name="rating" class="review-js-filter">
<option value="1">1 Star</option>
<option value="2">2 Star</option>
<option value="3">3 Star</option>
<option value="4">4 Star</option>
<option value="5">5 Star</option>
</select>
</div>
<div class="joshine-review-select-item">
<label>Verified Buyers</label>
<input type="checkbox" name="verified_buyers" class="checkbox review-js-filter">
</div>
<div class="joshine-review-select-item"><span>Sort by</span>
<label><?= /* @noEscape */ __('Sort By') ?></label>
<select name="sorter" class="review-js-sorter">
<option value="date">Date</option>
<option value="helpfulness">Helpfulness</option>
<option value="rating">Rating</option>
</select>
</div>
</div>
</div>
.joshine-review-container {
padding: 0;
}
.joshine-review-container .joshine-review-title {
font-size: 24px;
font-weight: 1000;
text-transform: capitalize;
}
.joshine-review-container .joshine-review-averate {
display: flex;
}
.joshine-review-container .joshine-review-item {
padding: 24px;
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-pack: center;
justify-content: center;
width: 33.33%;
}
.joshine-review-container .joshine-review-container .joshine-review-averate .add-new-box {
flex-grow:0;
flex-basis:auto;
font-size: 1.2em;
padding: .2em;
}
.joshine-review-container .joshine-review-container .joshine-review-averate .name {
margin-bottom: 15px;
}
.joshine-review-container .ave-rate {
align-items: center;
display: flex;
}
.joshine-review-container .ave-rate-total {
font-size: 22px;
font-weight: 700;
padding-left: 10px;
}
.joshine-review-container .joshine-review-item .fit-item .joshine-process {
background-color: #e4e4e4;
border-radius: 4px;
width: 190px;
}
.joshine-review-container .joshine-review-item .fit-item .joshine-process .joshine-process-active{
background-color: #222;
border-radius: 4px;
}
.joshine-review-container .joshine-review-item .fit-item {
display: flex;
align-items: center;
}
.joshine-review-container .joshine-review-item .fit-item .fit-name {
width: 80px;
}
.joshine-review-container .joshine-review-item.add-new-box .add-new-button {
font-weight: 400;
font-size: 22px;
}
.joshine-review-container .joshine-review-list {
padding: 20px 0;
}
.joshine-review-container .joshine-review-list-item {
display: flex;
position: relative;
width: 100%;
border-bottom: 1px dashed #e5e5e5;
padding: 25px 0;
}
.joshine-review-container .joshine-review-list-item-left {
width: 200px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-nickname {
width: 100%;
white-space: nowrap;
margin-bottom: 15px;
}
.joshine-review-container .joshine-review-list-item-mid {
width: calc(100% - 288px);
display: -webkit-box;
display: flex;
padding-right: 150px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-detail {
padding-right: 40px;
width: 50%;
}
.joshine-review-container .joshine-review-list-item .joshine-review-pics {
width: 50%;
display: flex;
}
.joshine-review-container .joshine-review-list-item .joshine-review-pic-item {
position: relative;
flex-shrink: 0;
width: 108px;
height: 108px;
line-height: 108px;
overflow: hidden;
margin-right: 5px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-des-title {
font-weight: 400;
margin-bottom: 5px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-des {
font-size: 14px;
line-height: 1.4em;
color: #5b5b5b;
}
.joshine-review-container .joshine-review-list-item .joshine-review-date {
position: absolute;
bottom: 28px;
}
.joshine-review-container .joshine-review-list-item .rate-fit {
display: inline-flex;
-webkit-box-align: center;
align-items: center;
flex-wrap: wrap;
padding-top: 10px;
}
.joshine-review-container .joshine-review-list-item .rate-fit .rate-fit-item{
margin-bottom: 5px;
margin-right: 14px;
}
.joshine-review-container .joshine-review-helpful {
display: inline-block;
position: absolute;
right: 0;
}
.joshine-review-container .joshine-review-helpful .joshine-like{
display: inline-block;
}
/*遮罩蒙层*/
.joshine-mask {
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
background: rgba(0, 0, 0, .8);
}
.joshine-review-container .joshine-review-tab-container {
margin-right: auto;
}
.joshine-review-container .joshine-review-tab {
font-size: 14px;
color: #666;
font-weight: 700;
position: relative;
display: inline-block;
}
.joshine-review-container .joshine-review-tab .tab {
padding: 25px 0;
cursor: pointer;
}
.joshine-review-container .joshine-review-tab .tab + .tab {
margin-left: 25px;
}
.joshine-review-container .joshine-review-filter {
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
}
.joshine-review-container .joshine-review-select-wrap {
padding: 10px;
font-size: .9em;
}
.joshine-review-container .joshine-review-filter .joshine-review-select-item {
display: inline-block;
}
.joshine-review-container .joshine-review-filter .joshine-review-select-item + .joshine-review-select-item {
margin-left: 25px;
}
.joshine-review-container .joshine-review-select-wrap .joshine-review-select-box {
padding: 5px 0;
position: relative;
display: inline-block;
}
.joshine-review-container .joshine-review-select-wrap .joshine-review-select-box-input {
height: 34px;
line-height: 32px;
display: inline-block;
}
.joshine-review-container .joshine-review-tab .tab.active {
border-bottom: #222 solid 2px;
}
.joshine-review-container .joshine-reviews-page {
margin-top: 15px;
margin-bottom: 15px;
}
.joshine-review-container .joshine-review-select-wrap select {
width: auto;
padding: .5em 2.2em .5em .5em;
height: auto;
line-height: normal;
}
.joshine-review-pic-item img {
width: 100%;
height: 7.5em ;
object-fit: cover;
}
.joshine-review-container-mobile .joshine-review-pics {
display: flex;
flex-wrap: wrap;
}
.joshine-review-container-mobile .joshine-review-pic-item{
padding: .1em;
}
.joshine-review-container-mobile .joshine-review-item .fit-item .joshine-process {
background-color: #e4e4e4;
border-radius: 4px;
width: 190px;
}
.joshine-review-container-mobile .joshine-review-item .fit-item .joshine-process .joshine-process-active{
background-color: #222;
border-radius: 4px;
}
.modal-reviews-details__image-swiper .swiper-button-next {
cursor : url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-next.ico), auto;
}
.modal-reviews-details__image-swiper .swiper-button-prev {
cursor : url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-pre.ico), auto;
}
\ No newline at end of file
define([
'jquery',
'ko'
], function ($) {
'use strict';
$.widget('mage.joshineReviewSlider', {
_create: function () {
},
_binding : function () {
},
});
return $.mage.joshineReviewSlider
});
\ No newline at end of file
define(['jquery', 'Magento_Ui/js/modal/modal'], function ($) {
'use strict';
return {
handleReviewsImgClick : function (element, event) {
$('.joshine-model-warp').removeClass('joshine-hidden');
},
handlePage : function (element, event) {
let self = $(this);
if (element.hasClass('pagination-btn-disabled')) {
return;
}
element.siblings().removeClass('active');
element.addClass('active');
let pageLoad = {
loaderContext: $('#product-review-container'),
};
event.preventDefault();
$.ajax({
url: element.data('href'),
cache: true,
}).done(function (data) {
pageLoad.loaderContext.html(data);
});
},
}
});
\ No newline at end of file
define([
'jquery',
'ko',
'joshineReviewHandler'
], function ($, ko, handler) {
'use strict';
$.widget('mage.joshineReview', {
_create: function () {
var self = this;
self._binding();
},
_binding : function () {
$(document).on('click', '.joshine-pagination-btn', function(event){
handler.handlePage($(this), event);
});
},
});
return $.mage.joshineReview
});
define([
'jquery',
'ko',
'joshineReviewConfig'
], function ($, ko, config) {
'use strict';
return {
isApi : function (url) {
var apisArr = config.apis;
return apisArr.indexOf(url);
}
}
});
\ No newline at end of file
...@@ -13,10 +13,12 @@ define([ ...@@ -13,10 +13,12 @@ define([
$(element).mage('validation', { $(element).mage('validation', {
/** @inheritdoc */ /** @inheritdoc */
errorPlacement: function (error, el) { errorPlacement: function (error, el) {
if (el.parents('#product-review-table').length) { if (el.parents('#product-review-table').length) {
$('#product-review-table').siblings(this.errorElement + '.' + this.errorClass).remove(); $('#product-review-table').siblings(this.errorElement + '.' + this.errorClass).remove();
$('#product-review-table').after(error); $('#product-review-table').after(error);
} else if(el.parents('.options-list').length ) {
$('.options-list').siblings(this.errorElement + '.' + this.errorClass).remove();
$('.options-list').after(error);
} else { } else {
el.after(error); el.after(error);
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
/** /**
* Pager template * Pager template
* *
* @see \Magento\Theme\Block\Html\Pager * @var \Magento\Theme\Block\Html\Pager $block
*/ */
?> ?>
<?php if ($block->getCollection()->getSize()): ?> <?php if ($block->getCollection()->getSize()): ?>
...@@ -117,22 +117,6 @@ ...@@ -117,22 +117,6 @@
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($block->isShowPerPage()): ?>
<div class="limiter">
<strong class="limiter-label"><?= $block->escapeHtml(__('Show')) ?></strong>
<select id="limiter" data-mage-init='{"redirectUrl": {"event":"change"}}' class="limiter-options">
<?php foreach ($block->getAvailableLimit() as $_key => $_limit): ?>
<option value="<?= $block->escapeUrl($block->getLimitUrl($_key)) ?>"
<?php if ($block->isLimitCurrent($_key)): ?>
selected="selected"<?php endif ?>>
<?= $block->escapeHtml($_limit) ?>
</option>
<?php endforeach; ?>
</select>
<span class="limiter-text"><?= $block->escapeHtml(__('per page')) ?></span>
</div>
<?php endif ?>
<?php if ($block->getUseContainer()): ?> <?php if ($block->getUseContainer()): ?>
</div> </div>
<?php endif ?> <?php endif ?>
......
define([
'jquery',
'underscore',
'Rokanthemes_OnePageCheckout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/model/error-processor',
'Magento_Checkout/js/model/shipping-service',
'Magento_Checkout/js/model/totals',
'Rokanthemes_OnePageCheckout/js/model/payment-service',
'mage/url',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/payment/method-converter',
'Magento_Checkout/js/model/payment-service',
'Rokanthemes_OnePageCheckout/js/model/update-item-service',
'Magento_Ui/js/model/messageList',
'Magento_Checkout/js/action/get-totals',
'Magento_Checkout/js/action/select-shipping-method',
'Magento_Checkout/js/checkout-data',
'Rokanthemes_OnePageCheckout/js/model/shipping-save-processor'
], function (
$,
_,
urlBuilder,
storage,
errorProcessor,
shippingService,
totals,
paymentService,
url,
quote,
methodConverter,
paymentServiceDefault,
updateItemService,
globalMessageList,
getTotalsAction,
selectShippingMethodAction,
checkoutData,
shippingSaveProcessor
) {
'use strict';
return function (item) {
var serviceUrl = urlBuilder.getUpdateQtyUrl(),
address = quote.shippingAddress();
shippingService.isLoading(true);
totals.isLoading(true);
paymentService.isLoading(true);
return storage.post(
serviceUrl,
JSON.stringify({
address: {
'region_id': address.regionId,
'region': address.region,
'country_id': address.countryId,
'postcode': address.postcode
},
itemId: parseInt(item.item_id),
qty: parseFloat(item.qty)
})
).done(function (response) {
if (response.has_error && response.status) {
globalMessageList.addSuccessMessage(response);
window.location.replace(url.build('checkout/cart/'));
} else {
if (response.status) {
globalMessageList.addSuccessMessage(response);
updateItemService.hasUpdateResult(true);
//shippingService.setShippingRates(response.shipping_methods);
paymentServiceDefault.setPaymentMethods(methodConverter(response.payment_methods));
var sh = response.shipping_methods;
var new_sh = [];
selectShippingMethodAction(sh[0]);
checkoutData.setSelectedShippingRate(sh[0]['carrier_code'] + '_' + sh[0]['method_code']);
quote.shippingMethod(sh[0]);
new_sh.push(sh[0]);
shippingService.setShippingRates(new_sh);
updateItemService.hasUpdateResult(false);
response.totals.coupon_code ? paymentService.isAppliedCoupon(true) : paymentService.isAppliedCoupon(false);
var deferred = $.Deferred();
getTotalsAction([], deferred);
$('.items-in-cart').find('[data-bind="text: getCartSummaryItemsCount()"]')
.text(response['totals']['items_qty']);
shippingSaveProcessor.saveShippingInformation(quote.shippingAddress().getType());
} else {
globalMessageList.addErrorMessage(response);
}
}
}).fail(function (response) {
errorProcessor.process(response);
}).always(function () {
shippingService.isLoading(false);
totals.isLoading(false);
paymentService.isLoading(false);
});
};
});
define([
'Magento_Checkout/js/model/resource-url-manager',
'Magento_Checkout/js/model/quote',
'mage/storage',
'Magento_Checkout/js/model/shipping-service',
'Magento_Checkout/js/model/shipping-rate-registry',
'Magento_Checkout/js/model/error-processor'
], function (resourceUrlManager, quote, storage, shippingService, rateRegistry, errorProcessor) {
'use strict';
return function (address) {
var serviceUrl, payload;
shippingService.isLoading(true);
serviceUrl = resourceUrlManager.getUrlForEstimationShippingMethodsForNewAddress(quote);
payload = JSON.stringify({
address: {
'street': address.street,
'city': address.city,
'region_id': address.regionId,
'region': address.region,
'country_id': address.countryId,
'postcode': address.postcode,
'email': address.email,
'customer_id': address.customerId,
'firstname': address.firstname,
'lastname': address.lastname,
'middlename': address.middlename,
'prefix': address.prefix,
'suffix': address.suffix,
'vat_id': address.vatId,
'company': address.company,
'telephone': address.telephone,
'fax': address.fax,
'custom_attributes': address.customAttributes,
'save_in_address_book': address.saveInAddressBook
}
}
);
return storage.post(
serviceUrl, payload, false
).done(function (result) {
var new_sh = [];
new_sh.push(result[0]);
rateRegistry.set(address.getCacheKey(), result);
shippingService.setShippingRates(new_sh);
}).fail(function (response) {
shippingService.setShippingRates([]);
errorProcessor.process(response);
}).always(function () {
shippingService.isLoading(false);
});
}
});
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
<?php <?php
$isModuleEnable = $block->getConfig('sparsh_free_shipping_bar/general/enable'); $isModuleEnable = $block->getConfig('sparsh_free_shipping_bar/general/enable');
$isFreeShippingBarAvailable = null; $isFreeShippingBarAvailable = null;
if ($isModuleEnable): if ($isModuleEnable):
$layoutPosition = $block->getData('position') ? $block->getData('position') : null; $layoutPosition = $block->getData('position') ? $block->getData('position') : null;
$entityId = $block->getData('entity_id') ? $block->getData('entity_id') : null; $entityId = $block->getData('entity_id') ? $block->getData('entity_id') : null;
...@@ -29,18 +28,15 @@ endif; ...@@ -29,18 +28,15 @@ endif;
.sparsh-free-shipping-bar-goal-message{ .sparsh-free-shipping-bar-goal-message{
width: 100%; width: 100%;
height: 60px; height: 60px;
background-image: url("<?= $block->getImgUrl($barData['background_img']) ?>");
line-height: 60px; line-height: 60px;
background-size: 100% 100%!important; background-size: 100% 100%!important;
background-repeat: no-repeat; background-repeat: no-repeat;
text-align: center;
}
.sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>{
background-image: url('<?= $block->getImgUrl($barData['background_img']) ?>');
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.sparsh-free-shipping-bar-goal-message{ .sparsh-free-shipping-bar-goal-message{
background-image: url("<?= $block->getImgUrl($barData['background_img_phone']) ?>");
width: 100%; width: 100%;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
...@@ -48,17 +44,14 @@ endif; ...@@ -48,17 +44,14 @@ endif;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>{
background-image: url('<?= $block->getImgUrl($barData['background_img_phone']) ?>');
}
} }
</style> </style>
<div class="sparsh-free-shipping-bar-goal-message sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>" <div class="sparsh-free-shipping-bar-goal-message"
style=" background-color: <?= /* @noEscape */ $barData['bar_background_color']?>; font-size: <?= /* @noEscape */ $barData['bar_font_size'].'px'?>"> style=" background-size: 100%; text-align: center; background-color: <?= /* @noEscape */ $barData['bar_background_color']?>; font-size: <?= /* @noEscape */ $barData['bar_font_size'].'px'?>">
<a <?= /* @noEscape */ $barData['is_clickable'] ? 'href='.$barData['bar_link_url'] : null ?> <a <?= /* @noEscape */ $barData['is_clickable'] ? 'href='.$barData['bar_link_url'] : null ?>
<?= /* @noEscape */ $barData['is_clickable'] ? ($barData['is_link_open_in_new_page'] ? 'target=_blank' : 'target=_self') : null ?> <?= /* @noEscape */ $barData['is_clickable'] ? ($barData['is_link_open_in_new_page'] ? 'target=_blank' : 'target=_self') : null ?>
style="width: 100%;height: 100%; display: block; color: <?= /* @noEscape */ $barData['bar_text_color']?>"> style="width: 100%;height: 100%; display: block; color: <?= /* @noEscape */ $barData['bar_text_color']?>">
<div data-bind="scope: 'free-shipping-scope<?= $barData['entity_id']; ?>'"> <div data-bind="scope: 'free-shipping-scope'">
<p data-bind="html: goalMessage"></p> <p data-bind="html: goalMessage"></p>
</div> </div>
</a> </a>
...@@ -68,7 +61,7 @@ endif; ...@@ -68,7 +61,7 @@ endif;
"*": { "*": {
"Magento_Ui/js/core/app": { "Magento_Ui/js/core/app": {
"components": { "components": {
"free-shipping-scope<?= $barData['entity_id']; ?>": { "free-shipping-scope": {
"component": "Sparsh_FreeShippingBar/js/free_shipping_bar", "component": "Sparsh_FreeShippingBar/js/free_shipping_bar",
"goal": "<?= /* @noEscape */ $barData['goal'] ?>", "goal": "<?= /* @noEscape */ $barData['goal'] ?>",
"currency": "<?= /* @noEscape */ $block->getCurrentCurrencySymbol() ?>", "currency": "<?= /* @noEscape */ $block->getCurrentCurrencySymbol() ?>",
......
...@@ -11,15 +11,18 @@ ...@@ -11,15 +11,18 @@
.field.size-fits .field.choice { .field.size-fits .field.choice {
clear: both; clear: both;
display: inline-block; display: inline-block;
padding: 0 5px;
line-height: 30px; line-height: 30px;
margin-right: 20px; margin-right: 20px;
} }
/* 猫头鹰选择 */
.size-fits + .size-fits {
padding: 0 5px;
}
.field.size-fits .field.choice input { .field.size-fits .field.choice input {
margin-right: 5px; margin-right: 5px;
} }
</style> </style>
<div class="block review-add"> <div class="block review-add joshine-bg-silver joshine-hidden">
<div class="block-title"><strong><?= $block->escapeHtml(__('Write Your Own Review')) ?></strong></div> <div class="block-title"><strong><?= $block->escapeHtml(__('Write Your Own Review')) ?></strong></div>
<div class="block-content"> <div class="block-content">
<?php if ($block->getAllowWriteReviewFlag()):?> <?php if ($block->getAllowWriteReviewFlag()):?>
......
...@@ -2225,6 +2225,8 @@ font-weight: bold; ...@@ -2225,6 +2225,8 @@ font-weight: bold;
.product.info .review-add { .product.info .review-add {
float: left; float: left;
width: 100% !important; width: 100% !important;
padding: 24px;
margin-bottom: 20px;
} }
@media (max-width: 768px){ @media (max-width: 768px){
...@@ -2701,7 +2703,7 @@ tr.grand.totals { ...@@ -2701,7 +2703,7 @@ tr.grand.totals {
} }
} }
.choice.field { .size-fits + .size-fits {
padding-left: 15px; padding-left: 15px;
} }
...@@ -2843,4 +2845,5 @@ strong#block-related-heading,strong#block-upsell-heading{ font-weight: 600; colo ...@@ -2843,4 +2845,5 @@ strong#block-related-heading,strong#block-upsell-heading{ font-weight: 600; colo
} }
/*导入Joshine工具类*/ /*导入Joshine工具类*/
@import "./_joshine_col"; @import "./_joshine_col";
@import "./_joshine_utils";
...@@ -733,9 +733,9 @@ ...@@ -733,9 +733,9 @@
} }
.joshine-center-block { .joshine-center-block {
display: block; display: block !important;
margin-right: auto; margin-right: auto !important;
margin-left: auto; margin-left: auto !important;
} }
.joshine-pull-right { .joshine-pull-right {
float: right !important; float: right !important;
......
/*正常大小*/
.joshine-rating-container {
overflow: hidden;
margin: 0 0 10px;
width: 160px;
height: 27px;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMjciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgzM3YyN0gweiIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTcuMzA1IDEwLjMxM0wxNCAwbC0zLjMwNSAxMC4zMTNIMGw4LjY1MiA2LjM3NEw1LjM0OCAyNyAxNCAyMC42MjYgMjIuNjUyIDI3bC0zLjMwNS0xMC4zMTNMMjggMTAuMzEzSDE3LjMwNXptNC42NTQgMS45OWgtNi4wOEwxNCA2LjQ0bC0xLjg3OSA1Ljg2M2gtNi4wOGw0LjkxOSAzLjYyNC0xLjg4IDUuODYzTDE0IDE4LjE2Nmw0LjkyIDMuNjI0LTEuODgtNS44NjMgNC45Mi0zLjYyNHoiIGZpbGw9IiNCMUFFQUUiLz48L3N2Zz4=);
}
.joshine-rating-container .joshine-rating-starts {
height: 100%;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMjciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgzM3YyN0gweiIvPjxwYXRoIGQ9Ik0xNCAwbDMuMzA1IDEwLjMxM0gyOGwtOC42NTIgNi4zNzRMMjIuNjUyIDI3IDE0IDIwLjYyNiA1LjM0OCAyN2wzLjMwNC0xMC4zMTNMMCAxMC4zMTNoMTAuNjk1TDE0IDB6IiBmaWxsPSIjRUQ5RDAwIi8+PC9zdmc+);
}
/*小号评分容器*/
.joshine-rating-container.joshine-rating-small {
width: 107px;
height: 18px;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyMnYxOEgweiIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTEuNzQzIDYuODc1TDkuNSAwIDcuMjU3IDYuODc1SDBsNS44NzEgNC4yNUwzLjYzIDE4IDkuNSAxMy43NSAxNS4zNzEgMThsLTIuMjQyLTYuODc1TDE5IDYuODc1aC03LjI1N3pNMTQuOSA4LjIwMmgtNC4xMjZMOS41IDQuMjkzbC0xLjI3NSAzLjkxSDQuMDk5bDMuMzM4IDIuNDE1LTEuMjc1IDMuOTA5TDkuNSAxMi4xMWwzLjMzOCAyLjQxNi0xLjI3NS0zLjkxIDMuMzM4LTIuNDE1eiIgZmlsbD0iI0IxQUVBRSIvPjwvc3ZnPg==);
}
/*小号评分图标*/
.joshine-rating-container.joshine-rating-small .joshine-rating-starts {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyMnYxOEgweiIvPjxwYXRoIGQ9Ik05LjUgMGwyLjI0MyA2Ljg3NUgxOWwtNS44NzEgNC4yNUwxNS4zNyAxOCA5LjUgMTMuNzUgMy42MjkgMThsMi4yNDItNi44NzVMMCA2Ljg3NWg3LjI1N0w5LjUgMHoiIGZpbGw9IiNFRDlEMDAiLz48L3N2Zz4=);
}
/*点赞图标*/
.joshine-like {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyMHYxOEgweiIvPjxwYXRoIGQ9Ik0zLjg5NiA3LjI4NkguNTU2QS41NTQuNTU0IDAgMCAwIDAgNy44NDh2OC45ODVjMCAuMzE0LjI0NS41NjEuNTU3LjU2MWgzLjM0YS41NTMuNTUzIDAgMCAwIC41NTYtLjU2MVY3Ljg0OGEuNTUzLjU1MyAwIDAgMC0uNTU3LS41NjJ6TTIwIDguMzVjMC0uOTE1LS42MjMtMS44NC0xLjgxMy0xLjg0aC01LjM5MmMuNzctMS4zOTEuOTk3LTMuMzQ3LjQ2Mi00Ljc3OEMxMi44NjMuNjggMTIuMTEuMDY2IDExLjEzOS4wMDRsLS4wMTYtLjAwMkExLjE3IDEuMTcgMCAwIDAgOS44ODkgMS4wOEM5Ljc1MSAyLjUgOS4xMzQgNS4wMSA4LjI1MSA1LjljLS43NDQuNzUtMS4zOCAxLjA2NS0yLjQzNiAxLjU4Ni0uMTUyLjA3NS0uMzIuMTU3LS40OTYuMjQ2YTEuNCAxLjQgMCAwIDEgLjAwNS4xMTd2OC44OTNsLjM3Ny4xM0M3LjQ0MyAxNy40NzggOC45NDggMTggMTEuMjQ4IDE4aDQuMzZjMS4xOSAwIDEuODEyLS45MjYgMS44MTItMS44NDFhMiAyIDAgMCAwLS4xNjMtLjc5MyAxLjcxOCAxLjcxOCAwIDAgMCAxLS41NTljLjI5Mi0uMzM0LjQ1My0uNzc5LjQ1My0xLjI1MSAwLS4yNy0uMDU1LS41NDMtLjE2My0uNzkyLjk1Ni0uMTY2IDEuNDUzLS45OTMgMS40NTMtMS44MSAwLS40NzUtLjE2OC0uOTUzLS40OTQtMS4zMDIuMzI2LS4zNS40OTQtLjgyNy40OTQtMS4zMDJ6IiBmaWxsPSIjQzRDNEM0Ii8+PC9zdmc+);
margin: 0 0 0 10px;
width: 20px;
height: 18px;
display: inline-block;
cursor: pointer;
transition: .3s;
}
.joshine-like.active {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyMHYxOEgweiIvPjxwYXRoIGQ9Ik0zLjg5NiA3LjI4NkguNTU2QS41NTQuNTU0IDAgMCAwIDAgNy44NDh2OC45ODVjMCAuMzE0LjI0NS41NjEuNTU3LjU2MWgzLjM0YS41NTMuNTUzIDAgMCAwIC41NTYtLjU2MVY3Ljg0OGEuNTUzLjU1MyAwIDAgMC0uNTU3LS41NjJ6TTIwIDguMzVjMC0uOTE1LS42MjMtMS44NC0xLjgxMy0xLjg0aC01LjM5MmMuNzctMS4zOTEuOTk3LTMuMzQ3LjQ2Mi00Ljc3OEMxMi44NjMuNjggMTIuMTEuMDY2IDExLjEzOS4wMDRsLS4wMTYtLjAwMkExLjE3IDEuMTcgMCAwIDAgOS44ODkgMS4wOEM5Ljc1MSAyLjUgOS4xMzQgNS4wMSA4LjI1MSA1LjljLS43NDQuNzUtMS4zOCAxLjA2NS0yLjQzNiAxLjU4Ni0uMTUyLjA3NS0uMzIuMTU3LS40OTYuMjQ2YTEuNCAxLjQgMCAwIDEgLjAwNS4xMTd2OC44OTNsLjM3Ny4xM0M3LjQ0MyAxNy40NzggOC45NDggMTggMTEuMjQ4IDE4aDQuMzZjMS4xOSAwIDEuODEyLS45MjYgMS44MTItMS44NDFhMiAyIDAgMCAwLS4xNjMtLjc5MyAxLjcxOCAxLjcxOCAwIDAgMCAxLS41NTljLjI5Mi0uMzM0LjQ1My0uNzc5LjQ1My0xLjI1MSAwLS4yNy0uMDU1LS41NDMtLjE2My0uNzkyLjk1Ni0uMTY2IDEuNDUzLS45OTMgMS40NTMtMS44MSAwLS40NzUtLjE2OC0uOTUzLS40OTQtMS4zMDIuMzI2LS4zNS40OTQtLjgyNy40OTQtMS4zMDJ6IiBmaWxsPSIjMUFCOTAwIi8+PC9zdmc+);
}
.joshine-diss {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMThoMjBWMEgweiIvPjxwYXRoIGQ9Ik0zLjg5NiAxMC43MTRILjU1NkEuNTU0LjU1NCAwIDAgMSAwIDEwLjE1MlYxLjE2N0MwIC44NTMuMjQ1LjYwNi41NTcuNjA2aDMuMzRjLjMxMSAwIC41NTYuMjQ3LjU1Ni41NjF2OC45ODVhLjU1My41NTMgMCAwIDEtLjU1Ny41NjJ6TTIwIDkuNjVjMCAuOTE1LS42MjMgMS44NC0xLjgxMyAxLjg0aC01LjM5MmMuNzcgMS4zOTEuOTk3IDMuMzQ3LjQ2MiA0Ljc3Ny0uMzk0IDEuMDUzLTEuMTQ2IDEuNjY3LTIuMTE4IDEuNzNoLS4wMTZhMS4xNyAxLjE3IDAgMCAxLTEuMjM0LTEuMDc2Yy0uMTM4LTEuNDItLjc1NS0zLjkzLTEuNjM4LTQuODItLjc0NC0uNzUtMS4zOC0xLjA2NS0yLjQzNi0xLjU4Ni0uMTUyLS4wNzUtLjMyLS4xNTctLjQ5Ni0uMjQ2YTEuNCAxLjQgMCAwIDAgLjAwNS0uMTE3VjEuMjZsLjM3Ny0uMTNDNy40NDMuNTIyIDguOTQ4IDAgMTEuMjQ4IDBoNC4zNmMxLjE5IDAgMS44MTIuOTI2IDEuODEyIDEuODQxYTIgMiAwIDAgMS0uMTYzLjc5M2MuMzk3LjA3Mi43NDMuMjY0IDEgLjU1OS4yOTIuMzM0LjQ1My43NzkuNDUzIDEuMjUxIDAgLjI3LS4wNTUuNTQzLS4xNjMuNzkyLjk1Ni4xNjYgMS40NTMuOTkzIDEuNDUzIDEuODEgMCAuNDc1LS4xNjguOTUyLS40OTQgMS4zMDIuMzI2LjM1LjQ5NC44MjcuNDk0IDEuMzAyeiIgZmlsbD0iI0M0QzRDNCIvPjwvc3ZnPg==);
}
.joshine-diss.active {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMThoMjBWMEgweiIvPjxwYXRoIGQ9Ik0zLjg5NiAxMC43MTRILjU1NkEuNTU0LjU1NCAwIDAgMSAwIDEwLjE1MlYxLjE2N0MwIC44NTMuMjQ1LjYwNi41NTcuNjA2aDMuMzRjLjMxMSAwIC41NTYuMjQ3LjU1Ni41NjF2OC45ODVhLjU1My41NTMgMCAwIDEtLjU1Ny41NjJ6TTIwIDkuNjVjMCAuOTE1LS42MjMgMS44NC0xLjgxMyAxLjg0aC01LjM5MmMuNzcgMS4zOTEuOTk3IDMuMzQ3LjQ2MiA0Ljc3Ny0uMzk0IDEuMDUzLTEuMTQ2IDEuNjY3LTIuMTE4IDEuNzNoLS4wMTZhMS4xNyAxLjE3IDAgMCAxLTEuMjM0LTEuMDc2Yy0uMTM4LTEuNDItLjc1NS0zLjkzLTEuNjM4LTQuODItLjc0NC0uNzUtMS4zOC0xLjA2NS0yLjQzNi0xLjU4Ni0uMTUyLS4wNzUtLjMyLS4xNTctLjQ5Ni0uMjQ2YTEuNCAxLjQgMCAwIDAgLjAwNS0uMTE3VjEuMjZsLjM3Ny0uMTNDNy40NDMuNTIyIDguOTQ4IDAgMTEuMjQ4IDBoNC4zNmMxLjE5IDAgMS44MTIuOTI2IDEuODEyIDEuODQxYTIgMiAwIDAgMS0uMTYzLjc5M2MuMzk3LjA3Mi43NDMuMjY0IDEgLjU1OS4yOTIuMzM0LjQ1My43NzkuNDUzIDEuMjUxIDAgLjI3LS4wNTUuNTQzLS4xNjMuNzkyLjk1Ni4xNjYgMS40NTMuOTkzIDEuNDUzIDEuODEgMCAuNDc1LS4xNjguOTUyLS40OTQgMS4zMDIuMzI2LjM1LjQ5NC44MjcuNDk0IDEuMzAyeiIgZmlsbD0iI0QyMzczNyIvPjwvc3ZnPg==);
}
/**进度条*/
.joshine-process {
width: 80px;
height: 6px;
background: #dfdfdf;
vertical-align: middle;
display: inline-block;
line-height: 14px;
}
.joshine-process .joshine-process-active {
height: 100%;
margin: 0;
background: #ed9d00;
}
/*背景色组*/
.joshine-bg-default {
background-color: #f7f8fa !important;
}
.joshine-bg-darkgray {
background-color: #767676 !important;
}
.joshine-bg-gray {
background-color: #999 !important;
}
.joshine-bg-silver {
background-color: #f7f8fa;
}
.joshine-bg-img-default {
cursor: pointer;
background-image: url(../images/placeholders_small.jpg);
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
/*玄铁灰*/
.joshine-font-c-darkgray {
color: #767676 !important;
}
/*沉稳灰*/
.joshine-font-c-gray {
color: #999 !important;
}
/*神秘银*/
.joshine-font-c-silver {
color: #f7f8fa;
}
/*深邃黑*/
.joshine-font-c-deepin {
color: #222222 !important;
}
/*字重组*/
.joshine-font-w-bolder{
font-weight: bolder;
}
/*字号组*/
.joshine-font-size-default {
font-size: 16px;
}
.joshine-font-mini {
font-size: 12px;
}
.joshine-font-mini-plus {
font-size:13px;
}
.joshine-font-text {
font-size:14px;
}
.joshine-foot-mark {
font-size: 13px;
line-height: 13px;
color: #999;
}
/*按钮*/
.joshine-btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
}
.joshine-btn:focus,
.joshine-btn:active:focus,
.joshine-btn.active:focus,
.joshine-btn.focus,
.joshine-btn:active.focus,
.joshine-btn.active.focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.joshine-btn:active,
.joshine-btn.active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.joshine-btn.disabled,
.joshine-btn[disabled],
fieldset[disabled] .joshine-btn {
cursor: not-allowed;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
opacity: .65;
}
a.joshine-btn.disabled,
fieldset[disabled] a.joshine-btn {
pointer-events: none;
}
.joshine-btn-dark {
color: #fff;
background-color: #000;
border-color: #000;
}
.joshine-inline-block {
display: inline-block;
}
.joshine-pagination {
display: flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 24px;
user-select: none;
color: #222;
font-size: 14px;
}
.joshine-pagination .joshine-pagination-btn {
margin-left: 12px;
padding: 0 4px;
border-radius: 12px;
text-align: center;
color: #222;
font-weight: 700;
min-width: 24px;
height: 24px;
line-height: normal;
display: flex;
align-items: center;
justify-content: center;
}
.joshine-pagination .joshine-pagination-btn.active {
color: #fff;
background: #222;
}
.joshine-pagination-hover:not(.active) {
cursor: pointer;
}
.joshine-pagination-hover:not(.active):hover {
background: #e0e0e0;
}
.joshine-pagination .joshine-pagination-next {
margin-left: 12px;
}
.joshine-pagination .joshine-pagination-btn.pagination-btn-disabled {
cursor: not-allowed;
color: #c2c2c2;
}
.joshine-pagination-right {
justify-content: flex-end;
}
.joshine-pagination-left {
justify-content: flex-start;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment