@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="dailyfresh/settings.py" />
|
||||||
|
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="migrations" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.9 (dailyfresh-master)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="Django default" uuid="9064b16a-5590-4f62-8cff-ddf29d23a4a8">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<remarks>$PROJECT_DIR$/dailyfresh/settings.py</remarks>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://127.0.0.1:3306/dailyfresh</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/static/页面说明.txt" charset="GBK" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,24 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myValues">
|
||||||
|
<value>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="placeholder" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myCustomValuesEnabled" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="Django" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (online_store)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (dailyfresh-master)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/dailyfresh-master.iml" filepath="$PROJECT_DIR$/.idea/dailyfresh-master.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CartConfig(AppConfig):
|
||||||
|
name = 'apps.cart'
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
@ -0,0 +1,11 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from apps.cart.views import CartAddView, CartInfoView, CartUpdateView, CartDeleteView
|
||||||
|
|
||||||
|
app_name = 'cart'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('add', CartAddView.as_view(), name='add'), # 添加购物车记录
|
||||||
|
path('', CartInfoView.as_view(), name='show'), # 显示购物车页面
|
||||||
|
path('update', CartUpdateView.as_view(), name='update'), # 更新购物车记录
|
||||||
|
path('delete', CartDeleteView.as_view(), name='delete'), # 删除购物车记录
|
||||||
|
]
|
@ -0,0 +1,161 @@
|
|||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.views.generic.base import View
|
||||||
|
from django_redis import get_redis_connection
|
||||||
|
|
||||||
|
from apps.goods.models import GoodsSKU
|
||||||
|
from utils.mixin import LoginRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
|
# 购物车视图
|
||||||
|
|
||||||
|
class CartAddView(View):
|
||||||
|
"""购物车记录添加"""
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""处理购物车记录添加请求"""
|
||||||
|
user = request.user
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
|
||||||
|
|
||||||
|
# 接收数据
|
||||||
|
sku_id = request.POST.get('sku_id')
|
||||||
|
count = request.POST.get('count')
|
||||||
|
|
||||||
|
# 数据校验
|
||||||
|
if not all([sku_id, count]):
|
||||||
|
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
|
||||||
|
|
||||||
|
# 校验添加商品的数量
|
||||||
|
try:
|
||||||
|
count = int(count)
|
||||||
|
except ValueError:
|
||||||
|
return JsonResponse({'res': 2, 'errmsg': '商品数量出错'})
|
||||||
|
|
||||||
|
# 校验商品是否存在
|
||||||
|
try:
|
||||||
|
sku = GoodsSKU.objects.get(id=sku_id)
|
||||||
|
except GoodsSKU.DoesNotExist:
|
||||||
|
return JsonResponse({'res': 3, 'errmsg': '商品不存在'})
|
||||||
|
|
||||||
|
# 业务处理:添加购物车记录
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
cart_count = conn.hget(cart_key, sku_id)
|
||||||
|
if cart_count:
|
||||||
|
count += int(cart_count)
|
||||||
|
|
||||||
|
# 校验商品的库存
|
||||||
|
if count > sku.stock:
|
||||||
|
return JsonResponse({'res': 4, 'errmsg': '商品库存不足'})
|
||||||
|
|
||||||
|
conn.hset(cart_key, sku_id, count)
|
||||||
|
# 获取购物车商品条目数
|
||||||
|
total_count = conn.hlen(cart_key)
|
||||||
|
|
||||||
|
# 返回应答
|
||||||
|
return JsonResponse({'res': 5, 'message': '添加成功', 'total_count': total_count})
|
||||||
|
|
||||||
|
|
||||||
|
class CartInfoView(LoginRequiredMixin, View):
|
||||||
|
"""显示购物车页面"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""处理显示购物车页面请求"""
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# 获取用户购物车中的商品信息
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
cart_dict = conn.hgetall(cart_key)
|
||||||
|
|
||||||
|
skus = []
|
||||||
|
total_count = 0
|
||||||
|
total_price = 0
|
||||||
|
|
||||||
|
# 遍历获取商品的信息
|
||||||
|
for sku_id, count in cart_dict.items():
|
||||||
|
sku = GoodsSKU.objects.get(id=sku_id)
|
||||||
|
count = int(count)
|
||||||
|
amount = sku.price * count
|
||||||
|
sku.amount = amount
|
||||||
|
sku.count = count
|
||||||
|
skus.append(sku)
|
||||||
|
|
||||||
|
total_count += count
|
||||||
|
total_price += amount
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'total_count': total_count,
|
||||||
|
'total_price': total_price,
|
||||||
|
'skus': skus,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'cart.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
class CartUpdateView(View):
|
||||||
|
"""购物车记录更新"""
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""处理购物车记录更新请求"""
|
||||||
|
user = request.user
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
|
||||||
|
|
||||||
|
sku_id = request.POST.get('sku_id')
|
||||||
|
count = request.POST.get('count')
|
||||||
|
|
||||||
|
if not all([sku_id, count]):
|
||||||
|
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
count = int(count)
|
||||||
|
except ValueError:
|
||||||
|
return JsonResponse({'res': 2, 'errmsg': '商品数量出错'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
sku = GoodsSKU.objects.get(id=sku_id)
|
||||||
|
except GoodsSKU.DoesNotExist:
|
||||||
|
return JsonResponse({'res': 3, 'errmsg': '商品不存在'})
|
||||||
|
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
|
||||||
|
if count > sku.stock:
|
||||||
|
return JsonResponse({'res': 4, 'errmsg': '商品库存不足'})
|
||||||
|
|
||||||
|
conn.hset(cart_key, sku_id, count)
|
||||||
|
|
||||||
|
total_count = sum(int(val) for val in conn.hvals(cart_key))
|
||||||
|
|
||||||
|
return JsonResponse({'res': 5, 'message': '更新成功', 'total_count': total_count})
|
||||||
|
|
||||||
|
|
||||||
|
class CartDeleteView(View):
|
||||||
|
"""购物车记录删除"""
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""处理购物车记录删除请求"""
|
||||||
|
user = request.user
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
|
||||||
|
|
||||||
|
sku_id = request.POST.get('sku_id')
|
||||||
|
|
||||||
|
if not sku_id:
|
||||||
|
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
sku = GoodsSKU.objects.get(id=sku_id)
|
||||||
|
except GoodsSKU.DoesNotExist:
|
||||||
|
return JsonResponse({'res': 2, 'errmsg': '商品不存在'})
|
||||||
|
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
|
||||||
|
conn.hdel(cart_key, sku_id)
|
||||||
|
|
||||||
|
total_count = sum(int(val) for val in conn.hvals(cart_key))
|
||||||
|
|
||||||
|
return JsonResponse({'res': 5, 'message': '删除成功', 'total_count': total_count})
|
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GoodsConfig(AppConfig):
|
||||||
|
name = 'apps.goods'
|
@ -0,0 +1,150 @@
|
|||||||
|
# Generated by Django 2.2.3 on 2019-07-26 10:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import tinymce.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GoodsSKU',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=20, verbose_name='商品名称')),
|
||||||
|
('desc', models.CharField(max_length=256, verbose_name='商品简介')),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品价格')),
|
||||||
|
('unite', models.CharField(max_length=20, verbose_name='商品单位')),
|
||||||
|
('image', models.ImageField(upload_to='goods', verbose_name='商品图片')),
|
||||||
|
('stock', models.IntegerField(default=1, verbose_name='商品库存')),
|
||||||
|
('sales', models.IntegerField(default=0, verbose_name='商品销量')),
|
||||||
|
('status', models.SmallIntegerField(choices=[(0, '下线'), (1, '上线')], default=1, verbose_name='商品状态')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商品',
|
||||||
|
'verbose_name_plural': '商品',
|
||||||
|
'db_table': 'df_goods_sku',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GoodsSPU',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=20, verbose_name='商品SPU名称')),
|
||||||
|
('detail', tinymce.models.HTMLField(blank=True, verbose_name='商品详情')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商品SPU',
|
||||||
|
'verbose_name_plural': '商品SPU',
|
||||||
|
'db_table': 'df_goods_spu',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GoodsType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=20, verbose_name='种类名称')),
|
||||||
|
('logo', models.CharField(max_length=20, verbose_name='标识')),
|
||||||
|
('image', models.ImageField(upload_to='type', verbose_name='商品类型图片')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商品种类',
|
||||||
|
'verbose_name_plural': '商品种类',
|
||||||
|
'db_table': 'df_goods_type',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IndexPromotionBanner',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('url', models.URLField(verbose_name='活动链接')),
|
||||||
|
('name', models.CharField(max_length=20, verbose_name='活动名称')),
|
||||||
|
('image', models.ImageField(upload_to='goods', verbose_name='图片路径')),
|
||||||
|
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '主页促销活动',
|
||||||
|
'verbose_name_plural': '主页促销活动',
|
||||||
|
'db_table': 'df_index_promotion',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IndexTypeGoodsBanner',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('display_type', models.SmallIntegerField(choices=[(0, '标题'), (1, '图片')], default=1, verbose_name='商品显示方式')),
|
||||||
|
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
|
||||||
|
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
|
||||||
|
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsType', verbose_name='商品类型')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '主页分类展示商品',
|
||||||
|
'verbose_name_plural': '主页分类展示商品',
|
||||||
|
'db_table': 'df_index_type_goods',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IndexGoodsBanner',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('image', models.ImageField(upload_to='banner', verbose_name='图片')),
|
||||||
|
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
|
||||||
|
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '首页轮播图片',
|
||||||
|
'verbose_name_plural': '首页轮播图片',
|
||||||
|
'db_table': 'df_index_banner',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='goodssku',
|
||||||
|
name='goods_spu',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSPU', verbose_name='商品SPU'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='goodssku',
|
||||||
|
name='type',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsType', verbose_name='商品种类'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GoodsImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('image', models.ImageField(upload_to='goods', verbose_name='图片路径')),
|
||||||
|
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商品图片',
|
||||||
|
'verbose_name_plural': '商品图片',
|
||||||
|
'db_table': 'df_goods_image',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,125 @@
|
|||||||
|
from django.db import models
|
||||||
|
from tinymce.models import HTMLField
|
||||||
|
from db.base_model import BaseModel
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
class GoodsType(BaseModel):
|
||||||
|
"""商品类型模型类"""
|
||||||
|
name = models.CharField(max_length=20, verbose_name='种类名称')
|
||||||
|
logo = models.CharField(max_length=20, verbose_name='标识')
|
||||||
|
image = models.ImageField(upload_to='type', verbose_name='商品类型图片')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_goods_type'
|
||||||
|
verbose_name = '商品种类'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class GoodsSPU(BaseModel):
|
||||||
|
"""商品SPU模型类"""
|
||||||
|
name = models.CharField(max_length=20, verbose_name='商品SPU名称')
|
||||||
|
detail = HTMLField(blank=True, verbose_name='商品详情')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_goods_spu'
|
||||||
|
verbose_name = '商品SPU'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class GoodsSKU(BaseModel):
|
||||||
|
"""商品SKU模型类"""
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
(0, '下线'),
|
||||||
|
(1, '上线'),
|
||||||
|
)
|
||||||
|
|
||||||
|
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品种类')
|
||||||
|
goods_spu = models.ForeignKey('GoodsSPU', on_delete=models.CASCADE, verbose_name='商品SPU')
|
||||||
|
name = models.CharField(max_length=20, verbose_name='商品名称')
|
||||||
|
desc = models.CharField(max_length=256, verbose_name='商品简介')
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
|
||||||
|
unite = models.CharField(max_length=20, verbose_name='商品单位')
|
||||||
|
image = models.ImageField(upload_to='goods', verbose_name='商品图片')
|
||||||
|
stock = models.IntegerField(default=1, verbose_name='商品库存')
|
||||||
|
sales = models.IntegerField(default=0, verbose_name='商品销量')
|
||||||
|
status = models.SmallIntegerField(default=1, choices=STATUS_CHOICES, verbose_name='商品状态')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_goods_sku'
|
||||||
|
verbose_name = '商品'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class GoodsImage(BaseModel):
|
||||||
|
"""商品图片模型类"""
|
||||||
|
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
|
||||||
|
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_goods_image'
|
||||||
|
verbose_name = '商品图片'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.sku.name
|
||||||
|
|
||||||
|
|
||||||
|
class IndexGoodsBanner(BaseModel):
|
||||||
|
"""首页轮播商品展示模型类"""
|
||||||
|
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
|
||||||
|
image = models.ImageField(upload_to='banner', verbose_name='图片')
|
||||||
|
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_index_banner'
|
||||||
|
verbose_name = '首页轮播图片'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.sku.name
|
||||||
|
|
||||||
|
|
||||||
|
class IndexTypeGoodsBanner(BaseModel):
|
||||||
|
"""首页分类商品展示模型类"""
|
||||||
|
DISPLAY_TYPE_CHOICES = (
|
||||||
|
(0, '标题'),
|
||||||
|
(1, '图片'),
|
||||||
|
)
|
||||||
|
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品类型')
|
||||||
|
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
|
||||||
|
display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='商品显示方式')
|
||||||
|
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_index_type_goods'
|
||||||
|
verbose_name = '主页分类展示商品'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.sku.name
|
||||||
|
|
||||||
|
|
||||||
|
class IndexPromotionBanner(BaseModel):
|
||||||
|
"""首页促销活动模型类"""
|
||||||
|
url = models.CharField(max_length=256, verbose_name='活动链接')
|
||||||
|
name = models.CharField(max_length=20, verbose_name='活动名称')
|
||||||
|
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
|
||||||
|
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_index_promotion'
|
||||||
|
verbose_name = '主页促销活动'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
@ -0,0 +1,19 @@
|
|||||||
|
# 定义索引类
|
||||||
|
from haystack import indexes
|
||||||
|
# 导入模型类
|
||||||
|
from apps.goods.models import GoodsSKU
|
||||||
|
# 指定对于某个类的某些数据建立索引
|
||||||
|
# 索引类名格式:模型类名+Index
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
|
||||||
|
# 索引字段 指定根据表中哪些字段建立索引文件
|
||||||
|
text = indexes.CharField(document=True, use_template=True)
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
return GoodsSKU
|
||||||
|
|
||||||
|
# 建立索引的数据
|
||||||
|
def index_queryset(self, using=None):
|
||||||
|
return self.get_model().objects.all()
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
from apps.goods.views import IndexView, DetailView, ListView
|
||||||
|
|
||||||
|
app_name = 'goods'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('index/', IndexView.as_view(), name='index'), # Index view
|
||||||
|
re_path(r'^goods/(?P<goods_id>\d+)$', DetailView.as_view(), name='detail'), # Detail view
|
||||||
|
re_path(r'^goods/list/(?P<type_id>\d+)/(?P<page>\d+)$', ListView.as_view(), name='list'), # List view
|
||||||
|
]
|
@ -0,0 +1,197 @@
|
|||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.shortcuts import render, redirect, reverse
|
||||||
|
from django.views.generic.base import View
|
||||||
|
from django_redis import get_redis_connection
|
||||||
|
from django.core.cache import cache
|
||||||
|
from apps.goods.models import GoodsType, IndexGoodsBanner, IndexTypeGoodsBanner, IndexPromotionBanner, GoodsSKU
|
||||||
|
from apps.order.models import OrderGoods
|
||||||
|
|
||||||
|
class IndexView(View):
|
||||||
|
"""首页"""
|
||||||
|
def get(self, request):
|
||||||
|
"""首页"""
|
||||||
|
# 获取用户信息
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# 尝试获取缓存数据
|
||||||
|
context = cache.get('index_page_data')
|
||||||
|
if not context:
|
||||||
|
# 获取商品种类信息
|
||||||
|
types = GoodsType.objects.all()
|
||||||
|
|
||||||
|
# 获取首页轮播商品信息
|
||||||
|
goods_banners = IndexGoodsBanner.objects.all().order_by('index')
|
||||||
|
|
||||||
|
# 获取首页促销商品信息
|
||||||
|
promotion_banners = IndexPromotionBanner.objects.all().order_by('index')
|
||||||
|
|
||||||
|
# 获取分类商品展示信息
|
||||||
|
for type in types:
|
||||||
|
image_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1)
|
||||||
|
font_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0)
|
||||||
|
type.image_goods_banners = image_goods_banners
|
||||||
|
type.font_goods_banners = font_goods_banners
|
||||||
|
|
||||||
|
# 组织上下文
|
||||||
|
context = {
|
||||||
|
'types': types,
|
||||||
|
'goods_banners': goods_banners,
|
||||||
|
'promotion_banners': promotion_banners,
|
||||||
|
}
|
||||||
|
# 设置缓存
|
||||||
|
cache.set('index_page_data', context, 3600)
|
||||||
|
|
||||||
|
# 获取购物车中商品数量
|
||||||
|
cart_count = self._get_cart_count(user)
|
||||||
|
|
||||||
|
context.update(user=user, cart_count=cart_count)
|
||||||
|
|
||||||
|
return render(request, 'index.html', context)
|
||||||
|
|
||||||
|
def _get_cart_count(self, user):
|
||||||
|
"""获取用户购物车商品数量"""
|
||||||
|
if user.is_authenticated:
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
return conn.hlen(cart_key)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class DetailView(View):
|
||||||
|
"""详情页面"""
|
||||||
|
def get(self, request, goods_id):
|
||||||
|
"""显示详情页面"""
|
||||||
|
# 获取商品SKU信息
|
||||||
|
try:
|
||||||
|
sku = GoodsSKU.objects.get(id=goods_id)
|
||||||
|
except GoodsSKU.DoesNotExist:
|
||||||
|
return redirect(reverse('goods:index'))
|
||||||
|
|
||||||
|
# 获取商品的分类信息
|
||||||
|
types = GoodsType.objects.all()
|
||||||
|
|
||||||
|
# 获取商品的评论信息
|
||||||
|
sku_orders = OrderGoods.objects.filter(sku=sku)
|
||||||
|
|
||||||
|
# 获取新商品信息
|
||||||
|
new_skus = GoodsSKU.objects.filter(type=sku.type).order_by('-create_time')[:2]
|
||||||
|
|
||||||
|
# 获取同一个SPU的其他商品
|
||||||
|
same_spu_skus = GoodsSKU.objects.filter(goods_spu=sku.goods_spu).exclude(id=goods_id)
|
||||||
|
|
||||||
|
# 获取用户购物车中的商品数目
|
||||||
|
user = request.user
|
||||||
|
cart_count = self._get_cart_count(user)
|
||||||
|
|
||||||
|
# 添加用户的历史浏览记录
|
||||||
|
if user.is_authenticated:
|
||||||
|
self._add_user_history(user, goods_id)
|
||||||
|
|
||||||
|
# 组织上下文
|
||||||
|
context = {
|
||||||
|
'sku': sku,
|
||||||
|
'types': types,
|
||||||
|
'sku_orders': sku_orders,
|
||||||
|
'new_skus': new_skus,
|
||||||
|
'same_spu_skus': same_spu_skus,
|
||||||
|
'cart_count': cart_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'detail.html', context)
|
||||||
|
|
||||||
|
def _get_cart_count(self, user):
|
||||||
|
"""获取用户购物车商品数量"""
|
||||||
|
if user.is_authenticated:
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
return conn.hlen(cart_key)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _add_user_history(self, user, goods_id):
|
||||||
|
"""添加用户浏览历史记录"""
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
history_key = f'history_{user.id}'
|
||||||
|
conn.lrem(history_key, 0, goods_id)
|
||||||
|
conn.lpush(history_key, goods_id)
|
||||||
|
conn.ltrim(history_key, 0, 4)
|
||||||
|
|
||||||
|
|
||||||
|
class ListView(View):
|
||||||
|
"""列表页"""
|
||||||
|
def get(self, request, type_id, page):
|
||||||
|
"""显示列表页"""
|
||||||
|
# 获取种类信息
|
||||||
|
try:
|
||||||
|
type = GoodsType.objects.get(id=type_id)
|
||||||
|
except GoodsType.DoesNotExist:
|
||||||
|
return redirect(reverse('goods:index'))
|
||||||
|
|
||||||
|
# 获取排序方式
|
||||||
|
sort = request.GET.get('sort', 'default')
|
||||||
|
skus = self._get_skus_by_sort(type, sort)
|
||||||
|
|
||||||
|
# 对商品进行分页
|
||||||
|
paginator = Paginator(skus, 1)
|
||||||
|
page = self._get_valid_page(paginator, page)
|
||||||
|
skus_page = paginator.page(page)
|
||||||
|
pages = self._get_page_range(paginator, page)
|
||||||
|
|
||||||
|
# 获取商品的分类信息
|
||||||
|
types = GoodsType.objects.all()
|
||||||
|
|
||||||
|
# 获取新商品信息
|
||||||
|
new_skus = GoodsSKU.objects.filter(type=type).order_by('-create_time')[:2]
|
||||||
|
|
||||||
|
# 获取用户购物车中的商品数目
|
||||||
|
user = request.user
|
||||||
|
cart_count = self._get_cart_count(user)
|
||||||
|
|
||||||
|
# 组织上下文
|
||||||
|
context = {
|
||||||
|
'type': type,
|
||||||
|
'skus_page': skus_page,
|
||||||
|
'types': types,
|
||||||
|
'new_skus': new_skus,
|
||||||
|
'cart_count': cart_count,
|
||||||
|
'sort': sort,
|
||||||
|
'pages': pages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'list.html', context)
|
||||||
|
|
||||||
|
def _get_skus_by_sort(self, type, sort):
|
||||||
|
"""获取排序后的商品SKU"""
|
||||||
|
if sort == 'price':
|
||||||
|
return GoodsSKU.objects.filter(type=type).order_by('price')
|
||||||
|
elif sort == 'hot':
|
||||||
|
return GoodsSKU.objects.filter(type=type).order_by('-sales')
|
||||||
|
return GoodsSKU.objects.filter(type=type).order_by('-id')
|
||||||
|
|
||||||
|
def _get_valid_page(self, paginator, page):
|
||||||
|
"""获取有效的分页页码"""
|
||||||
|
try:
|
||||||
|
page = int(page)
|
||||||
|
except ValueError:
|
||||||
|
page = 1
|
||||||
|
if page > paginator.num_pages:
|
||||||
|
page = 1
|
||||||
|
return page
|
||||||
|
|
||||||
|
def _get_page_range(self, paginator, page):
|
||||||
|
"""获取分页页码范围"""
|
||||||
|
num_pages = paginator.num_pages
|
||||||
|
if num_pages < 5:
|
||||||
|
return range(1, num_pages + 1)
|
||||||
|
if page <= 3:
|
||||||
|
return range(1, 6)
|
||||||
|
if num_pages - page <= 2:
|
||||||
|
return range(num_pages - 4, num_pages + 1)
|
||||||
|
return range(page - 2, page + 3)
|
||||||
|
|
||||||
|
def _get_cart_count(self, user):
|
||||||
|
"""获取用户购物车商品数量"""
|
||||||
|
if user.is_authenticated:
|
||||||
|
conn = get_redis_connection('default')
|
||||||
|
cart_key = f'cart_{user.id}'
|
||||||
|
return conn.hlen(cart_key)
|
||||||
|
return 0
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjvgjL5EnRL1KrPqLFTZ0D4TyTFbSmmg/UWgkm1QyuqOYHZZ679beAsy6d2oY6WIcp5AqNrFIRiPz6jejRMNE9zhMEXQOG8zi/ttmJlyBlyW77FN5zrG5GPtGeZcr9cPEjc7Mh1srQLPHRYwDyyEHWp3VKe+tYCZMnPvJSch3iVCwY1jP9ApdozBCrLCs83m5p4mgoJLvrV0fp1sJKODDqazm8JixjOTZc5iZwI/ue7AGrFgxzSaU+pzhJiqBCoriq6w3hhzkGryGp4cXWNmjytn9rEK1wXOjaJPbWwtDtKtZiH4ou35pqCjTIWesqod+vEit5BUQuFoSzrM19LUx1QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAr3dngISSMTXZmWPosXgeJSTz/lSnNT/zXRXeCLSWfTSq6yML
|
||||||
|
J9uuFxoiJJbHZdS1wFEnbqO8ZLX7Bqhm70/XITfrM8GDTjh8ITYvAnx60F/+7Fmk
|
||||||
|
f8FTEt9KUW4DQQyf45EiIKW82CTClns5/d6hqt0Lxac/4jP1nhhL4Jde5DLggYdv
|
||||||
|
sf2IlBOtqnhAcHOGFDq1ZBkp1uW457zjX0GujwbHKtI7YYf43aGtHwUtVBctyEXC
|
||||||
|
NMJMpioSQQuFo4+SGp211lVIPTpzv++zCmL67R+yrXTGFqbqJpoJnrE93Fuy8Qlg
|
||||||
|
HgHeTr75jic83+GwqOpN3KLy0Hpg595EoKx/PwIDAQABAoIBAA0rgipESRDGgPGh
|
||||||
|
bRq88E5LasDhK7e0eBi5hnPS0iTNqjKB69lvBK8ZOAzVAFxlTcsEjFgilAZfHltO
|
||||||
|
koNN09DbeJzm3msllDON9JNUMoenXOPyioVIRmr5NYPNJRNh1jJnd09KAVWb1Lsk
|
||||||
|
vqKObkX712Fbf1EEI2BdZHyT//xSsV/xTZ5kXHJIIYzGuXtP5/h6m3s7qkAOOVzG
|
||||||
|
LcFrCtGzifxZsprh8ztL0+PFvZywikGIg+GkBMaVA1Xz28WT9pggsa8KuY8dBI+K
|
||||||
|
XRgsbKuVwccA3tIkgkgvdKKCH5b6Vbco8dxY4f1ZMt0Id3ouEVY2cq2vFmrGbFOP
|
||||||
|
PIfucrkCgYEA43ZFFJDOon2yv2SydGlqXzKIEtJsRet0tpXu9pZFswt32vmIM/lX
|
||||||
|
RHYFiq5ulFPZiYw3HFr6CwDM44eIn0/VJDh3/qMUptFhlQtg2WurIvNmZ7HqDK3S
|
||||||
|
sXVr0jsFn1mD4t6PwMLNboCm6lfnQxvJXfHKaV/RW7RuVIcop6S/TFMCgYEAxXsd
|
||||||
|
2DR+A0AI/JbXbRlVsUvV7B0/UH8Cz+PPJXsjazY0H6s+HQbXPJa0sG8mehCgXsDU
|
||||||
|
wRz5n6gUy3ryBqzW/e8XTztZlHiRU+CXJNiqyfmzmfHGr+KKZvluFSWYdlQcu9qL
|
||||||
|
43cqUqYddfwnEo6Q+/Hkdwi5BUq88APjJW6uw+UCgYA74zzG8GVnRN8WI0YU/lhC
|
||||||
|
XkSTaBGXyyl8lTdId0I8pM1WuxJQVNrULJrC67Azn2wMGf28mntxADHxyhJ/l35P
|
||||||
|
vgph4cAjN8eQfWFvfTieyCTzMlWkJvPtQzQzMtUFIoVl6yFAKEn8SSUpWCGMerlm
|
||||||
|
4a1gVxkBIx1VZgyfLvIq/wKBgD+nyN35Rak8iekJolU7dmDZBhK+9rq2xixGzW3S
|
||||||
|
fH9BkJmotDPdEaIpHgNFQMzV8Su50pqRAXHSVymj7sHyErb1y7ixc9Wk64ty+KVa
|
||||||
|
5eqG/7qesaHeTyiUPES6wqNZx41SDAd9UPolK5fteJbFt7xOo4svF5y6E572UdCu
|
||||||
|
Fc11AoGAAitTUwM3rDHjgutUL6/ffE56rgrkh50Up9vyR+gH2WO7AfIYgB3RRS08
|
||||||
|
TQGnuS/CGCK8Q8tYS75W4wIrfdqVXAQbDf9skhIVUsVqKe9cC15nrmVZGTwToprt
|
||||||
|
lHvbvCgxjoVti9tBv7mSDet8TqxqB7n+kkzk/cgzp5ht4i6xdMk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OrderConfig(AppConfig):
|
||||||
|
name = 'apps.order'
|
@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 2.2.3 on 2019-07-26 10:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrderGoods',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('count', models.IntegerField(default=1, verbose_name='商品数目')),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品价格')),
|
||||||
|
('comment', models.CharField(max_length=256, verbose_name='评论')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '订单商品',
|
||||||
|
'verbose_name_plural': '订单商品',
|
||||||
|
'db_table': 'df_order_goods',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrderInfo',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('order_id', models.CharField(max_length=20, primary_key=True, serialize=False, verbose_name='订单编号')),
|
||||||
|
('pay_method', models.SmallIntegerField(choices=[(1, '货到付款'), (2, '微信支付'), (3, '支付宝'), (4, '银联支付')], default=3, verbose_name='支付方式')),
|
||||||
|
('total_count', models.IntegerField(default=1, verbose_name='商品数量')),
|
||||||
|
('total_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品总价')),
|
||||||
|
('transit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='运费')),
|
||||||
|
('order_status', models.SmallIntegerField(choices=[(1, '待支付'), (2, '待发货'), (3, '待收货'), (4, '待评价'), (5, '已完成')], default=1, verbose_name='订单状态')),
|
||||||
|
('trade_no', models.CharField(max_length=20, verbose_name='支付编号')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '订单信息',
|
||||||
|
'verbose_name_plural': '订单信息',
|
||||||
|
'db_table': 'df_order_info',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,40 @@
|
|||||||
|
# Generated by Django 2.2.3 on 2019-07-26 10:54
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('goods', '0001_initial'),
|
||||||
|
('order', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('user', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orderinfo',
|
||||||
|
name='address',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.Address', verbose_name='地址'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orderinfo',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordergoods',
|
||||||
|
name='order',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.OrderInfo', verbose_name='订单'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordergoods',
|
||||||
|
name='sku',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品SKU'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,65 @@
|
|||||||
|
from django.db import models
|
||||||
|
from db.base_model import BaseModel
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class OrderInfo(BaseModel):
|
||||||
|
"""订单信息模型类"""
|
||||||
|
|
||||||
|
PAY_METHOD = {
|
||||||
|
'1': '货到付款',
|
||||||
|
'2': '微信支付',
|
||||||
|
'3': '支付宝',
|
||||||
|
'4': '银联支付',
|
||||||
|
}
|
||||||
|
|
||||||
|
PAY_METHOD_CHOICES = (
|
||||||
|
(1, '货到付款'),
|
||||||
|
(2, '微信支付'),
|
||||||
|
(3, '支付宝'),
|
||||||
|
(4, '银联支付'),
|
||||||
|
)
|
||||||
|
ORDER_STATUS = {
|
||||||
|
'1': '待支付',
|
||||||
|
'2': '待发货',
|
||||||
|
'3': '待收货',
|
||||||
|
'4': '待评价',
|
||||||
|
'5': '已完成',
|
||||||
|
}
|
||||||
|
|
||||||
|
ORDER_STATUS_CHOICES = (
|
||||||
|
(1, '待支付'),
|
||||||
|
(2, '待发货'),
|
||||||
|
(3, '待收货'),
|
||||||
|
(4, '待评价'),
|
||||||
|
(5, '已完成'),
|
||||||
|
)
|
||||||
|
|
||||||
|
order_id = models.CharField(max_length=20, primary_key=True, verbose_name='订单编号')
|
||||||
|
user = models.ForeignKey('user.User', on_delete=models.CASCADE, verbose_name='用户')
|
||||||
|
address = models.ForeignKey('user.Address', on_delete=models.CASCADE, verbose_name='地址')
|
||||||
|
pay_method = models.SmallIntegerField(default=3, choices=PAY_METHOD_CHOICES, verbose_name='支付方式')
|
||||||
|
total_count = models.IntegerField(default=1, verbose_name='商品数量')
|
||||||
|
total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品总价')
|
||||||
|
transit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='运费')
|
||||||
|
order_status = models.SmallIntegerField(default=1, choices=ORDER_STATUS_CHOICES, verbose_name='订单状态')
|
||||||
|
trade_no = models.CharField(max_length=20, default='', verbose_name='支付编号')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_order_info'
|
||||||
|
verbose_name = '订单信息'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
class OrderGoods(BaseModel):
|
||||||
|
"""订单商品模型类"""
|
||||||
|
order = models.ForeignKey('OrderInfo', on_delete=models.CASCADE, verbose_name='订单')
|
||||||
|
sku = models.ForeignKey('goods.GoodsSKU', on_delete=models.CASCADE, verbose_name='商品SKU')
|
||||||
|
count = models.IntegerField(default=1, verbose_name='商品数目')
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
|
||||||
|
comment = models.CharField(max_length=256, default='', verbose_name='评论')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_order_goods'
|
||||||
|
verbose_name = '订单商品'
|
||||||
|
verbose_name_plural = verbose_name
|
@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
from apps.order.views import OrderPlaceView, OrderCommitView, OrderPayView, CheckPayView, CommentView
|
||||||
|
|
||||||
|
app_name = 'order'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('place', OrderPlaceView.as_view(), name='place'), # 提交订单页面显示
|
||||||
|
path('commit', OrderCommitView.as_view(), name='commit'), # 订单创建
|
||||||
|
path('pay', OrderPayView.as_view(), name='pay'), # 订单支付
|
||||||
|
path('check', CheckPayView.as_view(), name='check'), # 查看订单支付状态
|
||||||
|
re_path(r'^comment/(?P<order_id>.+)$', CommentView.as_view(), name='comment'), # 订单评论
|
||||||
|
]
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UserConfig(AppConfig):
|
||||||
|
name = 'apps.user'
|
@ -0,0 +1,69 @@
|
|||||||
|
# Generated by Django 2.2.3 on 2019-07-26 10:54
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0011_update_proxy_permissions'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '用户',
|
||||||
|
'verbose_name_plural': '用户',
|
||||||
|
'db_table': 'df_user',
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Address',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
|
||||||
|
('receiver', models.CharField(max_length=20, verbose_name='收件人')),
|
||||||
|
('addr', models.CharField(max_length=256, verbose_name='收货地址')),
|
||||||
|
('zip_code', models.CharField(max_length=6, null=True, verbose_name='邮政编码')),
|
||||||
|
('phone', models.CharField(max_length=11, verbose_name='联系电话')),
|
||||||
|
('is_default', models.BooleanField(default=False, verbose_name='是否默认')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='所属用户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '地址',
|
||||||
|
'verbose_name_plural': '地址',
|
||||||
|
'db_table': 'df_address',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,49 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from itsdangerous import URLSafeTimedSerializer as Serializer
|
||||||
|
from db.base_model import BaseModel
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# AddressManager Class
|
||||||
|
class AddressManager(models.Manager):
|
||||||
|
"""地址模型管理类"""
|
||||||
|
def get_default_address(self, user):
|
||||||
|
"""获取用户的默认地址"""
|
||||||
|
try:
|
||||||
|
address = self.get(user=user, is_default=True)
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
address = None
|
||||||
|
return address
|
||||||
|
|
||||||
|
# User Model
|
||||||
|
class User(AbstractUser, BaseModel):
|
||||||
|
"""用户模型类"""
|
||||||
|
def generate_active_token(self):
|
||||||
|
"""生成用户签名字符串"""
|
||||||
|
serializer = Serializer(settings.SECRET_KEY, 3600)
|
||||||
|
info = {'confirm': self.id}
|
||||||
|
token = serializer.dumps(info)
|
||||||
|
return token.decode()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_user'
|
||||||
|
verbose_name = '用户'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# Address Model
|
||||||
|
class Address(BaseModel):
|
||||||
|
"""地址模型类"""
|
||||||
|
user = models.ForeignKey('User', on_delete=models.CASCADE, verbose_name='所属用户')
|
||||||
|
receiver = models.CharField(max_length=20, verbose_name='收件人')
|
||||||
|
addr = models.CharField(max_length=256, verbose_name='收货地址')
|
||||||
|
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码')
|
||||||
|
phone = models.CharField(max_length=11, verbose_name='联系电话')
|
||||||
|
is_default = models.BooleanField(default=False, verbose_name='是否默认')
|
||||||
|
|
||||||
|
# 自定义一个模型管理器对象
|
||||||
|
objects = AddressManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'df_address'
|
||||||
|
verbose_name = '地址'
|
||||||
|
verbose_name_plural = verbose_name
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
@ -0,0 +1,19 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
from apps.user.views import (
|
||||||
|
RegisterView, ActiveView, LoginView, UserInfoView,
|
||||||
|
UserOrderView, UserSiteView, LogoutView
|
||||||
|
)
|
||||||
|
from apps.user import views
|
||||||
|
|
||||||
|
app_name = 'user'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('index/', views.index, name='index'),
|
||||||
|
path('register/', RegisterView.as_view(), name='register'), # Register
|
||||||
|
re_path(r'active/(?P<token>.*)/$', ActiveView.as_view(), name='active'), # User Activation
|
||||||
|
path('login/', LoginView.as_view(), name='login'), # Login
|
||||||
|
path('logout/', LogoutView.as_view(), name='logout'), # Logout
|
||||||
|
path('', UserInfoView.as_view(), name='user'), # User Info
|
||||||
|
re_path(r'^order/(?P<page>\d+)/$', UserOrderView.as_view(), name='order'), # Orders
|
||||||
|
path('address/', UserSiteView.as_view(), name='address'), # Address
|
||||||
|
]
|
@ -0,0 +1,215 @@
|
|||||||
|
import re
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.shortcuts import render, redirect, reverse, HttpResponse
|
||||||
|
from django.views.generic import View
|
||||||
|
from itsdangerous import URLSafeTimedSerializer as Serializer, SignatureExpired
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from celery_tasks.tasks import send_register_active_email
|
||||||
|
from utils.mixin import LoginRequiredMixin
|
||||||
|
from django_redis import get_redis_connection
|
||||||
|
from apps.goods.models import GoodsSKU
|
||||||
|
from apps.order.models import OrderInfo, OrderGoods
|
||||||
|
from apps.user.models import User, Address, AddressManager
|
||||||
|
|
||||||
|
|
||||||
|
# Index View
|
||||||
|
def index(request):
|
||||||
|
return render(request, 'index.html')
|
||||||
|
|
||||||
|
|
||||||
|
# Function-based Register View (Commented out)
|
||||||
|
# def register(request):
|
||||||
|
# """显示注册页面"""
|
||||||
|
# if request.method == 'GET':
|
||||||
|
# return render(request, 'register.html')
|
||||||
|
# elif request.method == 'POST':
|
||||||
|
# # Handle registration logic
|
||||||
|
# pass
|
||||||
|
|
||||||
|
# Class-based Register View
|
||||||
|
class RegisterView(View):
|
||||||
|
"""注册"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return render(request, 'register.html')
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
# 接受数据
|
||||||
|
username = request.POST.get('user_name')
|
||||||
|
password = request.POST.get('pwd')
|
||||||
|
email = request.POST.get('email')
|
||||||
|
allow = request.POST.get('allow')
|
||||||
|
|
||||||
|
# 数据处理
|
||||||
|
if not all([username, password, email]):
|
||||||
|
return render(request, 'register.html', {'errmsg': '数据不完整'})
|
||||||
|
|
||||||
|
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
|
||||||
|
return render(request, 'register.html', {'errmsg': '邮箱格式不正确'})
|
||||||
|
|
||||||
|
if allow != 'on':
|
||||||
|
return render(request, 'register.html', {'errmsg': '请同意协议'})
|
||||||
|
|
||||||
|
# 校验用户名是否重复
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
if user:
|
||||||
|
return render(request, 'register.html', {'errmsg': '用户名已存在'})
|
||||||
|
|
||||||
|
# 进行用户注册
|
||||||
|
user = User.objects.create_user(username, email, password)
|
||||||
|
user.is_active = 0
|
||||||
|
user.is_superuser = False
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# 发送激活邮件,包含激活链接
|
||||||
|
serializer = Serializer(settings.SECRET_KEY, 3600)
|
||||||
|
info = {'confirm': user.id}
|
||||||
|
token = serializer.dumps(info).decode()
|
||||||
|
|
||||||
|
# 为处理者分配任务
|
||||||
|
send_register_active_email.delay(email, username, token)
|
||||||
|
|
||||||
|
return redirect(reverse('goods:index'))
|
||||||
|
|
||||||
|
|
||||||
|
# User Activation View
|
||||||
|
class ActiveView(View):
|
||||||
|
"""用户激活"""
|
||||||
|
|
||||||
|
def get(self, request, token):
|
||||||
|
try:
|
||||||
|
serializer = Serializer(settings.SECRET_KEY, 3600)
|
||||||
|
token = token.encode()
|
||||||
|
info = serializer.loads(token)
|
||||||
|
user_id = info['confirm']
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
user.is_active = 1
|
||||||
|
user.save()
|
||||||
|
return redirect(reverse('user:login'))
|
||||||
|
except SignatureExpired:
|
||||||
|
return HttpResponse('激活链接已过期')
|
||||||
|
|
||||||
|
|
||||||
|
# Login View
|
||||||
|
class LoginView(View):
|
||||||
|
"""登录"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if 'username' in request.COOKIES:
|
||||||
|
username = request.COOKIES.get('username')
|
||||||
|
checked = 'checked'
|
||||||
|
else:
|
||||||
|
username = ''
|
||||||
|
checked = ''
|
||||||
|
context = {'username': username, 'checked': checked}
|
||||||
|
return render(request, 'login.html', context)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
username = request.POST.get('username')
|
||||||
|
password = request.POST.get('pwd')
|
||||||
|
if not all([username, password]):
|
||||||
|
return render(request, 'login.html', {'errmsg': '数据不完整'})
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
if user:
|
||||||
|
if user.is_active:
|
||||||
|
login(request, user)
|
||||||
|
next_url = request.GET.get('next', reverse('goods:index'))
|
||||||
|
response = redirect(next_url)
|
||||||
|
remember = request.POST.get('remember')
|
||||||
|
if remember == 'on':
|
||||||
|
response.set_cookie('username', username, max_age=7 * 24 * 3600)
|
||||||
|
else:
|
||||||
|
response.delete_cookie('username')
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return render(request, 'login.html', {'errmsg': '账户未激活'})
|
||||||
|
else:
|
||||||
|
return render(request, 'login.html', {'errmsg': '用户名或密码错误'})
|
||||||
|
|
||||||
|
|
||||||
|
# Logout View
|
||||||
|
class LogoutView(View):
|
||||||
|
"""退出登录"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
logout(request)
|
||||||
|
return redirect(reverse('goods:index'))
|
||||||
|
|
||||||
|
|
||||||
|
# User Info View
|
||||||
|
class UserInfoView(LoginRequiredMixin, View):
|
||||||
|
"""用户中心——信息页"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
user = request.user
|
||||||
|
address = Address.objects.get_default_address(user)
|
||||||
|
con = get_redis_connection('default')
|
||||||
|
history_key = f'history_{user.id}'
|
||||||
|
sku_ids = con.lrange(history_key, 0, 4)
|
||||||
|
goods_list = [GoodsSKU.objects.get(id=i) for i in sku_ids]
|
||||||
|
context = {'page': 'user', 'user': user, 'address': address, 'goods_list': goods_list}
|
||||||
|
return render(request, 'user_center_info.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
# User Order View
|
||||||
|
class UserOrderView(LoginRequiredMixin, View):
|
||||||
|
"""用户中心——订单页"""
|
||||||
|
|
||||||
|
def get(self, request, page):
|
||||||
|
user = request.user
|
||||||
|
orders = OrderInfo.objects.filter(user=user)
|
||||||
|
for order in orders:
|
||||||
|
order_skus = OrderGoods.objects.filter(order_id=order.order_id)
|
||||||
|
for order_sku in order_skus:
|
||||||
|
order_sku.amount = order_sku.price * order_sku.count
|
||||||
|
order.order_skus = order_skus
|
||||||
|
order.status_name = OrderInfo.ORDER_STATUS[str(order.order_status)]
|
||||||
|
paginator = Paginator(orders, 1)
|
||||||
|
try:
|
||||||
|
page = int(page)
|
||||||
|
except Exception:
|
||||||
|
page = 1
|
||||||
|
order_page = paginator.page(page)
|
||||||
|
num_pages = paginator.num_pages
|
||||||
|
if num_pages < 5:
|
||||||
|
pages = range(1, num_pages + 1)
|
||||||
|
elif page <= 3:
|
||||||
|
pages = range(1, 6)
|
||||||
|
elif num_pages - page <= 2:
|
||||||
|
pages = range(num_pages - 4, num_pages + 1)
|
||||||
|
else:
|
||||||
|
pages = range(page - 2, page + 3)
|
||||||
|
context = {'order_page': order_page, 'pages': pages, 'page': 'order'}
|
||||||
|
return render(request, 'user_center_order.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
# User Address View
|
||||||
|
class UserSiteView(LoginRequiredMixin, View):
|
||||||
|
"""用户中心——地址页"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
user = request.user
|
||||||
|
address = Address.objects.get_default_address(user)
|
||||||
|
return render(request, 'user_center_site.html', {'address': address, 'user': user})
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
receiver = request.POST.get('receiver')
|
||||||
|
addr = request.POST.get('addr')
|
||||||
|
zip_code = request.POST.get('zip_code')
|
||||||
|
phone = request.POST.get('phone')
|
||||||
|
if not all([receiver, addr, phone]):
|
||||||
|
return render(request, 'user_center_site.html', {'errmsg': '数据不完整'})
|
||||||
|
if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
|
||||||
|
return render(request, 'user_center_site.html', {'errmsg': '手机格式错误'})
|
||||||
|
user = request.user
|
||||||
|
address = Address.objects.get_default_address(user)
|
||||||
|
is_default = not bool(address)
|
||||||
|
Address.objects.create(user=user, receiver=receiver, addr=addr, zip_code=zip_code, phone=phone,
|
||||||
|
is_default=is_default)
|
||||||
|
return redirect(reverse('user:address'))
|
@ -0,0 +1,73 @@
|
|||||||
|
# 使用celery
|
||||||
|
|
||||||
|
# # 在任务一段加这几句代码
|
||||||
|
# import os
|
||||||
|
# import django
|
||||||
|
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
|
||||||
|
# django.setup()
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.template import loader
|
||||||
|
|
||||||
|
from dailyfresh import settings
|
||||||
|
from celery import Celery
|
||||||
|
from django_redis import get_redis_connection
|
||||||
|
|
||||||
|
from apps.goods.models import GoodsType, IndexGoodsBanner, IndexTypeGoodsBanner, IndexPromotionBanner
|
||||||
|
# 创建一个Celery类的实例对象
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
app = Celery('celery_tasks.tasks', broker='redis://192.168.209.130:6379/8')
|
||||||
|
|
||||||
|
# 定义任务函数
|
||||||
|
@app.task
|
||||||
|
def send_register_active_email(to_email, username, token):
|
||||||
|
"""发送激活邮件"""
|
||||||
|
# 发送邮件
|
||||||
|
subject = '天天生鲜欢迎信息'
|
||||||
|
message = ''
|
||||||
|
sender = settings.EMAIL_FROM
|
||||||
|
receiver = [to_email]
|
||||||
|
html_message = '<h1>{0}, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/{1}">http://127.0.0.1/user/active/{2}</a>'.format(
|
||||||
|
username, token, token)
|
||||||
|
send_mail(subject, message, sender, receiver, html_message=html_message)
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def generate_static_index_html():
|
||||||
|
"""产生首页静态页面"""
|
||||||
|
|
||||||
|
# 获取商品种类信息
|
||||||
|
types = GoodsType.objects.all()
|
||||||
|
|
||||||
|
# 获取首页轮播商品信息
|
||||||
|
goods_banners = IndexGoodsBanner.objects.all().order_by('index')
|
||||||
|
|
||||||
|
# 获取首页促销商品信息
|
||||||
|
promotion_banners = IndexPromotionBanner.objects.all().order_by('index')
|
||||||
|
|
||||||
|
# 获取分类商品展示信息
|
||||||
|
for type in types:
|
||||||
|
image_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1)
|
||||||
|
font_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0)
|
||||||
|
type.image_goods_banners = image_goods_banners
|
||||||
|
type.font_goods_banners = font_goods_banners
|
||||||
|
|
||||||
|
# 组织上下文
|
||||||
|
context = {
|
||||||
|
'types': types,
|
||||||
|
'goods_banners': goods_banners,
|
||||||
|
'promotion_goods': promotion_banners,
|
||||||
|
}
|
||||||
|
|
||||||
|
# s使用模板
|
||||||
|
# 1. 加载模板文件,返回模板对象
|
||||||
|
temp = loader.get_template('static_index.html')
|
||||||
|
# 2. 渲染模板
|
||||||
|
static_index_html = temp.render(context)
|
||||||
|
|
||||||
|
# 生成对应静态文件
|
||||||
|
save_path = os.path.join(settings.BASE_DIR, 'static/index.html')
|
||||||
|
with open(save_path, 'w') as f:
|
||||||
|
f.write(static_index_html)
|
@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
Django settings for dailyfresh project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 2.2.3.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/2.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/2.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'qi1pgrrshxdstv3v71b8ax%@2p)zh#&_hy5mo633xh3$+g-bs('
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'tinymce', # 富文本编辑器
|
||||||
|
'haystack', # 全文检索框架
|
||||||
|
# sys.path.insert(0, os.path.join(BASE_DIR, 'apps')
|
||||||
|
'apps.goods', # 商品模块
|
||||||
|
'apps.cart', # 购物车模块
|
||||||
|
'apps.order', # 订单模块
|
||||||
|
'apps.user', # 用户模块
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'dailyfresh.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'dailyfresh.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': 'dailyfresh',
|
||||||
|
'USER': 'root',
|
||||||
|
'PASSWORD': '1234567',
|
||||||
|
'HOST': '127.0.0.1',
|
||||||
|
'POST': '3306',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# django认证系统使用的模型类
|
||||||
|
AUTH_USER_MODEL='user.User'
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'zh-hans'
|
||||||
|
|
||||||
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||||
|
|
||||||
|
# 富文本编辑器配置
|
||||||
|
TINYMCE_DEFAULT_CONFIG = {
|
||||||
|
'theme': 'advanced',
|
||||||
|
'width': 600,
|
||||||
|
'height': 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 发送邮件配置
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
# EMAIL_USE_TLS = False
|
||||||
|
# smpt服务地址
|
||||||
|
EMAIL_HOST = 'smtp.163.com'
|
||||||
|
EMAIL_PORT = 25
|
||||||
|
# 邮箱
|
||||||
|
EMAIL_HOST_USER = 'a1691795341@163.com'
|
||||||
|
# 授权密码
|
||||||
|
EMAIL_HOST_PASSWORD = 'w809326582'
|
||||||
|
# DEFAULT_FROM_EMAIL = 'w1691795341@163.com'
|
||||||
|
EMAIL_FROM = '天天生鲜<a1691795341@163.com>'
|
||||||
|
|
||||||
|
# Django的缓存配置
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django_redis.cache.RedisCache',
|
||||||
|
'LOCATION': 'redis://192.168.209.130:6379/1',
|
||||||
|
'OPTIONS': {
|
||||||
|
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 配置SESSION的存储
|
||||||
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||||
|
SESSION_CACHE_ALIAS = "default"
|
||||||
|
|
||||||
|
# 配置登录url地址
|
||||||
|
LOGIN_URL = '/user/login'
|
||||||
|
|
||||||
|
# 设置django的默认文件存储类
|
||||||
|
DEFAULT_FILE_STORAGE = 'utils.fdfs.storage.FDFSStorage'
|
||||||
|
|
||||||
|
# 设置fdfs使用的client.conf的文件路径
|
||||||
|
FDFS_CLIENT_CONF = '/etc/fdfs/client.conf'
|
||||||
|
|
||||||
|
# 设置fdfs存储服务器上Nginx的ip和port
|
||||||
|
FDFS_URL = 'http://192.168.209.130:8888/'
|
||||||
|
|
||||||
|
# 全文检索框架配置
|
||||||
|
HAYSTACK_CONNECTIONS = {
|
||||||
|
'default': {
|
||||||
|
# 使用whoosh
|
||||||
|
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
|
||||||
|
# 索引文件路径
|
||||||
|
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 当添加、修改、删除数据时,自动生成索引
|
||||||
|
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
||||||
|
# 指定搜索每页显示条数 默认20
|
||||||
|
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 1
|
@ -0,0 +1,37 @@
|
|||||||
|
"""dailyfresh URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from apps.user import views
|
||||||
|
from apps.user.views import RegisterView, ActiveView, LoginView, UserInfoView, UserOrderView, UserSiteView, LogoutView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.index, name='index'), # 根路径指向index视图
|
||||||
|
# path('index/', views.index, name='index'),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
# path('login/', views.login_view, name='login'),
|
||||||
|
# path('register/', views.register_view, name='register'),
|
||||||
|
path('login/', LoginView.as_view(), name='login'), # 登录
|
||||||
|
path('logout/', LogoutView.as_view(), name='logout'), # 退出登录
|
||||||
|
path('register/', RegisterView.as_view(), name='register'), # 注册
|
||||||
|
path('tinymce/', include('tinymce.urls')), # 富文本编辑器
|
||||||
|
path('search/', include('haystack.urls')), # 全文检索
|
||||||
|
path('user/', include(('apps.user.urls', 'user'), namespace='user')), # 用户模块
|
||||||
|
path('cart/', include(('apps.cart.urls', 'cart'), namespace='cart')), # 购物车模块
|
||||||
|
path('order/', include(('apps.order.urls', 'order'), namespace='order')), # 订单模块
|
||||||
|
path('goods/', include(('apps.goods.urls', 'goods'), namespace='goods')), # 商品模块
|
||||||
|
]
|
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for dailyfresh project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
@ -0,0 +1,11 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class BaseModel(models.Model):
|
||||||
|
'''模型抽象基类'''
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||||
|
is_delete = models.BooleanField(default=False, verbose_name='删除标记')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
# 说明是一个抽象基类
|
||||||
|
abstract = True
|
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
|
||||||
|
如果 安装有其他fdfs包, 要先卸载完再安装py3Fdfs
|
||||||
|
|
||||||
|
pip install py3Fdfs
|
||||||
|
测试
|
||||||
|
|
||||||
|
from fdfs_client.client import *
|
||||||
|
client_conf_obj = get_tracker_conf('/etc/fdfs/client.conf')
|
||||||
|
client = Fdfs_client(client_conf_obj)
|
||||||
|
ret = client.upload_by_filename('test')
|
||||||
|
ret
|
||||||
|
|
||||||
|
{'Group name': b'group1', 'Remote file_id': b'group1/M00/00/00/wKjRgl09sfWALPKYAAAAAAAAAAA765.txt', 'Status': 'Upload successed.', 'Local file name': 'test.txt', 'Uploaded size': '0B', 'Storage IP': b'192.168.209.130'}
|
||||||
|
|
||||||
|
|
||||||
|
关于storege.conf配置文件
|
||||||
|
|
||||||
|
tracker_server = 填写所在服务器的外网ip
|
||||||
|
|
||||||
|
安全组开放 配置文件里的23000端口
|
@ -0,0 +1,215 @@
|
|||||||
|
|
||||||
|
本文标题: Django中redis的使用方法(包括安装、配置、启动)
|
||||||
|
本文地址: http://www.cppcns.com/shujuku/redis/220910.html
|
||||||
|
|
||||||
|
|
||||||
|
./redis-4.0.8/src/redis-cli [-h] [-p]
|
||||||
|
127.0.0.1:6379> ping
|
||||||
|
PONG
|
||||||
|
127.0.0.1:6379> select 5
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
(empty list or set)
|
||||||
|
127.0.0.1:6379[5]> set name itchat
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> get name
|
||||||
|
"itchat"
|
||||||
|
127.0.0.1:6379[5]> set name itchat2
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> get name
|
||||||
|
"itchat2"
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
127.0.0.1:6379[5]> setex aa 3 ii
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
127.0.0.1:6379[5]> setex aa 3 ii
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> get aa
|
||||||
|
"ii"
|
||||||
|
127.0.0.1:6379[5]> get aa
|
||||||
|
(nil)
|
||||||
|
127.0.0.1:6379[5]> mset a1 java a2 python a3 c
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> get a1
|
||||||
|
"java"
|
||||||
|
127.0.0.1:6379[5]> get a2
|
||||||
|
"python"
|
||||||
|
127.0.0.1:6379[5]> get a3
|
||||||
|
"c"
|
||||||
|
127.0.0.1:6379[5]> append a1 haha
|
||||||
|
(integer) 8
|
||||||
|
127.0.0.1:6379[5]> get a1
|
||||||
|
"javahaha"
|
||||||
|
127.0.0.1:6379[5]> exists a
|
||||||
|
(integer) 0
|
||||||
|
127.0.0.1:6379[5]> exists a2
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> type name
|
||||||
|
string
|
||||||
|
127.0.0.1:6379[5]> mget a1 a2 a3
|
||||||
|
1) "javahaha"
|
||||||
|
2) "python"
|
||||||
|
3) "c"
|
||||||
|
127.0.0.1:6379[5]> del a1 a2 a3
|
||||||
|
(integer) 3
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
127.0.0.1:6379[5]> set a1 python
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> expire a1 3
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> get a1
|
||||||
|
(nil)
|
||||||
|
127.0.0.1:6379[5]> get a1
|
||||||
|
(nil)
|
||||||
|
127.0.0.1:6379[5]> expire a1 3
|
||||||
|
(integer) 0
|
||||||
|
127.0.0.1:6379[5]> get a1
|
||||||
|
(nil)
|
||||||
|
127.0.0.1:6379[5]> hset user name itthema
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
2) "user"
|
||||||
|
127.0.0.1:6379[5]> hmset u2 name itcast age 12
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> type u2
|
||||||
|
hash
|
||||||
|
127.0.0.1:6379[5]> hkeys user
|
||||||
|
1) "name"
|
||||||
|
127.0.0.1:6379[5]> hkeys u2
|
||||||
|
1) "name"
|
||||||
|
2) "age"
|
||||||
|
127.0.0.1:6379[5]> keys name
|
||||||
|
1) "name"
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
2) "user"
|
||||||
|
3) "u2"
|
||||||
|
127.0.0.1:6379[5]> hget u2 name
|
||||||
|
"itcast"
|
||||||
|
127.0.0.1:6379[5]> hget u2 age
|
||||||
|
"12"
|
||||||
|
127.0.0.1:6379[5]> hvals u2
|
||||||
|
1) "itcast"
|
||||||
|
2) "12"
|
||||||
|
127.0.0.1:6379[5]> hdel u2 name
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> hkeys u2
|
||||||
|
1) "age"
|
||||||
|
127.0.0.1:6379[5]> set a1
|
||||||
|
(error) ERR wrong number of arguments for 'set' command
|
||||||
|
127.0.0.1:6379[5]> lpush a1 a b c
|
||||||
|
(integer) 3
|
||||||
|
127.0.0.1:6379[5]> type a1
|
||||||
|
list
|
||||||
|
127.0.0.1:6379[5]> lrange a1
|
||||||
|
(error) ERR wrong number of arguments for 'lrange' command
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 3
|
||||||
|
1) "c"
|
||||||
|
2) "b"
|
||||||
|
3) "a"
|
||||||
|
127.0.0.1:6379[5]> rpush a1 0 1
|
||||||
|
(integer) 5
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 5
|
||||||
|
1) "c"
|
||||||
|
2) "b"
|
||||||
|
3) "a"
|
||||||
|
4) "0"
|
||||||
|
5) "1"
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 4
|
||||||
|
1) "c"
|
||||||
|
2) "b"
|
||||||
|
3) "a"
|
||||||
|
4) "0"
|
||||||
|
5) "1"
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 3
|
||||||
|
1) "c"
|
||||||
|
2) "b"
|
||||||
|
3) "a"
|
||||||
|
4) "0"
|
||||||
|
127.0.0.1:6379[5]> linsert a1 before b 3
|
||||||
|
(integer) 6
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 5
|
||||||
|
1) "c"
|
||||||
|
2) "3"
|
||||||
|
3) "b"
|
||||||
|
4) "a"
|
||||||
|
5) "0"
|
||||||
|
6) "1"
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 -1
|
||||||
|
1) "c"
|
||||||
|
2) "3"
|
||||||
|
3) "b"
|
||||||
|
4) "a"
|
||||||
|
5) "0"
|
||||||
|
6) "1"
|
||||||
|
127.0.0.1:6379[5]> lset a1 1 z
|
||||||
|
OK
|
||||||
|
127.0.0.1:6379[5]> lrange a1 0 -1
|
||||||
|
1) "c"
|
||||||
|
2) "z"
|
||||||
|
3) "b"
|
||||||
|
4) "a"
|
||||||
|
5) "0"
|
||||||
|
6) "1"
|
||||||
|
127.0.0.1:6379[5]> sadd a3 1 2 3 4
|
||||||
|
(integer) 4
|
||||||
|
127.0.0.1:6379[5]> smembers a2
|
||||||
|
(empty list or set)
|
||||||
|
127.0.0.1:6379[5]> smembers a3
|
||||||
|
1) "1"
|
||||||
|
2) "2"
|
||||||
|
3) "3"
|
||||||
|
4) "4"
|
||||||
|
127.0.0.1:6379[5]> sadd a2 zhangsan wanger lisi
|
||||||
|
(integer) 3
|
||||||
|
127.0.0.1:6379[5]> smembers a2
|
||||||
|
1) "wanger"
|
||||||
|
2) "zhangsan"
|
||||||
|
3) "lisi"
|
||||||
|
127.0.0.1:6379[5]> smembers a3
|
||||||
|
1) "1"
|
||||||
|
2) "2"
|
||||||
|
3) "3"
|
||||||
|
4) "4"
|
||||||
|
127.0.0.1:6379[5]> srem a3 3
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> smembers a3
|
||||||
|
1) "1"
|
||||||
|
2) "2"
|
||||||
|
3) "4"
|
||||||
|
127.0.0.1:6379[5]> zadd a4 4 lisi 5 wangwu 6 zhaoliu 3 zhangsan
|
||||||
|
(integer) 4
|
||||||
|
127.0.0.1:6379[5]> keys *
|
||||||
|
1) "name"
|
||||||
|
2) "a2"
|
||||||
|
3) "a1"
|
||||||
|
4) "user"
|
||||||
|
5) "a4"
|
||||||
|
6) "a3"
|
||||||
|
7) "u2"
|
||||||
|
127.0.0.1:6379[5]> type a4
|
||||||
|
zset
|
||||||
|
127.0.0.1:6379[5]> zrange a4 0 -1
|
||||||
|
1) "zhangsan"
|
||||||
|
2) "lisi"
|
||||||
|
3) "wangwu"
|
||||||
|
4) "zhaoliu"
|
||||||
|
127.0.0.1:6379[5]> zscore a4 zhangsan
|
||||||
|
"3"
|
||||||
|
127.0.0.1:6379[5]> zrem z4 zhangsan
|
||||||
|
(integer) 0
|
||||||
|
127.0.0.1:6379[5]> zrem a4 zhangsan
|
||||||
|
(integer) 1
|
||||||
|
127.0.0.1:6379[5]> zrange a4 0 -1
|
||||||
|
1) "lisi"
|
||||||
|
2) "wangwu"
|
||||||
|
3) "zhaoliu"
|
||||||
|
127.0.0.1:6379[5]> zremrangebyscore a4 5 6
|
||||||
|
(integer) 2
|
||||||
|
127.0.0.1:6379[5]> zrange a4 0 -1
|
||||||
|
1) "lisi"
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
from redis import StrictRedis
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
try:
|
||||||
|
sr = StrictRedis()
|
||||||
|
res = sr.set('name', 'itheima')
|
||||||
|
print(res) # True
|
||||||
|
|
||||||
|
res = sr.get('name')
|
||||||
|
print(res) # b'itheima'
|
||||||
|
|
||||||
|
res = sr.set('name', 'itheima2')
|
||||||
|
print(res) # True
|
||||||
|
|
||||||
|
res = sr.delete('name')
|
||||||
|
print(res) # 1
|
||||||
|
|
||||||
|
res = sr.delete('a1', 'a2')
|
||||||
|
print(res) # 0
|
||||||
|
|
||||||
|
res = sr.keys()
|
||||||
|
print(res)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
@ -0,0 +1,642 @@
|
|||||||
|
body{font-family:'Microsoft Yahei';font-size:12px;color:#666;}
|
||||||
|
html,body{height:100%}
|
||||||
|
/* 顶部样式 */
|
||||||
|
.header_con{
|
||||||
|
background-color:#f7f7f7;
|
||||||
|
height:29px;
|
||||||
|
border-bottom:1px solid #dddddd
|
||||||
|
}
|
||||||
|
|
||||||
|
.header{
|
||||||
|
width:1200px;
|
||||||
|
height:29px;
|
||||||
|
margin:0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome,.login_info,.login_btn,.user_link{
|
||||||
|
line-height:29px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login_info{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login_info em{color:#ff8800}
|
||||||
|
|
||||||
|
.login_btn a,.user_link a{
|
||||||
|
color:#666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login_btn a:hover,.user_link a:hover{
|
||||||
|
color:#ff8800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login_btn span,.user_link span{
|
||||||
|
color:#cecece;
|
||||||
|
margin:0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* logo、搜索框、购物车样式 */
|
||||||
|
|
||||||
|
.search_bar{width:1200px;height:115px;margin:0 auto;}
|
||||||
|
.logo{width:150px;height:59px;margin:29px 0 0 17px;}
|
||||||
|
|
||||||
|
.search_con{width:616px;height:38px;border:1px solid #37ab40;margin:34px 0 0 124px;background:url(../images/icons.png) 10px -338px no-repeat;}
|
||||||
|
.search_con .input_text{width:470px;height:34px;border:0px;margin:2px 0 0 36px;outline:none;font-size:12px;color:#737272;font-family:'Microsoft Yahei'}
|
||||||
|
|
||||||
|
.search_con .input_btn{
|
||||||
|
width:100px;height:38px;background-color:#37ab40;border:0px;font-size:14px;color:#fff;font-family:'Microsoft Yahei';outline:none;cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest_cart{
|
||||||
|
width:200px;height:40px;margin-top:34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest_cart .cart_name{
|
||||||
|
width:158px;height:38px;line-height:38px;border:1px solid #dddddd;display:block;background:url(../images/icons.png) 13px -300px no-repeat;font-size:14px;color:#37ab40;text-indent:56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest_cart .goods_count{
|
||||||
|
width:40px;height:40px;text-align:center;line-height:40px;font-size:18px;
|
||||||
|
font-weight:bold;color:#fff;background-color:#ff8800;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 菜单、幻灯片样式 */
|
||||||
|
|
||||||
|
.navbar_con{height:40px;border-bottom:2px solid #39a93e}
|
||||||
|
.navbar{width:1200px;margin:0 auto;}
|
||||||
|
.navbar h1{width:200px;height:40px;line-height:40px;text-align: center;font-size:14px;color:#fff;background-color:#39a93e;}
|
||||||
|
|
||||||
|
.navbar .subnav_con{width:200px;height:40px;background-color:#39a93e;position:relative;cursor:pointer;}
|
||||||
|
|
||||||
|
.navbar .subnav_con h1{position:absolute;left:0;top:0;text-align:left;text-indent:40px}
|
||||||
|
.navbar .subnav_con span{display:block;width:16px;height:9px;background:url(../images/down.png) no-repeat;position:absolute;right:27px;top:16px;transition:all 300ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .subnav_con:hover span{transform:rotateZ(180deg)}
|
||||||
|
|
||||||
|
.navbar .subnav_con .subnav{position:absolute;left:0;top:40px;display:none;border-top:2px solid #39a93e;}
|
||||||
|
.navbar .subnav_con:hover .subnav{display:block;}
|
||||||
|
|
||||||
|
|
||||||
|
.navlist{margin-left:34px;}
|
||||||
|
.navlist li{height:40px;float:left;line-height:40px;}
|
||||||
|
.navlist li a{color:#666;font-size:14px}
|
||||||
|
.navlist li a:hover{color:#ff8800}
|
||||||
|
.navlist .interval{margin:0 15px;}
|
||||||
|
|
||||||
|
|
||||||
|
.center_con{width:1200px;height:270px;margin:0 auto;}
|
||||||
|
.subnav{width:198px;height:270px;border-left:1px solid #eee;border-right:1px solid #eee;}
|
||||||
|
.subnav li{height:44px;border-bottom:1px solid #eee;background:url(../images/icons.png) 178px -257px no-repeat #fff;}
|
||||||
|
|
||||||
|
.subnav li a{display:block;height:44px;line-height:44px;text-indent:71px;font-size:14px;color:#333}
|
||||||
|
.subnav li a:hover{color:#ff8800}
|
||||||
|
|
||||||
|
.subnav li .fruit{background:url(../images/icons.png) 28px 0px no-repeat;}
|
||||||
|
.subnav li .seafood{background:url(../images/icons.png) 28px -43px no-repeat;}
|
||||||
|
.subnav li .meet{background:url(../images/icons.png) 28px -86px no-repeat;}
|
||||||
|
.subnav li .egg{background:url(../images/icons.png) 28px -132px no-repeat;}
|
||||||
|
.subnav li .vegetables{background:url(../images/icons.png) 28px -174px no-repeat;}
|
||||||
|
.subnav li .ice{background:url(../images/icons.png) 28px -220px no-repeat;}
|
||||||
|
|
||||||
|
|
||||||
|
.slide{width:760px;height:270px;position:relative;overflow:hidden;}
|
||||||
|
.slide .slide_pics{position:relative;left:0;top:0;width:760px;height:270px;}
|
||||||
|
.slide .slide_pics li{width:760px;height:270px;position:absolute;left:0;top:0}
|
||||||
|
.slide .prev,.slide .next{width:17px;height:23px;background:url(../images/icons.png) left -388px no-repeat;position:absolute;left:11px;top:122px;cursor:pointer;}
|
||||||
|
.slide .next{background-position:left -428px;left:732px;}
|
||||||
|
.points{width:100%;height:11px;position:absolute;left:0;top:250px;text-align:center;}
|
||||||
|
.points li{display:inline-block;width:11px;height:11px;margin:0 5px;background-color:#9f9f9f;border-radius:50%;cursor:pointer;}
|
||||||
|
.points li.active{background-color:#cecece}
|
||||||
|
|
||||||
|
.adv{width:240px;height:270px; overflow:hidden; background-color:gold;}
|
||||||
|
.adv a{display:block;float:left;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 商品列表样式 */
|
||||||
|
|
||||||
|
.list_model{width:1200px;height:340px;margin:15px auto 0;}
|
||||||
|
.list_title{height:40px;border-bottom:2px solid #42ad46}
|
||||||
|
.list_title h3{height:40px;line-height:40px;font-size:16px;color:#37ab40;font-weight:bold;}
|
||||||
|
.list_title .subtitle{height:20px;line-height:20px;margin-top:15px;}
|
||||||
|
.list_title .subtitle span{color:#666;margin:0 10px 0 20px;}
|
||||||
|
.list_title .subtitle a{color:#666;margin:0 5px;}
|
||||||
|
.list_title .subtitle a:hover,.goods_more:hover{color:#ff8800}
|
||||||
|
.goods_more{height:20px;margin-top:15px;color:#666}
|
||||||
|
|
||||||
|
.goods_con{height:300px;}
|
||||||
|
.goods_banner{width:200px;height:300px;}
|
||||||
|
.goods_banner img{width:200px;height:300px;}
|
||||||
|
|
||||||
|
.goods_list{width:1000px;height:299px;border-bottom:1px solid #ededed}
|
||||||
|
.goods_list li{height:299px;width:249px;border-right:1px solid #ededed;float:left}
|
||||||
|
.goods_list li:hover{width:248px;height:297px;border:1px solid gold;}
|
||||||
|
.goods_list li:hover img{opacity:0.8}
|
||||||
|
|
||||||
|
.goods_list li h4{width:200px;height:50px;margin:20px auto 0;text-align:center;}
|
||||||
|
.goods_list li h4 a{font-size:14px;color:#666;font-weight:normal;line-height:24px;}
|
||||||
|
.goods_list li h4 a:hover{color:#ff8800}
|
||||||
|
|
||||||
|
.goods_list li img{display:block;width:180px;height:180px;margin:0 auto;}
|
||||||
|
.goods_list li .prize{text-align:center;font-size:20px;color:#c40000;margin-top:5px;}
|
||||||
|
|
||||||
|
/* 页面底部样式 */
|
||||||
|
.footer{
|
||||||
|
border-top:2px solid #42ad46;
|
||||||
|
margin:30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foot_link{text-align:center;margin-top:30px;}
|
||||||
|
.foot_link a,.foot_link span{color:#4e4e4e;}
|
||||||
|
.foot_link a:hover{color:#ff8800}
|
||||||
|
.foot_link span{padding:0 10px}
|
||||||
|
.footer p{text-align:center; margin-top:10px;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 二级页面面包屑导航 */
|
||||||
|
.breadcrumb{
|
||||||
|
width:1200px;height:40px;margin:0 auto;
|
||||||
|
}
|
||||||
|
.breadcrumb a{line-height:40px;color:#37ab40}
|
||||||
|
.breadcrumb a:hover{color:#ff8800}
|
||||||
|
.breadcrumb span{line-height:40px;color:#666;padding:0 5px;}
|
||||||
|
|
||||||
|
|
||||||
|
.main_wrap{width:1200px;margin:0 auto;}
|
||||||
|
.l_wrap{width:200px;}
|
||||||
|
.r_wrap{width:980px;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 新品推荐样式 */
|
||||||
|
|
||||||
|
.new_goods{
|
||||||
|
border:1px solid #ededed;
|
||||||
|
border-top:2px solid #37ab40;
|
||||||
|
padding-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new_goods h3{
|
||||||
|
height:33px;line-height:33px;background-color:#fcfcfc;border-bottom:1px solid #ededed;font-size:14px;font-weight:normal;text-indent:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new_goods ul{width:160px;margin:0 auto;overflow:hidden;}
|
||||||
|
.new_goods li{border-bottom:1px solid #ededed;margin-bottom:-1px;}
|
||||||
|
.new_goods li img{display:block;width:150px;height:150px;margin:10px auto;}
|
||||||
|
.new_goods li h4{width:160px;margin:0 auto;}
|
||||||
|
.new_goods li h4 a{font-weight:normal;color:#666;display:block;width:160px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
|
||||||
|
.new_goods li .prize{font-size:14px;color:#da260e;margin:10px auto;}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 商品列表样式 */
|
||||||
|
|
||||||
|
.sort_bar{height:30px;background-color:#f0fdec}
|
||||||
|
.sort_bar a{display:block;height:30px;line-height:30px;padding:0 20px;float:left;color:#000}
|
||||||
|
.sort_bar .active{background-color:#37ab40;color:#fff;}
|
||||||
|
|
||||||
|
|
||||||
|
.goods_type_list{
|
||||||
|
margin:10px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods_type_list li{
|
||||||
|
width:196px;
|
||||||
|
float:left;
|
||||||
|
margin-bottom:10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods_type_list li img{width:160px;height:160px;display:block;margin:10px auto;}
|
||||||
|
.goods_type_list li h4{width:160px;margin:0 auto;}
|
||||||
|
.goods_type_list li h4 a{font-weight:normal;color:#666;display:block;width:160px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
|
||||||
|
.operate{width:160px;margin:10px auto;position:relative;}
|
||||||
|
.goods_type_list .operate .prize{color:#da260e; font-size:14px;}
|
||||||
|
.goods_type_list .operate .unit{color:#999;padding-left:5px;}
|
||||||
|
.goods_type_list .operate .add_goods{display:inline-block;width:15px;height:15px;background:url(../images/shop_cart.png);position:absolute;right:0;top:3px;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 分页样式 */
|
||||||
|
|
||||||
|
.pagenation{height:32px;text-align:center;font-size:0;margin:30px auto;}
|
||||||
|
.pagenation a{display:inline-block;border:1px solid #d2d2d2;background-color:#f8f6f7;font-size:12px;padding:7px 10px;color:#666;margin:5px}
|
||||||
|
|
||||||
|
.pagenation .active{background-color:#fff;color:#43a200}
|
||||||
|
|
||||||
|
|
||||||
|
/* 商品详情样式 */
|
||||||
|
.goods_detail_con{
|
||||||
|
width:1198px;
|
||||||
|
height:398px;
|
||||||
|
border:1px solid #ededed;
|
||||||
|
margin:0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods_detail_pic{width:350px;height:350px;margin:24px 0 0 24px;}
|
||||||
|
.goods_detail_list{
|
||||||
|
width:730px;height:350px;margin:24px 24px 0 0;
|
||||||
|
}
|
||||||
|
.goods_detail_list h3{font-size:24px;line-height:24px;color:#666;font-weight:normal;}
|
||||||
|
.goods_detail_list p{color:#666;line-height:40px;}
|
||||||
|
.prize_bar{height:72px;background-color:#fff5f5;line-height:72px;}
|
||||||
|
.prize_bar .show_pirze{font-size:20px;color:#ff3e3e;padding-left:20px}
|
||||||
|
.prize_bar .show_pirze em{font-style:normal;font-size:36px;padding-left:10px}
|
||||||
|
.prize_bar .show_unit{padding-left:150px}
|
||||||
|
|
||||||
|
.goods_num{height:52px;margin-top:19px;}
|
||||||
|
.goods_num .num_name{width:70px;height:52px;line-height:52px;}
|
||||||
|
.goods_num .num_add{width:75px;height:50px;border:1px solid #dddddd}
|
||||||
|
.goods_num .num_add input{width:49px;height:50px;text-align:center;line-height:50px;border:0px;outline:none;font-size:14px;color:#666}
|
||||||
|
.goods_num .num_add .add,.goods_num .num_add .minus{width:25px;line-height:25px;text-align:center;border-left:1px solid #ddd;border-bottom:1px solid #ddd;color:#666;font-size:14px}
|
||||||
|
.goods_num .num_add .minus{border-bottom:0px}
|
||||||
|
|
||||||
|
.total{height:35px;line-height:35px;margin-top:25px;}
|
||||||
|
.total em{font-style:normal;color:#ff3e3e;font-size:18px}
|
||||||
|
|
||||||
|
.operate_btn{height:40px;margin-top:35px;font-size:0;position:relative;}
|
||||||
|
.operate_btn .buy_btn,.operate_btn .add_cart{display:inline-block;width:178px;height:38px;border:1px solid #c40000;font-size:14px;color:#c40000;line-height:38px;text-align:center;background-color:#ffeded;}
|
||||||
|
.operate_btn .add_cart{background-color:#c40000;color:#fff;margin-left:10px;position:relative;z-index:10;}
|
||||||
|
|
||||||
|
.add_jump{width:20px;height:20px;background-color:#c40000;position:absolute;left:268px;top:10px;border-radius:50%;z-index:9;display:none;}
|
||||||
|
|
||||||
|
.detail_tab{
|
||||||
|
height:35px;
|
||||||
|
border-bottom:1px solid #37ab40
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail_tab li{height:34px;line-height:34px;padding:0 30px;font-size:14px;color:#333333;float:left;border:1px solid #e8e8e8;border-bottom:0px;cursor:pointer;background-color:#faf8f8}
|
||||||
|
|
||||||
|
.detail_tab li.active{border-top:2px solid #37ab40;position:relative;background-color:#fff;border-left:1px solid #37ab40;border-right:1px solid #37ab40;top:-1px;height:35px;}
|
||||||
|
|
||||||
|
.tab_content dt{margin-top:10px;font-size:16px;color:#044d39}
|
||||||
|
.tab_content dd{line-height:24px;margin-top:5px;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 登录页 */
|
||||||
|
|
||||||
|
.login_top{width:960px;height:130px;margin:0 auto;}
|
||||||
|
.login_logo{display:block;width:193px;height:76px;margin-top:30px;}
|
||||||
|
.login_form_bg{height:480px;background-color:#518e17}
|
||||||
|
.no-mp{margin-top:0px;}
|
||||||
|
.login_form_wrap{width:960px;height:480px;margin:0 auto;}
|
||||||
|
.login_banner{width:381px;height:322px;background:url(../images/login_banner.png) no-repeat;margin-top:90px;}
|
||||||
|
.slogan{width:40px;height:300px;font-size:30px;color:#f0f9e8;text-align:center;line-height:36px;margin:80px 0 0 120px}
|
||||||
|
.login_form{width:368px;height:378px;border:1px solid #c6c6c5;background-color:#fff; margin-top:50px;}
|
||||||
|
|
||||||
|
.login_title{height:60px;width:308px;margin:10px auto;border-bottom:1px solid #e0e0e0;}
|
||||||
|
|
||||||
|
.login_title h1{font-size:24px;height:60px;line-height:60px;color:#a8a8a8;float:left;font-weight:bold;margin-left:44px;}
|
||||||
|
.login_title a{width:100px;height:20px;display:block;font-size:16px;color:#5fb42a;text-indent:26px;background:url(../images/icons02.png) left 5px no-repeat;float:left;margin:20px 0 0 36px}
|
||||||
|
|
||||||
|
.form_input{width:308px;height:250px;margin:20px auto;position:relative;}
|
||||||
|
.name_input,.pass_input{width:306px;height:36px;border:1px solid #e0e0e0;background:url(../images/icons02.png) 280px -41px no-repeat #f8f8f8;outline:none;font-size:14px;text-indent:10px;position: absolute;left:0;top:0}
|
||||||
|
.pass_input{top:65px;background-position:280px -95px;}
|
||||||
|
|
||||||
|
.user_error,.pwd_error{color:#f00;position:absolute;left:0;top:43px;display:none}
|
||||||
|
|
||||||
|
.pwd_error{top:110px;}
|
||||||
|
|
||||||
|
.more_input{position:absolute;left:0;top:130px;width:100%}
|
||||||
|
|
||||||
|
.more_input input{float:left;margin-top:2px;}
|
||||||
|
.more_input label{float:left;margin-left:10px;}
|
||||||
|
.more_input a{float:right;color:#666}
|
||||||
|
.more_input a:hover{color:#ff8800}
|
||||||
|
|
||||||
|
.input_submit{width:100%;height:40px;position:absolute;left:0;top:180px;background-color:#47aa34;color:#fff;font-size:22px;border:0px;font-family:'Microsoft Yahei';cursor:pointer;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 注册页面 */
|
||||||
|
.register_con{
|
||||||
|
width:700px;
|
||||||
|
height:560px;
|
||||||
|
margin:50px auto 0;
|
||||||
|
background:url(../images/interval_line.png) 300px top no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l_con{width:300px;}
|
||||||
|
.reg_logo{width:200px;height:76px;float:right;margin-right:30px;}
|
||||||
|
.reg_slogan{width:300px;height:30px;float:right;text-align:right;font-size:24px;color:#69a81e;margin:20px 30px 0 0;}
|
||||||
|
.reg_banner{width:251px;height:329px;background:url(../images/register_banner.png) no-repeat;float:right; margin:20px 10px 0 0;opacity:0.5}
|
||||||
|
|
||||||
|
|
||||||
|
.r_con{width:400px;}
|
||||||
|
.reg_title{width:360px;height:50px;float:left;margin-left:30px;border-bottom:1px solid #e0e0e0}
|
||||||
|
.reg_title h1{height:50px;line-height:50px;float:left;font-size:24px;color:#a8a8a8;font-weight:bold;}
|
||||||
|
.reg_title a{float:right;height:20px;line-height:20px;font-size:16px;color:#5fb42a;padding-right:20px;background:url(../images/icons02.png) 35px 3px no-repeat;margin-top:15px}
|
||||||
|
|
||||||
|
.reg_form{width:360px;margin:30px 0 0 30px;float:left;position:relative;}
|
||||||
|
.reg_form li{height:70px;}
|
||||||
|
.reg_form li label{width:70px;height:40px;line-height:40px;float:left;font-size:14px;color:#a8a8a8}
|
||||||
|
.reg_form li input{width:288px;height:38px;border:1px solid #e0e0e0;float:left;outline:none;text-indent:10px;background-color:#f8f8f8}
|
||||||
|
.reg_form li.agreement input{width:15px;height:15px;float:left;margin-top:13px}
|
||||||
|
.reg_form li.agreement label{width:300px;float:left;margin-left:10px;}
|
||||||
|
.reg_form li.reg_sub input{width:360px;height:40px;background-color:#47aa34;font-size:18px;color:#fff;font-family:'Microsoft Yahei';cursor:pointer;}
|
||||||
|
.reg_form li .error_tip{float:left;height:30px;line-height:30px;margin-left:70px;color:#e62e2e;display:none;}
|
||||||
|
.reg_form li .error_tip2{float:left;height:20px;line-height:20px;color:#e62e2e;display:none;}
|
||||||
|
|
||||||
|
|
||||||
|
.sub_page_name{font-size:18px;color:#666;margin:50px 0 0 20px}
|
||||||
|
|
||||||
|
.total_count{
|
||||||
|
width:1200px;margin:0 auto;height:40px;line-height:40px;font-size:14px;
|
||||||
|
}
|
||||||
|
.total_count em{
|
||||||
|
font-size:16px;color:#ff4200;margin:0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart_list_th{width:1198px;border:1px solid #ddd;background-color:#f7f7f7;margin:0 auto;}
|
||||||
|
.cart_list_th li{height:40px;line-height:40px;float:left;text-align:center;}
|
||||||
|
.cart_list_th .col01{width:26%;}
|
||||||
|
.cart_list_th .col02{width:16%;}
|
||||||
|
.cart_list_th .col03{width:13%;}
|
||||||
|
.cart_list_th .col04{width:12%;}
|
||||||
|
.cart_list_th .col05{width:15%;}
|
||||||
|
.cart_list_th .col06{width:18%;}
|
||||||
|
|
||||||
|
.cart_list_td{width:1198px;border:1px solid #ddd;background-color:#edfff9;margin:0 auto;margin-top:-1px;}
|
||||||
|
.cart_list_td li{height:120px;line-height:120px;float:left;text-align:center;}
|
||||||
|
|
||||||
|
.cart_list_td .col01{width:4%;}
|
||||||
|
.cart_list_td .col02{width:12%;}
|
||||||
|
.cart_list_td .col03{width:10%;}
|
||||||
|
.cart_list_td .col04{width:16%;}
|
||||||
|
.cart_list_td .col05{width:13%;}
|
||||||
|
.cart_list_td .col06{width:12%;}
|
||||||
|
.cart_list_td .col07{width:15%;}
|
||||||
|
.cart_list_td .col08{width:18%;}
|
||||||
|
|
||||||
|
.cart_list_td .col02 img{width:100px;height:100px;border:1px solid #ddd;display:block;margin:10px auto 0;}
|
||||||
|
.cart_list_td .col03{height:48px;text-align:left;line-height:24px;margin-top:38px;}
|
||||||
|
.cart_list_td .col03 em{color:#999}
|
||||||
|
.cart_list_td .col08 a{color:#666}
|
||||||
|
|
||||||
|
.cart_list_td .col06 .num_add{width:98px;height:28px;border:1px solid #ddd;margin:40px auto 0;}
|
||||||
|
.cart_list_td .col06 .num_add a{width:29px;height:28px;line-height:28px;background-color:#f3f3f3;font-size:14px;color:#666}
|
||||||
|
.cart_list_td .col06 .num_add input{width:38px;height:28px;text-align:center;line-height:30px;border:0px;display:block;float:left;outline:none;border-left:1px solid #ddd;border-right:1px solid #ddd;}
|
||||||
|
|
||||||
|
|
||||||
|
.settlements{width:1198px;height:78px;border:1px solid #ddd;background-color:#fff4e8;margin:-1px auto 0;}
|
||||||
|
.settlements li{line-height:78px;float:left;}
|
||||||
|
.settlements .col01{width:4%;text-align:center}
|
||||||
|
.settlements .col02{width:12%;}
|
||||||
|
.settlements .col03{width:69%; height:48px; line-height:28px;text-align:right;margin-top:10px;}
|
||||||
|
.settlements .col03 span{color:#ff0000;padding-right:5px}
|
||||||
|
.settlements .col03 em{color:#ff3d3d;font-size:22px;font-weight:bold;}
|
||||||
|
.settlements .col03 span{color:#ff0000;}
|
||||||
|
.settlements .col03 b{color:#ff0000;font-size:14px;padding:0 5px;}
|
||||||
|
|
||||||
|
.settlements .col04{width:14%;text-align:center;float:right;}
|
||||||
|
.settlements .col04 a{display:block;height:78px;background-color:#ff3d3d;text-align:center;line-height:78px;color:#fff;font-size:24px}
|
||||||
|
|
||||||
|
|
||||||
|
.common_title{width:1200px;margin:20px auto 0;font-size:14px;}
|
||||||
|
|
||||||
|
.common_list_con{width:1200px;border:1px solid #dddddd;border-top:2px solid #00bc6f;margin:10px auto 0;background-color:#f7f7f7;position:relative;}
|
||||||
|
|
||||||
|
.common_list_con dl{margin:20px;}
|
||||||
|
.common_list_con dt{font-size:14px;font-weight:bold;margin-bottom:10px}
|
||||||
|
.common_list_con dd input{vertical-align:bottom;margin-right:10px}
|
||||||
|
|
||||||
|
.edit_site{position:absolute; right:20px;top:30px;width:100px;height:30px;background-color:#37ab40;text-align:center;line-height:30px;color:#fff}
|
||||||
|
|
||||||
|
.pay_style_con{margin:20px;}
|
||||||
|
.pay_style_con input{float:left;margin:14px 7px 0 0;}
|
||||||
|
.pay_style_con label{float:left;border:1px solid #ccc;background-color:#fff;padding:10px 10px 10px 40px;margin-right:25px}
|
||||||
|
|
||||||
|
.pay_style_con .cash{background:url(../images/pay_icons.png) 8px top no-repeat #fff;}
|
||||||
|
.pay_style_con .weixin{background:url(../images/pay_icons.png) 6px -36px no-repeat #fff;}
|
||||||
|
|
||||||
|
.pay_style_con .zhifubao{background:url(../images/pay_icons.png) 12px -72px no-repeat #fff;width:50px;height:16px}
|
||||||
|
|
||||||
|
.pay_style_con .bank{background:url(../images/pay_icons.png) 6px -108px no-repeat #fff;}
|
||||||
|
|
||||||
|
|
||||||
|
.goods_list_th{height:40px;border-bottom:1px solid #ccc}
|
||||||
|
.goods_list_th li{float:left;line-height:40px;text-align:center;}
|
||||||
|
.goods_list_th .col01{width:25%}
|
||||||
|
.goods_list_th .col02{width:20%}
|
||||||
|
.goods_list_th .col03{width:25%}
|
||||||
|
.goods_list_th .col04{width:15%}
|
||||||
|
.goods_list_th .col05{width:15%}
|
||||||
|
|
||||||
|
.goods_list_td{height:80px;border-bottom:1px solid #eeeded}
|
||||||
|
.goods_list_td li{float:left;line-height:80px;text-align:center;}
|
||||||
|
.goods_list_td .col01{width:4%}
|
||||||
|
.goods_list_td .col02{width:6%}
|
||||||
|
.goods_list_td .col03{width:15%}
|
||||||
|
.goods_list_td .col04{width:20%}
|
||||||
|
.goods_list_td .col05{width:25%}
|
||||||
|
.goods_list_td .col06{width:15%}
|
||||||
|
.goods_list_td .col07{width:15%}
|
||||||
|
|
||||||
|
.goods_list_td .col02{text-align:right}
|
||||||
|
.goods_list_td .col02 img{width:63px;height:63px;border:1px solid #ddd;display:block;margin:7px 0;float:right;}
|
||||||
|
.goods_list_td .col03{text-align:left;text-indent:20px}
|
||||||
|
|
||||||
|
|
||||||
|
.settle_con{margin:10px}
|
||||||
|
.total_goods_count,.transit,.total_pay{line-height:24px;text-align:right}
|
||||||
|
.total_goods_count em,.total_goods_count b,.transit b,.total_pay b{font-size:14px;color:#ff4200;padding:0 5px;}
|
||||||
|
|
||||||
|
.order_submit{width:1200px;margin:20px auto;}
|
||||||
|
.order_submit a{width:160px;height:40px;line-height:40px;text-align:center;background-color:#47aa34;color:#fff;font-size:16px;display:block;float:right}
|
||||||
|
|
||||||
|
|
||||||
|
.order_list_th{width:1198px;border:1px solid #ddd;background-color:#f7f7f7;margin:20px auto 0;}
|
||||||
|
.order_list_th li{float:left;height:30px;line-height:30px}
|
||||||
|
.order_list_th .col01{width:20%;margin-left:20px}
|
||||||
|
.order_list_th .col02{width:20%}
|
||||||
|
|
||||||
|
|
||||||
|
.order_list_table{
|
||||||
|
width:1200px;
|
||||||
|
border-collapse:collapse;
|
||||||
|
border-spacing:0px;
|
||||||
|
border:1px solid #ddd;
|
||||||
|
margin:-1px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order_list_table td{
|
||||||
|
border:1px solid #ddd;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order_goods_list{border-bottom:1px solid #ddd;margin-bottom:-2px;}
|
||||||
|
.order_goods_list li{float:left; height:80px;line-height:80px;}
|
||||||
|
.order_goods_list .col01{width:20%}
|
||||||
|
.order_goods_list .col01 img{width:60px;height:60px;border:1px solid #ddd;margin:10px auto;}
|
||||||
|
.order_goods_list .col02{width:50%;text-align:left;}
|
||||||
|
.order_goods_list .col02 em{color:#999;margin-left:10px}
|
||||||
|
.order_goods_list .col03{width:10%}
|
||||||
|
.order_goods_list .col04{width:20%}
|
||||||
|
|
||||||
|
.oper_btn{display:inline-block;border:1px solid #ddd;color:#666;padding:5px 10px}
|
||||||
|
|
||||||
|
.popup_con{display:none;}
|
||||||
|
.popup{width:300px;height:150px;border:1px solid #dddddd;border-top:2px solid #00bc6f;background-color:#f7f7f7;position:fixed;
|
||||||
|
left:50%;
|
||||||
|
margin-left:-150px;
|
||||||
|
top:50%;
|
||||||
|
margin-top:-75px;
|
||||||
|
z-index:1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup p{height:150px;line-height:150px;text-align:center;font-size:18px;}
|
||||||
|
|
||||||
|
.mask{width:100%;height:100%;position:fixed;left:0;top:0;background-color:#000;opacity:0.3;z-index:999;}
|
||||||
|
|
||||||
|
|
||||||
|
.main_con{
|
||||||
|
width:1200px;
|
||||||
|
margin:0 auto;
|
||||||
|
background:url(../images/left_bg.jpg) repeat-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_menu_con{
|
||||||
|
width:200px;
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_menu_con h3{
|
||||||
|
font-size:16px;
|
||||||
|
line-height:40px;
|
||||||
|
border-bottom:1px solid #ddd;
|
||||||
|
text-align:center;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_menu_con ul li{
|
||||||
|
line-height:40px;
|
||||||
|
text-align:center;
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_menu_con ul li a{
|
||||||
|
color:#666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_menu_con ul li .active{
|
||||||
|
color:#ff8800;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_content{
|
||||||
|
width:980px;
|
||||||
|
float:right;
|
||||||
|
min-height:500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w980{
|
||||||
|
width:980px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w978{
|
||||||
|
width:978px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.common_title2{height:20px;line-height:20px;font-size:16px;margin:10px 0;}
|
||||||
|
.user_info_list{
|
||||||
|
background-color:#f9f9f9;
|
||||||
|
margin:10px 0 15px;
|
||||||
|
padding:10px 0;
|
||||||
|
height:90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_info_list li{
|
||||||
|
line-height:30px;
|
||||||
|
text-indent:30px;
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_info_list li span{
|
||||||
|
width:100px;
|
||||||
|
float:left;
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_con{
|
||||||
|
width:980px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_l{
|
||||||
|
width:600px;
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_r{
|
||||||
|
width:360px;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con{
|
||||||
|
background-color:#f9f9f9;
|
||||||
|
padding:10px 0;
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con dt{
|
||||||
|
font-size:14px;
|
||||||
|
line-height:30px;
|
||||||
|
text-indent:30px;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con dd{
|
||||||
|
font-size:14px;
|
||||||
|
line-height:30px;
|
||||||
|
text-indent:30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con .form_group{
|
||||||
|
height:40px;
|
||||||
|
line-height:40px;
|
||||||
|
margin-top:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con .form_group label{
|
||||||
|
width:100px;
|
||||||
|
float:left;
|
||||||
|
text-align:right;
|
||||||
|
font-size:14px;
|
||||||
|
height:40px;
|
||||||
|
line-height:40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con .form_group input{
|
||||||
|
width:300px;
|
||||||
|
height:25px;
|
||||||
|
border:1px solid #ddd;
|
||||||
|
float:left;
|
||||||
|
outline:none;
|
||||||
|
margin-top:7px;
|
||||||
|
text-indent:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_con .form_group2{
|
||||||
|
height:90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site_area{
|
||||||
|
width:280px;
|
||||||
|
height:60px;
|
||||||
|
border:1px solid #ddd;
|
||||||
|
outline:none;
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
.info_submit{
|
||||||
|
width:80px;
|
||||||
|
height:30px;
|
||||||
|
background-color:#37ab40;
|
||||||
|
border:0px;
|
||||||
|
color:#fff;
|
||||||
|
margin:10px 0 10px 100px;
|
||||||
|
cursor:pointer;
|
||||||
|
font-family:'Microsoft Yahei'
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress{
|
||||||
|
color:#ff8800;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/* 把标签默认的间距设为0 */
|
||||||
|
body,ul,ol,p,h1,h2,h3,h4,h5,h6,dl,dd,select,input,textarea,form{margin:0;padding:0}
|
||||||
|
|
||||||
|
/* 让h标签文字大小继承body的文字设置 */
|
||||||
|
h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}
|
||||||
|
|
||||||
|
/* 去掉列表默认的图标 */
|
||||||
|
ul,ol{list-style:none;}
|
||||||
|
|
||||||
|
/* 去掉em默认的斜体 */
|
||||||
|
em{font-style: normal;}
|
||||||
|
|
||||||
|
/* 去掉a标签默认的下划线 */
|
||||||
|
a{text-decoration:none;}
|
||||||
|
|
||||||
|
|
||||||
|
/* 去掉加链接时产生的框线 */
|
||||||
|
img{border:0;}
|
||||||
|
|
||||||
|
/* 清除浮动 */
|
||||||
|
.clearfix:before,.clearfix:after{content:"";display:table}
|
||||||
|
.clearfix:after{clear:both;}
|
||||||
|
.clearfix{zoom:1}
|
||||||
|
|
||||||
|
/* 浮动 */
|
||||||
|
.fl{float:left}
|
||||||
|
.fr{float:right}
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 553 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 153 KiB |
After Width: | Height: | Size: 9.8 KiB |